diff --git a/glide.lock b/glide.lock index 4ae361858..5c88c01d9 100644 --- a/glide.lock +++ b/glide.lock @@ -1,8 +1,8 @@ -hash: 4288149814e91e63396f7874b87151aca74047110f0d1f43027b60dd6014988f -updated: 2019-01-09T10:19:29.688011713Z +hash: 51c5c611e1db067eccc443c03a60c1f961b29aed9f85b081826ce229b5c63d8a +updated: 2019-05-15T17:03:46.44233316+09:00 imports: - name: github.com/containernetworking/cni - version: 07c1a6da47b7fbf8b357f4949ecce2113e598491 + version: 7d76556571b6cf1ab90d7026a73092ac8d5e0c23 subpackages: - libcni - pkg/invoke @@ -12,7 +12,7 @@ imports: - pkg/types/current - pkg/version - name: github.com/containernetworking/plugins - version: 2b8b1ac0af4568e928d96ccc5f47b075416eeabd + version: 0950a3607bf5e8a57c6a655c7e573e6aab0dc650 subpackages: - pkg/ip - pkg/ipam @@ -65,7 +65,6 @@ imports: version: 7f8ab55aaf3b86885aa55b762e803744d1674700 subpackages: - config - - extensions/table - internal/codelocation - internal/containernode - internal/failer @@ -96,7 +95,7 @@ imports: - name: github.com/peterbourgon/diskv version: 5f041e8faa004a95c88a202771f4cc3e991971e6 - name: github.com/pkg/errors - version: ffb6e22f01932bf7ac35e0bad9be11f01d1c8685 + version: 645ef00459ed84a119197bfb8d8205042c6df63d - name: github.com/spf13/pflag version: 583c0c0531f06d5278b7d917446061adc344b5cd - name: github.com/vishvananda/netlink @@ -176,7 +175,6 @@ imports: - name: k8s.io/api version: 2d6f90ab1293a1fb871cf149423ebb72aa7423aa subpackages: - - admission/v1beta1 - admissionregistration/v1alpha1 - admissionregistration/v1beta1 - apps/v1 diff --git a/glide.yaml b/glide.yaml index 0c2265dae..20a500cd4 100644 --- a/glide.yaml +++ b/glide.yaml @@ -3,13 +3,13 @@ ignore: - bytes import: - package: github.com/containernetworking/cni - version: 07c1a6da47b7fbf8b357f4949ecce2113e598491 + version: v0.7.0 subpackages: - pkg/skel - pkg/types - pkg/version - package: github.com/containernetworking/plugins - version: 2b8b1ac0af4568e928d96ccc5f47b075416eeabd + version: v0.8.0 subpackages: - pkg/ip - pkg/ipam diff --git a/multus/multus.go b/multus/multus.go index b3eab05b8..e8a68ff5e 100644 --- a/multus/multus.go +++ b/multus/multus.go @@ -19,6 +19,7 @@ package main import ( + "context" "encoding/json" "flag" "fmt" @@ -155,7 +156,7 @@ func conflistAdd(rt *libcni.RuntimeConf, rawnetconflist []byte, binDir string, e return nil, logging.Errorf("error in converting the raw bytes to conflist: %v", err) } - result, err := cniNet.AddNetworkList(confList, rt) + result, err := cniNet.AddNetworkList(context.Background(), confList, rt) if err != nil { return nil, logging.Errorf("error in getting result from AddNetworkList: %v", err) } @@ -175,7 +176,7 @@ func conflistDel(rt *libcni.RuntimeConf, rawnetconflist []byte, binDir string, e return logging.Errorf("error in converting the raw bytes to conflist: %v", err) } - err = cniNet.DelNetworkList(confList, rt) + err = cniNet.DelNetworkList(context.Background(), confList, rt) if err != nil { return logging.Errorf("error in getting result from DelNetworkList: %v", err) } @@ -237,7 +238,7 @@ func delegateAdd(exec invoke.Exec, ifName string, delegate *types.DelegateNetCon return nil, logging.Errorf("Multus: error in invoke Conflist add - %q: %v", delegate.ConfList.Name, err) } } else { - result, err = invoke.DelegateAdd(delegate.Conf.Type, delegate.Bytes, exec) + result, err = invoke.DelegateAdd(context.Background(), delegate.Conf.Type, delegate.Bytes, exec) if err != nil { return nil, logging.Errorf("Multus: error in invoke Delegate add - %q: %v", delegate.Conf.Type, err) } @@ -281,7 +282,7 @@ func delegateDel(exec invoke.Exec, ifName string, delegateConf *types.DelegateNe return logging.Errorf("Multus: error in invoke Conflist Del - %q: %v", delegateConf.ConfList.Name, err) } } else { - if err = invoke.DelegateDel(delegateConf.Conf.Type, delegateConf.Bytes, exec); err != nil { + if err = invoke.DelegateDel(context.Background(), delegateConf.Conf.Type, delegateConf.Bytes, exec); err != nil { return logging.Errorf("Multus: error in invoke Delegate del - %q: %v", delegateConf.Conf.Type, err) } } diff --git a/multus/multus_test.go b/multus/multus_test.go index 961b383a2..d98f36c5c 100644 --- a/multus/multus_test.go +++ b/multus/multus_test.go @@ -16,6 +16,7 @@ package main import ( + "context" "encoding/json" "fmt" "io/ioutil" @@ -97,7 +98,7 @@ func gatherCNIEnv() []string { return filtered } -func (f *fakeExec) ExecPlugin(pluginPath string, stdinData []byte, environ []string) ([]byte, error) { +func (f *fakeExec) ExecPlugin(ctx context.Context, pluginPath string, stdinData []byte, environ []string) ([]byte, error) { cmd := os.Getenv("CNI_COMMAND") var index int switch cmd { diff --git a/vendor/github.com/containernetworking/cni/.gitignore b/vendor/github.com/containernetworking/cni/.gitignore index 0d94fb809..eb8635c67 100644 --- a/vendor/github.com/containernetworking/cni/.gitignore +++ b/vendor/github.com/containernetworking/cni/.gitignore @@ -1,3 +1,4 @@ +.idea/ bin/ gopath/ *.sw[ponm] diff --git a/vendor/github.com/containernetworking/cni/.travis.yml b/vendor/github.com/containernetworking/cni/.travis.yml index 4bbce9b27..1d735734d 100644 --- a/vendor/github.com/containernetworking/cni/.travis.yml +++ b/vendor/github.com/containernetworking/cni/.travis.yml @@ -2,8 +2,8 @@ language: go dist: trusty go: - - 1.9.x - 1.10.x + - 1.11.x env: matrix: diff --git a/vendor/github.com/containernetworking/cni/CONTRIBUTING.md b/vendor/github.com/containernetworking/cni/CONTRIBUTING.md index 817f072af..58654015a 100644 --- a/vendor/github.com/containernetworking/cni/CONTRIBUTING.md +++ b/vendor/github.com/containernetworking/cni/CONTRIBUTING.md @@ -16,7 +16,7 @@ contribution. See the [DCO](DCO) file for details. # Email and Chat -The project uses the the cni-dev email list and IRC chat: +The project uses the cni-dev email list and IRC chat: - Email: [cni-dev](https://groups.google.com/forum/#!forum/cni-dev) - IRC: #[containernetworking](irc://irc.freenode.org:6667/#containernetworking) channel on freenode.org @@ -38,7 +38,7 @@ This is a rough outline of how to prepare a contribution: - Make sure your commit messages are in the proper format (see below). - Push your changes to a topic branch in your fork of the repository. - If you changed code: - - add automated tests to cover your changes, using the [Ginkgo](http://onsi.github.io/ginkgo/) & [Gomega](http://onsi.github.io/gomega/) style + - add automated tests to cover your changes, using the [Ginkgo](https://onsi.github.io/ginkgo/) & [Gomega](https://onsi.github.io/gomega/) style - if the package did not previously have any test coverage, add it to the list of `TESTABLE` packages in the `test.sh` script. - run the full test script and ensure it passes diff --git a/vendor/github.com/containernetworking/cni/CONVENTIONS.md b/vendor/github.com/containernetworking/cni/CONVENTIONS.md index b9d5a3d9a..0200a94d6 100644 --- a/vendor/github.com/containernetworking/cni/CONVENTIONS.md +++ b/vendor/github.com/containernetworking/cni/CONVENTIONS.md @@ -58,8 +58,9 @@ But the runtime would fill in the mappings so the plugin itself would receive so | Area | Purpose | Capability | Spec and Example | Runtime implementations | Plugin Implementations | | ----- | ------- | -----------| ---------------- | ----------------------- | --------------------- | | port mappings | Pass mapping from ports on the host to ports in the container network namespace. | `portMappings` | A list of portmapping entries.
[
{ "hostPort": 8080, "containerPort": 80, "protocol": "tcp" },
{ "hostPort": 8000, "containerPort": 8001, "protocol": "udp" }
]
| kubernetes | CNI `portmap` plugin | -| ip ranges | Dynamically configure the IP range(s) for address allocation. Runtimes that manage IP pools, but not individual IP addresses, can pass these to plugins. | `ipRanges` | The same as the `ranges` key for `host-local` - a list of lists of subnets. The outer list is the number of IPs to allocate, and the inner list is a pool of subnets for each allocation.
[
[
{ "subnet": "10.1.2.0/24", "rangeStart": "10.1.2.3", "rangeEnd": 10.1.2.99", "gateway": "10.1.2.254" }
]
]
| none | cni `host-local` plugin | -| bandwidth limits | Dynamically configure interface bandwidth limits | `bandwidth` | Desired bandwidth limits. Rates are in bits per second, burst values are in bits.
 { "ingressRate": 2048, "ingressBurst": 1600, "egressRate": 4096, "egressBurst": 1600 } 
| none | cni `bandwidth` plugin | +| ip ranges | Dynamically configure the IP range(s) for address allocation. Runtimes that manage IP pools, but not individual IP addresses, can pass these to plugins. | `ipRanges` | The same as the `ranges` key for `host-local` - a list of lists of subnets. The outer list is the number of IPs to allocate, and the inner list is a pool of subnets for each allocation.
[
[
{ "subnet": "10.1.2.0/24", "rangeStart": "10.1.2.3", "rangeEnd": 10.1.2.99", "gateway": "10.1.2.254" }
]
]
| none | CNI `host-local` plugin | +| bandwidth limits | Dynamically configure interface bandwidth limits | `bandwidth` | Desired bandwidth limits. Rates are in bits per second, burst values are in bits.
 { "ingressRate": 2048, "ingressBurst": 1600, "egressRate": 4096, "egressBurst": 1600 } 
| none | CNI `bandwidth` plugin | +| Dns | Dymaically configure dns according to runtime | `dns` | Dictionary containing a list of `servers` (string entries), a list of `searches` (string entries), a list of `options` (string entries).
{ 
"searches" : [ "internal.yoyodyne.net", "corp.tyrell.net" ]
"servers": [ "8.8.8.8", "10.0.0.10" ]
}
| kubernetes | CNI `win-bridge` plugin, CNI `win-overlay` plugin | ## "args" in network config @@ -72,7 +73,7 @@ But the runtime would fill in the mappings so the plugin itself would receive so This method of passing information to a plugin is recommended when the information is optional and the plugin can choose to ignore it. It's often that case that such information is passed to all plugins by the runtime without regard for whether the plugin can understand it. -The conventions documented here are all namepaced under `cni` so they don't conflict with any existing `args`. +The conventions documented here are all namespaced under `cni` so they don't conflict with any existing `args`. For example: ```json @@ -94,17 +95,17 @@ For example: | Area | Purpose| Spec and Example | Runtime implementations | Plugin Implementations | | ----- | ------ | ------------ | ----------------------- | ---------------------- | | labels | Pass`key=value` labels to plugins |
"labels" : [
{ "key" : "app", "value" : "myapp" },
{ "key" : "env", "value" : "prod" }
]
| none | none | -| ips | Request static IPs |
"ips": ["10.2.2.42", "2001:db8::5"]
| none | host-local | +| ips | Request static IPs | Spec:
"ips": ["\[/\]", ...]
Examples:
"ips": ["10.2.2.42/24", "2001:db8::5"]
The plugin may require the IP address to include a prefix length. | none | host-local | ## CNI_ARGS CNI_ARGS formed part of the original CNI spec and have been present since the initial release. > `CNI_ARGS`: Extra arguments passed in by the user at invocation time. Alphanumeric key-value pairs separated by semicolons; for example, "FOO=BAR;ABC=123" -The use of `CNI_ARGS` is deprecated and "args" should be used instead. +The use of `CNI_ARGS` is deprecated and "args" should be used instead. If a runtime passes an equivalent key via `args` (eg the `ips` `args` Area and the `CNI_ARGS` `IP` Field) and the plugin understands `args`, the plugin must ignore the CNI_ARGS Field. | Field | Purpose| Spec and Example | Runtime implementations | Plugin Implementations | | ------ | ------ | ---------------- | ----------------------- | ---------------------- | -| IP | Request a specific IP from IPAM plugins | IP=192.168.10.4 | *rkt* supports passing additional arguments to plugins and the [documentation](https://coreos.com/rkt/docs/latest/networking/overriding-defaults.html) suggests IP can be used. | host-local (since version v0.2.0) supports the field for IPv4 only - [documentation](https://github.com/containernetworking/cni/blob/master/Documentation/host-local.md#supported-arguments).| +| IP | Request a specific IP from IPAM plugins | Spec:
IP=\[/\]
Example:
IP=192.168.10.4/24
The plugin may require the IP addresses to include a prefix length. | *rkt* supports passing additional arguments to plugins and the [documentation](https://coreos.com/rkt/docs/latest/networking/overriding-defaults.html) suggests IP can be used. | host-local (since version v0.2.0) supports the field for IPv4 only - [documentation](https://github.com/containernetworking/plugins/tree/master/plugins/ipam/host-local#supported-arguments).| ## Chained Plugins If plugins are agnostic about the type of interface created, they SHOULD work in a chained mode and configure existing interfaces. Plugins MAY also create the desired interface when not run in a chain. diff --git a/vendor/github.com/containernetworking/cni/Documentation/spec-upgrades.md b/vendor/github.com/containernetworking/cni/Documentation/spec-upgrades.md index c9efc67a2..9ff04d4bb 100644 --- a/vendor/github.com/containernetworking/cni/Documentation/spec-upgrades.md +++ b/vendor/github.com/containernetworking/cni/Documentation/spec-upgrades.md @@ -82,7 +82,7 @@ the plugin must return result JSON conforming to CNI spec version 0.2.0. ### Specific guidance for plugins written in Go Plugins written in Go may leverage the Go language packages in this repository to ease the process of upgrading and supporting multiple versions. CNI -[Library and Plugins Release v0.5.0](https://github.com/containernetworking/cni/releases) +[Library and Plugins Release v0.5.0](https://github.com/containernetworking/cni/releases/tag/v0.5.0) includes important changes to the Golang APIs. Plugins using these APIs will require some changes now, but should more-easily handle spec changes and new features going forward. @@ -149,7 +149,7 @@ result, err := current.NewResultFromResult(ipamResult) ``` Other examples of spec v0.3.0-compatible plugins are the -[main plugins in this repo](https://github.com/containernetworking/cni/tree/master/plugins/main) +[main plugins in this repo](https://github.com/containernetworking/plugins/tree/master/plugins) ## For Runtime Authors @@ -249,7 +249,7 @@ work with the fields exposed by that struct: ```go // runtime invokes the plugin to get the opaque types.Result // this may conform to any CNI spec version -resultInterface, err := libcni.AddNetwork(netConf, runtimeConf) +resultInterface, err := libcni.AddNetwork(ctx, netConf, runtimeConf) // upconvert result to the current 0.3.0 spec result, err := current.NewResultFromResult(resultInterface) diff --git a/vendor/github.com/containernetworking/cni/MAINTAINERS b/vendor/github.com/containernetworking/cni/MAINTAINERS index ca7962afd..66981ddaa 100644 --- a/vendor/github.com/containernetworking/cni/MAINTAINERS +++ b/vendor/github.com/containernetworking/cni/MAINTAINERS @@ -1,5 +1,5 @@ Bryan Boreham (@bboreham) -Casey Callendrello (@squeed) +Casey Callendrello (@squeed) Dan Williams (@dcbw) Gabe Rosenhouse (@rosenhouse) Matt Dupre (@matthewdupre) diff --git a/vendor/github.com/containernetworking/cni/README.md b/vendor/github.com/containernetworking/cni/README.md index 65ccda9f9..3968d908a 100644 --- a/vendor/github.com/containernetworking/cni/README.md +++ b/vendor/github.com/containernetworking/cni/README.md @@ -9,9 +9,9 @@ # 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 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). -The next meeting will be held on *Wednesday, October 4th* at *3: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=2017-10-04&sln=15-16). +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). --- @@ -38,11 +38,13 @@ To avoid duplication, we think it is prudent to define a common interface betwee ## Who is using CNI? ### Container runtimes - [rkt - container engine](https://coreos.com/blog/rkt-cni-networking.html) -- [Kubernetes - a system to simplify container operations](http://kubernetes.io/docs/admin/network-plugins/) +- [Kubernetes - a system to simplify container operations](https://kubernetes.io/docs/admin/network-plugins/) - [OpenShift - Kubernetes with additional enterprise features](https://github.com/openshift/origin/blob/master/docs/openshift_networking_requirements.md) - [Cloud Foundry - a platform for cloud applications](https://github.com/cloudfoundry-incubator/cf-networking-release) - [Apache Mesos - a distributed systems kernel](https://github.com/apache/mesos/blob/master/docs/cni.md) - [Amazon ECS - a highly scalable, high performance container management service](https://aws.amazon.com/ecs/) +- [Singularity - container platform optimized for HPC, EPC, and AI](https://github.com/sylabs/singularity) +- [OpenSVC - orchestrator for legacy and containerized application stacks](https://docs.opensvc.com/latest/fr/agent.configure.cni.html) ### 3rd party plugins - [Project Calico - a layer 3 virtual network](https://github.com/projectcalico/calico-cni) @@ -61,6 +63,10 @@ To avoid duplication, we think it is prudent to define a common interface betwee - [Amazon ECS CNI Plugins - a collection of CNI Plugins to configure containers with Amazon EC2 elastic network interfaces (ENIs)](https://github.com/aws/amazon-ecs-cni-plugins) - [Bonding CNI - a Link aggregating plugin to address failover and high availability network](https://github.com/Intel-Corp/bond-cni) - [ovn-kubernetes - an container network plugin built on Open vSwitch (OVS) and Open Virtual Networking (OVN) with support for both Linux and Windows](https://github.com/openvswitch/ovn-kubernetes) +- [Juniper Contrail](https://www.juniper.net/cloud) / [TungstenFabric](https://tungstenfabric.io) - Provides overlay SDN solution, delivering multicloud networking, hybrid cloud networking, simultaneous overlay-underlay support, network policy enforcement, network isolation, service chaining and flexible load balancing +- [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) The CNI team also maintains some [core plugins in a separate repository](https://github.com/containernetworking/plugins). @@ -74,7 +80,7 @@ If you intend to contribute to code or documentation, please read [CONTRIBUTING. ### Requirements -The CNI spec is language agnostic. To use the Go language libraries in this repository, you'll need a recent version of Go. Our [automated tests](https://travis-ci.org/containernetworking/cni/builds) cover Go versions 1.7 and 1.8. +The CNI spec is language agnostic. To use the Go language libraries in this repository, you'll need a recent version of Go. You can find the Go versions covered by our [automated tests](https://travis-ci.org/containernetworking/cni/builds) in [.travis.yaml](.travis.yml). ### Reference Plugins @@ -111,6 +117,7 @@ EOF $ cat >/etc/cni/net.d/99-loopback.conf < (optional) "domain": (optional) "search": (optional) @@ -170,7 +184,7 @@ Plugins must indicate success with a return code of zero and the following JSON } ``` -`cniVersion` specifies a [Semantic Version 2.0](http://semver.org) of CNI specification used by the plugin. A plugin may support multiple CNI spec versions (as it reports via the `VERSION` command), here the `cniVersion` returned by the plugin in the result must be consistent with the `cniVersion` specified in [Network Configuration](#network-configuration). If the `cniVersion` in the network configuration is not supported by the plugin, the plugin should return an error code 1 (see [Well-known Error Codes](#well-known-error-codes) for details). +`cniVersion` specifies a [Semantic Version 2.0](https://semver.org) of CNI specification used by the plugin. A plugin may support multiple CNI spec versions (as it reports via the `VERSION` command), here the `cniVersion` returned by the plugin in the result must be consistent with the `cniVersion` specified in [Network Configuration](#network-configuration). If the `cniVersion` in the network configuration is not supported by the plugin, the plugin should return an error code 1 (see [Well-known Error Codes](#well-known-error-codes) for details). `interfaces` describes specific network interfaces the plugin created. If the `CNI_IFNAME` variable exists the plugin must use that name for the sandbox/hypervisor interface or return an error if it cannot. @@ -183,6 +197,9 @@ If the `CNI_IFNAME` variable exists the plugin must use that name for the sandbo The `ips` field is a list of IP configuration information. See the [IP well-known structure](#ips) section for more information. +The `routes` field is a list of route configuration information. +See the [Routes well-known structure](#routes) section for more information. + The `dns` field contains a dictionary consisting of common DNS information. See the [DNS well-known structure](#dns) section for more information. @@ -199,7 +216,7 @@ Errors must be indicated by a non-zero return code and the following JSON being } ``` -`cniVersion` specifies a [Semantic Version 2.0](http://semver.org) of CNI specification used by the plugin. +`cniVersion` specifies a [Semantic Version 2.0](https://semver.org) of CNI specification used by the plugin. Error codes 0-99 are reserved for well-known errors (see [Well-known Error Codes](#well-known-error-codes) section). Values of 100+ can be freely used for plugin specific errors. @@ -208,18 +225,18 @@ In addition, stderr can be used for unstructured output such as logs. ### Network Configuration The network configuration is described in JSON form. The configuration may be stored on disk or generated from other sources by the container runtime. The following fields are well-known and have the following meaning: -- `cniVersion` (string): [Semantic Version 2.0](http://semver.org) of CNI specification to which this configuration conforms. +- `cniVersion` (string): [Semantic Version 2.0](https://semver.org) of CNI specification to which this configuration conforms. - `name` (string): Network name. This should be unique across all containers on the host (or other administrative domain). - `type` (string): Refers to the filename of the CNI plugin executable. -- `args` (dictionary): Optional additional arguments provided by the container runtime. For example a dictionary of labels could be passed to CNI plugins by adding them to a labels field under `args`. -- `ipMasq` (boolean): Optional (if supported by the plugin). Set up an IP masquerade on the host for this network. This is necessary if the host will act as a gateway to subnets that are not able to route to the IP assigned to the container. -- `ipam`: Dictionary with IPAM specific values: +- `args` (dictionary, optional): Additional arguments provided by the container runtime. For example a dictionary of labels could be passed to CNI plugins by adding them to a labels field under `args`. +- `ipMasq` (boolean, optional): If supported by the plugin, sets up an IP masquerade on the host for this network. This is necessary if the host will act as a gateway to subnets that are not able to route to the IP assigned to the container. +- `ipam` (dictionary, optional): Dictionary with IPAM specific values: - `type` (string): Refers to the filename of the IPAM plugin executable. -- `dns`: Dictionary with DNS specific values: - - `nameservers` (list of strings): list of a priority-ordered list of DNS nameservers that this network is aware of. Each entry in the list is a string containing either an IPv4 or an IPv6 address. - - `domain` (string): the local domain used for short hostname lookups. - - `search` (list of strings): list of priority ordered search domains for short hostname lookups. Will be preferred over `domain` by most resolvers. - - `options` (list of strings): list of options that can be passed to the resolver +- `dns` (dictionary, optional): Dictionary with DNS specific values: + - `nameservers` (list of strings, optional): list of a priority-ordered list of DNS nameservers that this network is aware of. Each entry in the list is a string containing either an IPv4 or an IPv6 address. + - `domain` (string, optional): the local domain used for short hostname lookups. + - `search` (list of strings, optional): list of priority ordered search domains for short hostname lookups. Will be preferred over `domain` by most resolvers. + - `options` (list of strings, optional): list of options that can be passed to the resolver Plugins may define additional fields that they accept and may generate an error if called with unknown fields. The exception to this is the `args` field may be used to pass arbitrary data which should be ignored by plugins if not understood. @@ -255,7 +272,7 @@ Plugins may define additional fields that they accept and may generate an error "ipam": { "type": "dhcp", "routes": [ { "dst": "10.3.0.0/16" }, { "dst": "10.4.0.0/16" } ] - } + }, // args may be ignored by plugins "args": { "labels" : { @@ -287,31 +304,30 @@ Network configuration lists provide a mechanism to run multiple CNI plugins for The list is composed of well-known fields and list of one or more standard CNI network configurations (see above). The list is described in JSON form, and can be stored on disk or generated from other sources by the container runtime. The following fields are well-known and have the following meaning: -- `cniVersion` (string): [Semantic Version 2.0](http://semver.org) of CNI specification to which this configuration list and all the individual configurations conform. +- `cniVersion` (string): [Semantic Version 2.0](https://semver.org) of CNI specification to which this configuration list and all the individual configurations conform. - `name` (string): Network name. This should be unique across all containers on the host (or other administrative domain). +- `disableCheck` (string): Either `true` or `false`. If `disableCheck` is `true`, runtimes must not call `CHECK` for this network configuration list. This allows an administrator to prevent `CHECK`ing where a combination of plugins is known to return spurious errors. - `plugins` (list): A list of standard CNI network configuration dictionaries (see above). When executing a plugin list, the runtime MUST replace the `name` and `cniVersion` fields in each individual network configuration in the list with the `name` and `cniVersion` field of the list itself. This ensures that the name and CNI version is the same for all plugin executions in the list, preventing versioning conflicts between plugins. The runtime may also pass capability-based keys as a map in the top-level `runtimeConfig` key of the plugin's config JSON if a plugin advertises it supports a specific capability via the `capabilities` key of its network configuration. The key passed in `runtimeConfig` MUST match the name of the specific capability from the `capabilities` key of the plugins network configuration. See CONVENTIONS.md for more information on capabilities and how they are sent to plugins via the `runtimeConfig` key. For the `ADD` action, the runtime MUST also add a `prevResult` field to the configuration JSON of any plugin after the first one, which MUST be the `Result` of the previous plugin (if any) in JSON format ([see below](#network-configuration-list-runtime-examples)). -For the `GET` and `DEL` actions, the runtime MUST (if available) add a `prevResult` field to the configuration JSON of each plugin, which MUST be the `Result` of the immediately previous `ADD` or `GET` action in JSON format ([see below](#network-configuration-list-runtime-examples)). -For the `ADD` and `GET` actions, plugins SHOULD echo the contents of the `prevResult` field to their stdout to allow subsequent plugins (and the runtime) to receive the result, unless they wish to modify or suppress a previous result. +For the `CHECK` and `DEL` actions, the runtime MUST (except that it may be omitted for `DEL` if not available) add a `prevResult` field to the configuration JSON of each plugin, which MUST be the `Result` of the immediately previous `ADD` action in JSON format ([see below](#network-configuration-list-runtime-examples)). +For the `ADD` action, plugins SHOULD echo the contents of the `prevResult` field to their stdout to allow subsequent plugins (and the runtime) to receive the result, unless they wish to modify or suppress a previous result. Plugins are allowed to modify or suppress all or part of a `prevResult`. However, plugins that support a version of the CNI specification that includes the `prevResult` field MUST handle `prevResult` by either passing it through, modifying it, or suppressing it explicitly. It is a violation of this specification to be unaware of the `prevResult` field. The runtime MUST also execute each plugin in the list with the same environment. -For the DEL action, the runtime MUST execute the plugins in reverse-order. +For the `DEL` action, the runtime MUST execute the plugins in reverse-order. #### Network Configuration List Error Handling -When an error occurs while executing an action on a plugin list (eg, either ADD or DEL) the runtime MUST stop execution of the list. +When an error occurs while executing an action on a plugin list (eg, either `ADD` or `DEL`) the runtime MUST stop execution of the list. -If an ADD action fails, when the runtime decides to handle the failure it should execute the DEL action (in reverse order from the ADD as specified above) for all plugins in the list, even if some were not called during the ADD action. - -Plugins should generally complete a DEL action without error even if some resources are missing. For example, an IPAM plugin should generally release an IP allocation and return success even if the container network namespace no longer exists, unless that network namespace is critical for IPAM management. While DHCP may usually send a 'release' message on the container network interface, since DHCP leases have a lifetime this release action would not be considered critical and no error should be returned. For another example, the `bridge` plugin should delegate the DEL action to the IPAM plugin and clean up its own resources (if present) even if the container network namespace and/or container network interface no longer exist. +If an `ADD` action fails, when the runtime decides to handle the failure it should execute the `DEL` action (in reverse order from the `ADD` as specified above) for all plugins in the list, even if some were not called during the `ADD` action. #### Example network configuration lists @@ -420,7 +436,7 @@ Note that the runtime adds the `cniVersion` and `name` fields from configuration } ``` -Given the same network configuration JSON list, the container runtime would perform the following steps for the `GET` action. +Given the same network configuration JSON list, the container runtime would perform the following steps for the `CHECK` action. 1) first call the `bridge` plugin with the following JSON, including the `prevResult` field containing the JSON response from the `ADD` operation: @@ -474,7 +490,7 @@ Given the same network configuration JSON list, the container runtime would perf } ``` -2) next call the `tuning` plugin with the following JSON, including the `prevResult` field containing the JSON response from the `bridge` plugin: +2) next call the `tuning` plugin with the following JSON, including the `prevResult` field containing the JSON response from the `ADD` operation: ```json { @@ -514,10 +530,10 @@ Given the same network configuration JSON list, the container runtime would perf } ``` -Given the same network configuration JSON list, the container runtime would perform the following steps for the DEL action. -Note that plugins are executed in reverse order from the `ADD` and `GET` actions. +Given the same network configuration JSON list, the container runtime would perform the following steps for the `DEL` action. +Note that plugins are executed in reverse order from the `ADD` and `CHECK` actions. -1) first call the `tuning` plugin with the following JSON, including the `prevResult` field containing the JSON response from the `GET` action: +1) first call the `tuning` plugin with the following JSON, including the `prevResult` field containing the JSON response from the `ADD` action: ```json { @@ -557,7 +573,7 @@ Note that plugins are executed in reverse order from the `ADD` and `GET` actions } ``` -2) next call the `bridge` plugin with the following JSON, including the `prevResult` field containing the JSON response from the `GET` action: +2) next call the `bridge` plugin with the following JSON, including the `prevResult` field containing the JSON response from the `ADD` action: ```json { @@ -639,7 +655,7 @@ Success must be indicated by a zero return code and the following JSON being pri }, ... ] - "dns": { + "dns": { (optional) "nameservers": (optional) "domain": (optional) "search": (optional) @@ -650,11 +666,14 @@ Success must be indicated by a zero return code and the following JSON being pri Note that unlike regular CNI plugins, IPAM plugins should return an abbreviated `Result` structure that does not include the `interfaces` key, since IPAM plugins should be unaware of interfaces configured by their parent plugin except those specifically required for IPAM (eg, like the `dhcp` IPAM plugin). -`cniVersion` specifies a [Semantic Version 2.0](http://semver.org) of CNI specification used by the IPAM plugin. An IPAM plugin may support multiple CNI spec versions (as it reports via the `VERSION` command), here the `cniVersion` returned by the IPAM plugin in the result must be consistent with the `cniVersion` specified in [Network Configuration](#network-configuration). If the `cniVersion` in the network configuration is not supported by the IPAM plugin, the plugin should return an error code 1 (see [Well-known Error Codes](#well-known-error-codes) for details). +`cniVersion` specifies a [Semantic Version 2.0](https://semver.org) of CNI specification used by the IPAM plugin. An IPAM plugin may support multiple CNI spec versions (as it reports via the `VERSION` command), here the `cniVersion` returned by the IPAM plugin in the result must be consistent with the `cniVersion` specified in [Network Configuration](#network-configuration). If the `cniVersion` in the network configuration is not supported by the IPAM plugin, the plugin should return an error code 1 (see [Well-known Error Codes](#well-known-error-codes) for details). The `ips` field is a list of IP configuration information. See the [IP well-known structure](#ips) section for more information. +The `routes` field is a list of route configuration information. +See the [Routes well-known structure](#routes) section for more information. + The `dns` field contains a dictionary consisting of common DNS information. See the [DNS well-known structure](#dns) section for more information. @@ -708,10 +727,12 @@ All properties known to the plugin should be provided, even if not strictly requ ] ``` -- Each `routes` entry is a dictionary with the following fields. All IP addresses in the `routes` entry must be the same IP version, either 4 or 6. +Each `routes` entry is a dictionary with the following fields. All IP addresses in the `routes` entry must be the same IP version, either 4 or 6. - `dst` (string): destination subnet specified in CIDR notation. - `gw` (string): IP of the gateway. If omitted, a default gateway is assumed (as determined by the CNI plugin). +Each `routes` entry must be relevant for the sandbox interface specified by CNI_IFNAME. + #### DNS ``` @@ -737,3 +758,4 @@ Error codes 1-99 must not be used other than as specified here. - `1` - Incompatible CNI version - `2` - Unsupported field in network configuration. The error message must contain the key and value of the unsupported field. - `3` - Container unknown or does not exist. This error implies the runtime does not need to perform any container network cleanup (for example, calling the `DEL` action on the container). +- `11` - Try again later. If the plugin detects some transient condition that should clear up, it can use this code to notify the runtime it should re-try the operation later. diff --git a/vendor/github.com/containernetworking/cni/Vagrantfile b/vendor/github.com/containernetworking/cni/Vagrantfile index 210e4567e..0287f9715 100644 --- a/vendor/github.com/containernetworking/cni/Vagrantfile +++ b/vendor/github.com/containernetworking/cni/Vagrantfile @@ -12,7 +12,7 @@ Vagrant.configure(2) do |config| apt-get update -y || (sleep 40 && apt-get update -y) apt-get install -y git - wget -qO- https://storage.googleapis.com/golang/go1.10.linux-amd64.tar.gz | tar -C /usr/local -xz + wget -qO- https://storage.googleapis.com/golang/go1.11.1.linux-amd64.tar.gz | tar -C /usr/local -xz echo 'export GOPATH=/go; export PATH=/usr/local/go/bin:$GOPATH/bin:$PATH' >> /root/.bashrc eval `tail -n1 /root/.bashrc` diff --git a/vendor/github.com/containernetworking/cni/cnitool/README.md b/vendor/github.com/containernetworking/cni/cnitool/README.md index 0b1956054..5d593fe6a 100644 --- a/vendor/github.com/containernetworking/cni/cnitool/README.md +++ b/vendor/github.com/containernetworking/cni/cnitool/README.md @@ -14,7 +14,9 @@ Then, check out and build the plugins. All commands should be run from this dire ``` git clone https://github.com/containernetworking/plugins.git cd plugins -./build.sh +./build_linux.sh +# or +./build_windows.sh ``` Create a network configuration diff --git a/vendor/github.com/containernetworking/cni/cnitool/cnitool.go b/vendor/github.com/containernetworking/cni/cnitool/cnitool.go index f899e0257..e2c4e902e 100644 --- a/vendor/github.com/containernetworking/cni/cnitool/cnitool.go +++ b/vendor/github.com/containernetworking/cni/cnitool/cnitool.go @@ -15,6 +15,7 @@ package main import ( + "context" "crypto/sha512" "encoding/json" "fmt" @@ -30,11 +31,13 @@ const ( EnvNetDir = "NETCONFPATH" EnvCapabilityArgs = "CAP_ARGS" EnvCNIArgs = "CNI_ARGS" + EnvCNIIfname = "CNI_IFNAME" DefaultNetDir = "/etc/cni/net.d" - CmdAdd = "add" - CmdDel = "del" + CmdAdd = "add" + CmdCheck = "check" + CmdDel = "del" ) func parseArgs(args string) ([][2]string, error) { @@ -54,7 +57,7 @@ func parseArgs(args string) ([][2]string, error) { } func main() { - if len(os.Args) < 3 { + if len(os.Args) < 4 { usage() return } @@ -85,6 +88,11 @@ func main() { } } + ifName, ok := os.LookupEnv(EnvCNIIfname) + if !ok { + ifName = "eth0" + } + netns := os.Args[3] netns, err = filepath.Abs(netns) if err != nil { @@ -100,29 +108,33 @@ func main() { rt := &libcni.RuntimeConf{ ContainerID: containerID, NetNS: netns, - IfName: "eth0", + IfName: ifName, Args: cniArgs, CapabilityArgs: capabilityArgs, } switch os.Args[1] { case CmdAdd: - result, err := cninet.AddNetworkList(netconf, rt) + result, err := cninet.AddNetworkList(context.TODO(), netconf, rt) if result != nil { _ = result.Print() } exit(err) + case CmdCheck: + err := cninet.CheckNetworkList(context.TODO(), netconf, rt) + exit(err) case CmdDel: - exit(cninet.DelNetworkList(netconf, rt)) + exit(cninet.DelNetworkList(context.TODO(), netconf, rt)) } } func usage() { exe := filepath.Base(os.Args[0]) - fmt.Fprintf(os.Stderr, "%s: Add or remove network interfaces from a network namespace\n", exe) - fmt.Fprintf(os.Stderr, " %s %s \n", exe, CmdAdd) - fmt.Fprintf(os.Stderr, " %s %s \n", exe, CmdDel) + fmt.Fprintf(os.Stderr, "%s: Add, check, or remove network interfaces from a network namespace\n", exe) + fmt.Fprintf(os.Stderr, " %s add \n", exe) + fmt.Fprintf(os.Stderr, " %s check \n", exe) + fmt.Fprintf(os.Stderr, " %s del \n", exe) os.Exit(1) } diff --git a/vendor/github.com/containernetworking/cni/libcni/api.go b/vendor/github.com/containernetworking/cni/libcni/api.go index d494e43d4..360733e74 100644 --- a/vendor/github.com/containernetworking/cni/libcni/api.go +++ b/vendor/github.com/containernetworking/cni/libcni/api.go @@ -15,6 +15,7 @@ package libcni import ( + "context" "encoding/json" "fmt" "io/ioutil" @@ -57,20 +58,25 @@ type NetworkConfig struct { } type NetworkConfigList struct { - Name string - CNIVersion string - Plugins []*NetworkConfig - Bytes []byte + Name string + CNIVersion string + DisableCheck bool + Plugins []*NetworkConfig + Bytes []byte } type CNI interface { - AddNetworkList(net *NetworkConfigList, rt *RuntimeConf) (types.Result, error) - GetNetworkList(net *NetworkConfigList, rt *RuntimeConf) (types.Result, error) - DelNetworkList(net *NetworkConfigList, rt *RuntimeConf) error + 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 - AddNetwork(net *NetworkConfig, rt *RuntimeConf) (types.Result, error) - GetNetwork(net *NetworkConfig, rt *RuntimeConf) (types.Result, error) - DelNetwork(net *NetworkConfig, rt *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) + + ValidateNetworkList(ctx context.Context, net *NetworkConfigList) ([]string, error) + ValidateNetwork(ctx context.Context, net *NetworkConfig) ([]string, error) } type CNIConfig struct { @@ -120,7 +126,7 @@ func buildOneConfig(name, cniVersion string, orig *NetworkConfig, prevResult typ // These capabilities arguments are filtered through the plugin's advertised // capabilities from its config JSON, and any keys in the CapabilityArgs // matching plugin capabilities are added to the "runtimeConfig" dictionary -// sent to the plugin via JSON on stdin. For exmaple, if the plugin's +// sent to the plugin via JSON on stdin. For example, if the plugin's // capabilities include "portMappings", and the CapabilityArgs map includes a // "portMappings" key, that key and its value are added to the "runtimeConfig" // dictionary to be passed to the plugin's stdin. @@ -158,40 +164,12 @@ func (c *CNIConfig) ensureExec() invoke.Exec { return c.exec } -func (c *CNIConfig) addOrGetNetwork(command, name, cniVersion string, net *NetworkConfig, prevResult types.Result, rt *RuntimeConf) (types.Result, error) { - c.ensureExec() - pluginPath, err := c.exec.FindInPath(net.Network.Type, c.Path) - if err != nil { - return nil, err - } - - newConf, err := buildOneConfig(name, cniVersion, net, prevResult, rt) - if err != nil { - return nil, err - } - - return invoke.ExecPluginWithResult(pluginPath, newConf.Bytes, c.args(command, rt), c.exec) -} - -// Note that only GET requests should pass an initial prevResult -func (c *CNIConfig) addOrGetNetworkList(command string, prevResult types.Result, list *NetworkConfigList, rt *RuntimeConf) (types.Result, error) { - var err error - for _, net := range list.Plugins { - prevResult, err = c.addOrGetNetwork(command, list.Name, list.CNIVersion, net, prevResult, rt) - if err != nil { - return nil, err - } - } - - return prevResult, nil -} - func getResultCacheFilePath(netName string, rt *RuntimeConf) string { cacheDir := rt.CacheDir if cacheDir == "" { cacheDir = CacheDir } - return filepath.Join(cacheDir, "results", fmt.Sprintf("%s-%s", netName, rt.ContainerID)) + return filepath.Join(cacheDir, "results", fmt.Sprintf("%s-%s-%s", netName, rt.ContainerID, rt.IfName)) } func setCachedResult(result types.Result, netName string, rt *RuntimeConf) error { @@ -243,37 +221,94 @@ func getCachedResult(netName, cniVersion string, rt *RuntimeConf) (types.Result, return result, err } -// AddNetworkList executes a sequence of plugins with the ADD command -func (c *CNIConfig) AddNetworkList(list *NetworkConfigList, rt *RuntimeConf) (types.Result, error) { - result, err := c.addOrGetNetworkList("ADD", nil, list, rt) +// GetNetworkListCachedResult returns the cached Result of the previous +// previous 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) +} + +// GetNetworkCachedResult returns the cached Result of the previous +// previous 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) +} + +func (c *CNIConfig) addNetwork(ctx context.Context, name, cniVersion string, net *NetworkConfig, prevResult types.Result, rt *RuntimeConf) (types.Result, error) { + c.ensureExec() + pluginPath, err := c.exec.FindInPath(net.Network.Type, c.Path) if err != nil { return nil, err } + newConf, err := buildOneConfig(name, cniVersion, net, prevResult, rt) + if err != nil { + return nil, err + } + + return invoke.ExecPluginWithResult(ctx, pluginPath, newConf.Bytes, c.args("ADD", rt), c.exec) +} + +// AddNetworkList executes a sequence of plugins with the ADD command +func (c *CNIConfig) AddNetworkList(ctx context.Context, list *NetworkConfigList, rt *RuntimeConf) (types.Result, error) { + var err error + var result types.Result + for _, net := range list.Plugins { + result, err = c.addNetwork(ctx, list.Name, list.CNIVersion, net, result, rt) + if err != nil { + return nil, err + } + } + if err = setCachedResult(result, list.Name, rt); err != nil { - return nil, fmt.Errorf("failed to set network '%s' cached result: %v", list.Name, err) + return nil, fmt.Errorf("failed to set network %q cached result: %v", list.Name, err) } return result, nil } -// GetNetworkList executes a sequence of plugins with the GET command -func (c *CNIConfig) GetNetworkList(list *NetworkConfigList, rt *RuntimeConf) (types.Result, error) { - // GET was added in CNI spec version 0.4.0 and higher +func (c *CNIConfig) checkNetwork(ctx context.Context, name, cniVersion string, net *NetworkConfig, prevResult types.Result, rt *RuntimeConf) error { + c.ensureExec() + pluginPath, err := c.exec.FindInPath(net.Network.Type, c.Path) + if err != nil { + return err + } + + newConf, err := buildOneConfig(name, cniVersion, net, prevResult, rt) + if err != nil { + return err + } + + return invoke.ExecPluginWithoutResult(ctx, pluginPath, newConf.Bytes, c.args("CHECK", rt), c.exec) +} + +// CheckNetworkList executes a sequence of plugins with the CHECK command +func (c *CNIConfig) CheckNetworkList(ctx context.Context, list *NetworkConfigList, rt *RuntimeConf) error { + // CHECK was added in CNI spec version 0.4.0 and higher if gtet, err := version.GreaterThanOrEqualTo(list.CNIVersion, "0.4.0"); err != nil { - return nil, err + return err } else if !gtet { - return nil, fmt.Errorf("configuration version %q does not support the GET command", list.CNIVersion) + return fmt.Errorf("configuration version %q does not support the CHECK command", list.CNIVersion) + } + + if list.DisableCheck { + return nil } cachedResult, err := getCachedResult(list.Name, list.CNIVersion, rt) if err != nil { - return nil, fmt.Errorf("failed to get network '%s' cached result: %v", list.Name, err) + return fmt.Errorf("failed to get network %q cached result: %v", list.Name, err) + } + + for _, net := range list.Plugins { + if err := c.checkNetwork(ctx, list.Name, list.CNIVersion, net, cachedResult, rt); err != nil { + return err + } } - return c.addOrGetNetworkList("GET", cachedResult, list, rt) + + return nil } -func (c *CNIConfig) delNetwork(name, cniVersion string, net *NetworkConfig, prevResult types.Result, rt *RuntimeConf) error { +func (c *CNIConfig) delNetwork(ctx context.Context, name, cniVersion string, net *NetworkConfig, prevResult types.Result, rt *RuntimeConf) error { c.ensureExec() pluginPath, err := c.exec.FindInPath(net.Network.Type, c.Path) if err != nil { @@ -285,11 +320,11 @@ func (c *CNIConfig) delNetwork(name, cniVersion string, net *NetworkConfig, prev return err } - return invoke.ExecPluginWithoutResult(pluginPath, newConf.Bytes, c.args("DEL", rt), c.exec) + return invoke.ExecPluginWithoutResult(ctx, pluginPath, newConf.Bytes, c.args("DEL", rt), c.exec) } // DelNetworkList executes a sequence of plugins with the DEL command -func (c *CNIConfig) DelNetworkList(list *NetworkConfigList, rt *RuntimeConf) error { +func (c *CNIConfig) DelNetworkList(ctx context.Context, list *NetworkConfigList, rt *RuntimeConf) error { var cachedResult types.Result // Cached result on DEL was added in CNI spec version 0.4.0 and higher @@ -298,13 +333,13 @@ func (c *CNIConfig) DelNetworkList(list *NetworkConfigList, rt *RuntimeConf) err } else if gtet { cachedResult, err = getCachedResult(list.Name, list.CNIVersion, rt) if err != nil { - return fmt.Errorf("failed to get network '%s' cached result: %v", list.Name, err) + return fmt.Errorf("failed to get network %q cached result: %v", list.Name, err) } } for i := len(list.Plugins) - 1; i >= 0; i-- { net := list.Plugins[i] - if err := c.delNetwork(list.Name, list.CNIVersion, net, cachedResult, rt); err != nil { + if err := c.delNetwork(ctx, list.Name, list.CNIVersion, net, cachedResult, rt); err != nil { return err } } @@ -314,37 +349,37 @@ func (c *CNIConfig) DelNetworkList(list *NetworkConfigList, rt *RuntimeConf) err } // AddNetwork executes the plugin with the ADD command -func (c *CNIConfig) AddNetwork(net *NetworkConfig, rt *RuntimeConf) (types.Result, error) { - result, err := c.addOrGetNetwork("ADD", net.Network.Name, net.Network.CNIVersion, net, nil, rt) +func (c *CNIConfig) AddNetwork(ctx context.Context, net *NetworkConfig, rt *RuntimeConf) (types.Result, error) { + result, err := c.addNetwork(ctx, net.Network.Name, net.Network.CNIVersion, net, nil, rt) if err != nil { return nil, err } if err = setCachedResult(result, net.Network.Name, rt); err != nil { - return nil, fmt.Errorf("failed to set network '%s' cached result: %v", net.Network.Name, err) + return nil, fmt.Errorf("failed to set network %q cached result: %v", net.Network.Name, err) } return result, nil } -// GetNetwork executes the plugin with the GET command -func (c *CNIConfig) GetNetwork(net *NetworkConfig, rt *RuntimeConf) (types.Result, error) { - // GET was added in CNI spec version 0.4.0 and higher +// CheckNetwork executes the plugin with the CHECK command +func (c *CNIConfig) CheckNetwork(ctx context.Context, net *NetworkConfig, rt *RuntimeConf) error { + // CHECK was added in CNI spec version 0.4.0 and higher if gtet, err := version.GreaterThanOrEqualTo(net.Network.CNIVersion, "0.4.0"); err != nil { - return nil, err + return err } else if !gtet { - return nil, fmt.Errorf("configuration version %q does not support the GET command", net.Network.CNIVersion) + 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) if err != nil { - return nil, fmt.Errorf("failed to get network '%s' cached result: %v", net.Network.Name, err) + return fmt.Errorf("failed to get network %q cached result: %v", net.Network.Name, err) } - return c.addOrGetNetwork("GET", net.Network.Name, net.Network.CNIVersion, net, cachedResult, rt) + return c.checkNetwork(ctx, net.Network.Name, net.Network.CNIVersion, net, cachedResult, rt) } // DelNetwork executes the plugin with the DEL command -func (c *CNIConfig) DelNetwork(net *NetworkConfig, rt *RuntimeConf) error { +func (c *CNIConfig) DelNetwork(ctx context.Context, net *NetworkConfig, rt *RuntimeConf) error { var cachedResult types.Result // Cached result on DEL was added in CNI spec version 0.4.0 and higher @@ -353,27 +388,99 @@ func (c *CNIConfig) DelNetwork(net *NetworkConfig, rt *RuntimeConf) error { } else if gtet { cachedResult, err = getCachedResult(net.Network.Name, net.Network.CNIVersion, rt) if err != nil { - return fmt.Errorf("failed to get network '%s' cached result: %v", net.Network.Name, err) + return fmt.Errorf("failed to get network %q cached result: %v", net.Network.Name, err) } } - if err := c.delNetwork(net.Network.Name, net.Network.CNIVersion, net, cachedResult, rt); err != nil { + if err := c.delNetwork(ctx, net.Network.Name, net.Network.CNIVersion, net, cachedResult, rt); err != nil { return err } _ = delCachedResult(net.Network.Name, rt) return nil } +// ValidateNetworkList checks that a configuration is reasonably valid. +// - all the specified plugins exist on disk +// - every plugin supports the desired version. +// +// Returns a list of all capabilities supported by the configuration, or error +func (c *CNIConfig) ValidateNetworkList(ctx context.Context, list *NetworkConfigList) ([]string, error) { + version := list.CNIVersion + + // holding map for seen caps (in case of duplicates) + caps := map[string]interface{}{} + + errs := []error{} + for _, net := range list.Plugins { + if err := c.validatePlugin(ctx, net.Network.Type, version); err != nil { + errs = append(errs, err) + } + for c, enabled := range net.Network.Capabilities { + if !enabled { + continue + } + caps[c] = struct{}{} + } + } + + if len(errs) > 0 { + return nil, fmt.Errorf("%v", errs) + } + + // make caps list + cc := make([]string, 0, len(caps)) + for c := range caps { + cc = append(cc, c) + } + + return cc, nil +} + +// ValidateNetwork checks that a configuration is reasonably valid. +// It uses the same logic as ValidateNetworkList) +// Returns a list of capabilities +func (c *CNIConfig) ValidateNetwork(ctx context.Context, net *NetworkConfig) ([]string, error) { + caps := []string{} + for c, ok := range net.Network.Capabilities { + if ok { + caps = append(caps, c) + } + } + if err := c.validatePlugin(ctx, net.Network.Type, net.Network.CNIVersion); err != nil { + return nil, err + } + return caps, nil +} + +// 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) + if err != nil { + return err + } + + vi, err := invoke.GetVersionInfo(ctx, pluginPath, c.exec) + if err != nil { + return err + } + for _, vers := range vi.SupportedVersions() { + if vers == expectedVersion { + return nil + } + } + return fmt.Errorf("plugin %s does not support config version %q", pluginName, expectedVersion) +} + // GetVersionInfo reports which versions of the CNI spec are supported by // the given plugin. -func (c *CNIConfig) GetVersionInfo(pluginType string) (version.PluginInfo, error) { +func (c *CNIConfig) GetVersionInfo(ctx context.Context, pluginType string) (version.PluginInfo, error) { c.ensureExec() pluginPath, err := c.exec.FindInPath(pluginType, c.Path) if err != nil { return nil, err } - return invoke.GetVersionInfo(pluginPath, c.exec) + return invoke.GetVersionInfo(ctx, pluginPath, c.exec) } // ===== diff --git a/vendor/github.com/containernetworking/cni/libcni/api_test.go b/vendor/github.com/containernetworking/cni/libcni/api_test.go index 483720ed4..42ec3ba7a 100644 --- a/vendor/github.com/containernetworking/cni/libcni/api_test.go +++ b/vendor/github.com/containernetworking/cni/libcni/api_test.go @@ -15,12 +15,15 @@ package libcni_test import ( + "context" "encoding/json" "fmt" "io/ioutil" "net" "os" "path/filepath" + "strings" + "time" "github.com/containernetworking/cni/libcni" "github.com/containernetworking/cni/pkg/skel" @@ -116,8 +119,8 @@ func newPluginInfo(configValue, prevResult string, injectDebugFilePath bool, res } } -func resultCacheFilePath(cacheDirPath, netName, containerID string) string { - return filepath.Join(cacheDirPath, "results", netName+"-"+containerID) +func resultCacheFilePath(cacheDirPath, netName string, rt *libcni.RuntimeConf) string { + return filepath.Join(cacheDirPath, "results", netName+"-"+rt.ContainerID+"-"+rt.IfName) } var _ = Describe("Invoking plugins", func() { @@ -141,6 +144,7 @@ var _ = Describe("Invoking plugins", func() { cniConfig *libcni.CNIConfig runtimeConfig *libcni.RuntimeConf netConfig *libcni.NetworkConfig + ctx context.Context ) BeforeEach(func() { @@ -182,6 +186,7 @@ var _ = Describe("Invoking plugins", func() { }, CacheDir: cacheDirPath, } + ctx = context.TODO() }) AfterEach(func() { @@ -189,7 +194,7 @@ var _ = Describe("Invoking plugins", func() { }) It("adds correct runtime config for capabilities to stdin", func() { - _, err := cniConfig.AddNetwork(netConfig, runtimeConfig) + _, err := cniConfig.AddNetwork(ctx, netConfig, runtimeConfig) Expect(err).NotTo(HaveOccurred()) debug, err = noop_debug.ReadDebug(debugFilePath) @@ -222,7 +227,7 @@ var _ = Describe("Invoking plugins", func() { "somethingElse22": []string{"foobar", "baz"}, } - _, err := cniConfig.AddNetwork(netConfig, runtimeConfig) + _, err := cniConfig.AddNetwork(ctx, netConfig, runtimeConfig) Expect(err).NotTo(HaveOccurred()) debug, err = noop_debug.ReadDebug(debugFilePath) @@ -238,6 +243,13 @@ var _ = Describe("Invoking plugins", func() { _, ok := conf["runtimeConfig"] Expect(ok).Should(BeFalse()) }) + + It("outputs correct capabilities for validate", func() { + caps, err := cniConfig.ValidateNetwork(ctx, netConfig) + Expect(err).NotTo(HaveOccurred()) + Expect(caps).To(ConsistOf("portMappings", "somethingElse")) + }) + }) Describe("Invoking a single plugin", func() { @@ -249,6 +261,7 @@ var _ = Describe("Invoking plugins", func() { cniConfig *libcni.CNIConfig netConfig *libcni.NetworkConfig runtimeConfig *libcni.RuntimeConf + ctx context.Context expectedCmdArgs skel.CmdArgs ) @@ -312,6 +325,7 @@ var _ = Describe("Invoking plugins", func() { Path: cniBinPath, StdinData: newBytes, } + ctx = context.TODO() }) AfterEach(func() { @@ -320,7 +334,7 @@ var _ = Describe("Invoking plugins", func() { Describe("AddNetwork", func() { It("executes the plugin with command ADD", func() { - r, err := cniConfig.AddNetwork(netConfig, runtimeConfig) + r, err := cniConfig.AddNetwork(ctx, netConfig, runtimeConfig) Expect(err).NotTo(HaveOccurred()) result, err := current.GetResult(r) @@ -346,14 +360,16 @@ var _ = Describe("Invoking plugins", func() { Expect(string(debug.CmdArgs.StdinData)).To(ContainSubstring("\"portMappings\":")) // Ensure the cached result matches the returned one - cacheFile := resultCacheFilePath(cacheDirPath, netConfig.Network.Name, runtimeConfig.ContainerID) - _, err = os.Stat(cacheFile) + cachedResult, err := cniConfig.GetNetworkCachedResult(netConfig, runtimeConfig) Expect(err).NotTo(HaveOccurred()) - cachedData, err := ioutil.ReadFile(cacheFile) + result2, err := current.GetResult(cachedResult) Expect(err).NotTo(HaveOccurred()) - returnedData, err := json.Marshal(result) + cachedJson, err := json.Marshal(result2) Expect(err).NotTo(HaveOccurred()) - Expect(cachedData).To(MatchJSON(returnedData)) + + returnedJson, err := json.Marshal(result) + Expect(err).NotTo(HaveOccurred()) + Expect(cachedJson).To(MatchJSON(returnedJson)) }) Context("when finding the plugin fails", func() { @@ -362,7 +378,7 @@ var _ = Describe("Invoking plugins", func() { }) It("returns the error", func() { - _, err := cniConfig.AddNetwork(netConfig, runtimeConfig) + _, err := cniConfig.AddNetwork(ctx, netConfig, runtimeConfig) Expect(err).To(MatchError(ContainSubstring(`failed to find plugin "does-not-exist"`))) }) }) @@ -373,7 +389,7 @@ var _ = Describe("Invoking plugins", func() { Expect(debug.WriteDebug(debugFilePath)).To(Succeed()) }) It("unmarshals and returns the error", func() { - result, err := cniConfig.AddNetwork(netConfig, runtimeConfig) + result, err := cniConfig.AddNetwork(ctx, netConfig, runtimeConfig) Expect(result).To(BeNil()) Expect(err).To(MatchError("plugin error: banana")) }) @@ -381,22 +397,22 @@ var _ = Describe("Invoking plugins", func() { Context("when the result cache directory cannot be accessed", func() { It("returns an error", func() { - // Make the results directory inaccessble by making it a + // Make the results directory inaccessible by making it a // file instead of a directory tmpPath := filepath.Join(cacheDirPath, "results") err := ioutil.WriteFile(tmpPath, []byte("afdsasdfasdf"), 0600) Expect(err).NotTo(HaveOccurred()) - result, err := cniConfig.AddNetwork(netConfig, runtimeConfig) + result, err := cniConfig.AddNetwork(ctx, netConfig, runtimeConfig) Expect(result).To(BeNil()) Expect(err).To(HaveOccurred()) }) }) }) - Describe("GetNetwork", func() { - It("executes the plugin with command GET", func() { - cacheFile := resultCacheFilePath(cacheDirPath, netConfig.Network.Name, runtimeConfig.ContainerID) + Describe("CheckNetwork", func() { + It("executes the plugin with command CHECK", func() { + cacheFile := resultCacheFilePath(cacheDirPath, netConfig.Network.Name, runtimeConfig) err := os.MkdirAll(filepath.Dir(cacheFile), 0700) Expect(err).NotTo(HaveOccurred()) cachedJson := `{ @@ -407,28 +423,12 @@ var _ = Describe("Invoking plugins", func() { err = ioutil.WriteFile(cacheFile, []byte(cachedJson), 0600) Expect(err).NotTo(HaveOccurred()) - r, err := cniConfig.GetNetwork(netConfig, runtimeConfig) - Expect(err).NotTo(HaveOccurred()) - - result, err := current.GetResult(r) + err = cniConfig.CheckNetwork(ctx, netConfig, runtimeConfig) Expect(err).NotTo(HaveOccurred()) - Expect(result).To(Equal(¤t.Result{ - CNIVersion: current.ImplementedSpecVersion, - IPs: []*current.IPConfig{ - { - Version: "4", - Address: net.IPNet{ - IP: net.ParseIP("10.1.2.3"), - Mask: net.IPv4Mask(255, 255, 255, 0), - }, - }, - }, - })) - debug, err := noop_debug.ReadDebug(debugFilePath) Expect(err).NotTo(HaveOccurred()) - Expect(debug.Command).To(Equal("GET")) + Expect(debug.Command).To(Equal("CHECK")) Expect(string(debug.CmdArgs.StdinData)).To(ContainSubstring("\"portMappings\":")) // Explicitly match stdin data as json after @@ -454,7 +454,7 @@ var _ = Describe("Invoking plugins", func() { }) It("returns the error", func() { - _, err := cniConfig.GetNetwork(netConfig, runtimeConfig) + err := cniConfig.CheckNetwork(ctx, netConfig, runtimeConfig) Expect(err).To(MatchError(ContainSubstring(`failed to find plugin "does-not-exist"`))) }) }) @@ -465,23 +465,22 @@ var _ = Describe("Invoking plugins", func() { Expect(debug.WriteDebug(debugFilePath)).To(Succeed()) }) It("unmarshals and returns the error", func() { - result, err := cniConfig.GetNetwork(netConfig, runtimeConfig) - Expect(result).To(BeNil()) + err := cniConfig.CheckNetwork(ctx, netConfig, runtimeConfig) Expect(err).To(MatchError("plugin error: banana")) }) }) - Context("when GET is called with a configuration version", func() { + Context("when CHECK is called with a configuration version", func() { var cacheFile string BeforeEach(func() { - cacheFile = resultCacheFilePath(cacheDirPath, netConfig.Network.Name, runtimeConfig.ContainerID) + cacheFile = resultCacheFilePath(cacheDirPath, netConfig.Network.Name, runtimeConfig) err := os.MkdirAll(filepath.Dir(cacheFile), 0700) Expect(err).NotTo(HaveOccurred()) }) Context("less than 0.4.0", func() { - It("fails as GET is not supported before 0.4.0", func() { + It("fails as CHECK is not supported before 0.4.0", func() { // Generate plugin config with older version pluginConfig = `{ "type": "noop", @@ -491,8 +490,8 @@ var _ = Describe("Invoking plugins", func() { var err error netConfig, err = libcni.ConfFromBytes([]byte(pluginConfig)) Expect(err).NotTo(HaveOccurred()) - _, err = cniConfig.GetNetwork(netConfig, runtimeConfig) - Expect(err).To(MatchError("configuration version \"0.3.1\" does not support the GET command")) + err = cniConfig.CheckNetwork(ctx, netConfig, runtimeConfig) + Expect(err).To(MatchError("configuration version \"0.3.1\" does not support the CHECK command")) debug, err := noop_debug.ReadDebug(debugFilePath) Expect(err).NotTo(HaveOccurred()) @@ -509,7 +508,7 @@ var _ = Describe("Invoking plugins", func() { }`), 0600) Expect(err).NotTo(HaveOccurred()) - _, err = cniConfig.GetNetwork(netConfig, runtimeConfig) + err = cniConfig.CheckNetwork(ctx, netConfig, runtimeConfig) Expect(err).NotTo(HaveOccurred()) debug, err := noop_debug.ReadDebug(debugFilePath) Expect(err).NotTo(HaveOccurred()) @@ -522,7 +521,7 @@ var _ = Describe("Invoking plugins", func() { var cacheFile string BeforeEach(func() { - cacheFile = resultCacheFilePath(cacheDirPath, netConfig.Network.Name, runtimeConfig.ContainerID) + cacheFile = resultCacheFilePath(cacheDirPath, netConfig.Network.Name, runtimeConfig) err := os.MkdirAll(filepath.Dir(cacheFile), 0700) Expect(err).NotTo(HaveOccurred()) }) @@ -532,9 +531,8 @@ var _ = Describe("Invoking plugins", func() { err := ioutil.WriteFile(cacheFile, []byte("adfadsfasdfasfdsafaf"), 0600) Expect(err).NotTo(HaveOccurred()) - result, err := cniConfig.GetNetwork(netConfig, runtimeConfig) - Expect(result).To(BeNil()) - Expect(err).To(MatchError("failed to get network 'apitest' cached result: decoding version from network config: invalid character 'a' looking for beginning of value")) + err = cniConfig.CheckNetwork(ctx, netConfig, runtimeConfig) + Expect(err).To(MatchError("failed to get network \"apitest\" cached result: decoding version from network config: invalid character 'a' looking for beginning of value")) }) }) @@ -547,7 +545,7 @@ var _ = Describe("Invoking plugins", func() { }`), 0600) Expect(err).NotTo(HaveOccurred()) - _, err = cniConfig.GetNetwork(netConfig, runtimeConfig) + err = cniConfig.CheckNetwork(ctx, netConfig, runtimeConfig) Expect(err).NotTo(HaveOccurred()) }) @@ -559,9 +557,8 @@ var _ = Describe("Invoking plugins", func() { }`), 0600) Expect(err).NotTo(HaveOccurred()) - result, err := cniConfig.GetNetwork(netConfig, runtimeConfig) - Expect(result).To(BeNil()) - Expect(err).To(MatchError("failed to get network 'apitest' cached result: unsupported CNI result version \"0.4567.0\"")) + err = cniConfig.CheckNetwork(ctx, netConfig, runtimeConfig) + Expect(err).To(MatchError("failed to get network \"apitest\" cached result: unsupported CNI result version \"0.4567.0\"")) }) }) }) @@ -569,7 +566,7 @@ var _ = Describe("Invoking plugins", func() { Describe("DelNetwork", func() { It("executes the plugin with command DEL", func() { - cacheFile := resultCacheFilePath(cacheDirPath, netConfig.Network.Name, runtimeConfig.ContainerID) + cacheFile := resultCacheFilePath(cacheDirPath, netConfig.Network.Name, runtimeConfig) err := os.MkdirAll(filepath.Dir(cacheFile), 0700) Expect(err).NotTo(HaveOccurred()) cachedJson := `{ @@ -580,7 +577,7 @@ var _ = Describe("Invoking plugins", func() { err = ioutil.WriteFile(cacheFile, []byte(cachedJson), 0600) Expect(err).NotTo(HaveOccurred()) - err = cniConfig.DelNetwork(netConfig, runtimeConfig) + err = cniConfig.DelNetwork(ctx, netConfig, runtimeConfig) Expect(err).NotTo(HaveOccurred()) debug, err := noop_debug.ReadDebug(debugFilePath) @@ -603,6 +600,11 @@ var _ = Describe("Invoking plugins", func() { debug.CmdArgs.StdinData = nil expectedCmdArgs.StdinData = nil Expect(debug.CmdArgs).To(Equal(expectedCmdArgs)) + + // Ensure the cached result no longer exists + cachedResult, err := cniConfig.GetNetworkCachedResult(netConfig, runtimeConfig) + Expect(err).NotTo(HaveOccurred()) + Expect(cachedResult).To(BeNil()) }) Context("when finding the plugin fails", func() { @@ -611,7 +613,7 @@ var _ = Describe("Invoking plugins", func() { }) It("returns the error", func() { - err := cniConfig.DelNetwork(netConfig, runtimeConfig) + err := cniConfig.DelNetwork(ctx, netConfig, runtimeConfig) Expect(err).To(MatchError(ContainSubstring(`failed to find plugin "does-not-exist"`))) }) }) @@ -622,7 +624,7 @@ var _ = Describe("Invoking plugins", func() { Expect(debug.WriteDebug(debugFilePath)).To(Succeed()) }) It("unmarshals and returns the error", func() { - err := cniConfig.DelNetwork(netConfig, runtimeConfig) + err := cniConfig.DelNetwork(ctx, netConfig, runtimeConfig) Expect(err).To(MatchError("plugin error: banana")) }) }) @@ -631,7 +633,7 @@ var _ = Describe("Invoking plugins", func() { var cacheFile string BeforeEach(func() { - cacheFile = resultCacheFilePath(cacheDirPath, netConfig.Network.Name, runtimeConfig.ContainerID) + cacheFile = resultCacheFilePath(cacheDirPath, netConfig.Network.Name, runtimeConfig) err := os.MkdirAll(filepath.Dir(cacheFile), 0700) Expect(err).NotTo(HaveOccurred()) }) @@ -644,12 +646,12 @@ var _ = Describe("Invoking plugins", func() { }`), 0600) Expect(err).NotTo(HaveOccurred()) - err = cniConfig.DelNetwork(netConfig, runtimeConfig) + err = cniConfig.DelNetwork(ctx, netConfig, runtimeConfig) Expect(err).NotTo(HaveOccurred()) _, err = ioutil.ReadFile(cacheFile) Expect(err).To(HaveOccurred()) - err = cniConfig.DelNetwork(netConfig, runtimeConfig) + err = cniConfig.DelNetwork(ctx, netConfig, runtimeConfig) Expect(err).NotTo(HaveOccurred()) }) }) @@ -658,7 +660,7 @@ var _ = Describe("Invoking plugins", func() { var cacheFile string BeforeEach(func() { - cacheFile = resultCacheFilePath(cacheDirPath, netConfig.Network.Name, runtimeConfig.ContainerID) + cacheFile = resultCacheFilePath(cacheDirPath, netConfig.Network.Name, runtimeConfig) err := os.MkdirAll(filepath.Dir(cacheFile), 0700) Expect(err).NotTo(HaveOccurred()) }) @@ -680,7 +682,7 @@ var _ = Describe("Invoking plugins", func() { }` netConfig, err = libcni.ConfFromBytes([]byte(pluginConfig)) Expect(err).NotTo(HaveOccurred()) - err = cniConfig.DelNetwork(netConfig, runtimeConfig) + err = cniConfig.DelNetwork(ctx, netConfig, runtimeConfig) Expect(err).NotTo(HaveOccurred()) debug, err := noop_debug.ReadDebug(debugFilePath) @@ -699,7 +701,7 @@ var _ = Describe("Invoking plugins", func() { }`), 0600) Expect(err).NotTo(HaveOccurred()) - err = cniConfig.DelNetwork(netConfig, runtimeConfig) + err = cniConfig.DelNetwork(ctx, netConfig, runtimeConfig) Expect(err).NotTo(HaveOccurred()) debug, err := noop_debug.ReadDebug(debugFilePath) @@ -714,7 +716,7 @@ var _ = Describe("Invoking plugins", func() { var cacheFile string BeforeEach(func() { - cacheFile = resultCacheFilePath(cacheDirPath, netConfig.Network.Name, runtimeConfig.ContainerID) + cacheFile = resultCacheFilePath(cacheDirPath, netConfig.Network.Name, runtimeConfig) err := os.MkdirAll(filepath.Dir(cacheFile), 0700) Expect(err).NotTo(HaveOccurred()) }) @@ -724,8 +726,8 @@ var _ = Describe("Invoking plugins", func() { err := ioutil.WriteFile(cacheFile, []byte("adfadsfasdfasfdsafaf"), 0600) Expect(err).NotTo(HaveOccurred()) - err = cniConfig.DelNetwork(netConfig, runtimeConfig) - Expect(err).To(MatchError("failed to get network 'apitest' cached result: decoding version from network config: invalid character 'a' looking for beginning of value")) + err = cniConfig.DelNetwork(ctx, netConfig, runtimeConfig) + Expect(err).To(MatchError("failed to get network \"apitest\" cached result: decoding version from network config: invalid character 'a' looking for beginning of value")) }) }) @@ -738,7 +740,7 @@ var _ = Describe("Invoking plugins", func() { }`), 0600) Expect(err).NotTo(HaveOccurred()) - err = cniConfig.DelNetwork(netConfig, runtimeConfig) + err = cniConfig.DelNetwork(ctx, netConfig, runtimeConfig) Expect(err).NotTo(HaveOccurred()) }) @@ -750,8 +752,8 @@ var _ = Describe("Invoking plugins", func() { }`), 0600) Expect(err).NotTo(HaveOccurred()) - err = cniConfig.DelNetwork(netConfig, runtimeConfig) - Expect(err).To(MatchError("failed to get network 'apitest' cached result: unsupported CNI result version \"0.4567.0\"")) + err = cniConfig.DelNetwork(ctx, netConfig, runtimeConfig) + Expect(err).To(MatchError("failed to get network \"apitest\" cached result: unsupported CNI result version \"0.4567.0\"")) }) }) }) @@ -759,7 +761,7 @@ var _ = Describe("Invoking plugins", func() { Describe("GetVersionInfo", func() { It("executes the plugin with the command VERSION", func() { - versionInfo, err := cniConfig.GetVersionInfo("noop") + versionInfo, err := cniConfig.GetVersionInfo(ctx, "noop") Expect(err).NotTo(HaveOccurred()) Expect(versionInfo).NotTo(BeNil()) @@ -770,11 +772,30 @@ var _ = Describe("Invoking plugins", func() { Context("when finding the plugin fails", func() { It("returns the error", func() { - _, err := cniConfig.GetVersionInfo("does-not-exist") + _, err := cniConfig.GetVersionInfo(ctx, "does-not-exist") Expect(err).To(MatchError(ContainSubstring(`failed to find plugin "does-not-exist"`))) }) }) }) + + Describe("ValidateNetwork", func() { + It("validates a good configuration", func() { + _, err := cniConfig.ValidateNetwork(ctx, netConfig) + Expect(err).NotTo(HaveOccurred()) + }) + + It("catches non-existent plugins", func() { + netConfig.Network.Type = "nope" + _, err := cniConfig.ValidateNetwork(ctx, netConfig) + Expect(err).To(MatchError("failed to find plugin \"nope\" in path [" + cniConfig.Path[0] + "]")) + }) + + It("catches unsupported versions", func() { + netConfig.Network.CNIVersion = "broken" + _, err := cniConfig.ValidateNetwork(ctx, netConfig) + Expect(err).To(MatchError("plugin noop does not support config version \"broken\"")) + }) + }) }) Describe("Invoking a plugin list", func() { @@ -784,6 +805,8 @@ var _ = Describe("Invoking plugins", func() { cniConfig *libcni.CNIConfig netConfigList *libcni.NetworkConfigList runtimeConfig *libcni.RuntimeConf + ctx context.Context + ipResult string expectedCmdArgs skel.CmdArgs ) @@ -828,7 +851,7 @@ var _ = Describe("Invoking plugins", func() { "otherCapability": capabilityArgs["otherCapability"], } - ipResult := fmt.Sprintf(`{"cniVersion": "%s", "dns":{},"ips":[{"version": "4", "address": "10.1.2.3/24"}]}`, current.ImplementedSpecVersion) + ipResult = fmt.Sprintf(`{"cniVersion": "%s", "dns":{},"ips":[{"version": "4", "address": "10.1.2.3/24"}]}`, current.ImplementedSpecVersion) plugins = make([]pluginInfo, 3, 3) plugins[0] = newPluginInfo("some-value", "", true, ipResult, rc, []string{"portMappings", "otherCapability"}) plugins[1] = newPluginInfo("some-other-value", ipResult, true, "PASSTHROUGH", rc, []string{"otherCapability"}) @@ -846,6 +869,7 @@ var _ = Describe("Invoking plugins", func() { netConfigList, err = libcni.ConfListFromBytes(configList) Expect(err).NotTo(HaveOccurred()) + ctx = context.TODO() }) AfterEach(func() { @@ -856,7 +880,7 @@ var _ = Describe("Invoking plugins", func() { Describe("AddNetworkList", func() { It("executes all plugins with command ADD and returns an intermediate result", func() { - r, err := cniConfig.AddNetworkList(netConfigList, runtimeConfig) + r, err := cniConfig.AddNetworkList(ctx, netConfigList, runtimeConfig) Expect(err).NotTo(HaveOccurred()) result, err := current.GetResult(r) @@ -893,21 +917,23 @@ var _ = Describe("Invoking plugins", func() { }) It("writes the correct cached result", func() { - r, err := cniConfig.AddNetworkList(netConfigList, runtimeConfig) + r, err := cniConfig.AddNetworkList(ctx, netConfigList, runtimeConfig) Expect(err).NotTo(HaveOccurred()) result, err := current.GetResult(r) Expect(err).NotTo(HaveOccurred()) // Ensure the cached result matches the returned one - cacheFile := resultCacheFilePath(cacheDirPath, netConfigList.Name, runtimeConfig.ContainerID) - _, err = os.Stat(cacheFile) + cachedResult, err := cniConfig.GetNetworkListCachedResult(netConfigList, runtimeConfig) + Expect(err).NotTo(HaveOccurred()) + result2, err := current.GetResult(cachedResult) Expect(err).NotTo(HaveOccurred()) - cachedData, err := ioutil.ReadFile(cacheFile) + cachedJson, err := json.Marshal(result2) Expect(err).NotTo(HaveOccurred()) - returnedData, err := json.Marshal(result) + + returnedJson, err := json.Marshal(result) Expect(err).NotTo(HaveOccurred()) - Expect(cachedData).To(MatchJSON(returnedData)) + Expect(cachedJson).To(MatchJSON(returnedJson)) }) Context("when finding the plugin fails", func() { @@ -916,7 +942,7 @@ var _ = Describe("Invoking plugins", func() { }) It("returns the error", func() { - _, err := cniConfig.AddNetworkList(netConfigList, runtimeConfig) + _, err := cniConfig.AddNetworkList(ctx, netConfigList, runtimeConfig) Expect(err).To(MatchError(ContainSubstring(`failed to find plugin "does-not-exist"`))) }) }) @@ -927,7 +953,7 @@ var _ = Describe("Invoking plugins", func() { Expect(plugins[1].debug.WriteDebug(plugins[1].debugFilePath)).To(Succeed()) }) It("unmarshals and returns the error", func() { - result, err := cniConfig.AddNetworkList(netConfigList, runtimeConfig) + result, err := cniConfig.AddNetworkList(ctx, netConfigList, runtimeConfig) Expect(result).To(BeNil()) Expect(err).To(MatchError("plugin error: banana")) }) @@ -935,62 +961,66 @@ var _ = Describe("Invoking plugins", func() { Context("when the result cache directory cannot be accessed", func() { It("returns an error", func() { - // Make the results directory inaccessble by making it a + // Make the results directory inaccessible by making it a // file instead of a directory tmpPath := filepath.Join(cacheDirPath, "results") err := ioutil.WriteFile(tmpPath, []byte("afdsasdfasdf"), 0600) Expect(err).NotTo(HaveOccurred()) - result, err := cniConfig.AddNetworkList(netConfigList, runtimeConfig) + result, err := cniConfig.AddNetworkList(ctx, netConfigList, runtimeConfig) Expect(result).To(BeNil()) Expect(err).To(HaveOccurred()) }) }) }) - Describe("GetNetworkList", func() { - It("executes all plugins with command GET and returns an intermediate result", func() { - r, err := cniConfig.GetNetworkList(netConfigList, runtimeConfig) + Describe("CheckNetworkList", func() { + It("executes all plugins with command CHECK", func() { + cacheFile := resultCacheFilePath(cacheDirPath, netConfigList.Name, runtimeConfig) + err := os.MkdirAll(filepath.Dir(cacheFile), 0700) Expect(err).NotTo(HaveOccurred()) - - result, err := current.GetResult(r) + err = ioutil.WriteFile(cacheFile, []byte(ipResult), 0600) Expect(err).NotTo(HaveOccurred()) - Expect(result).To(Equal(¤t.Result{ - CNIVersion: current.ImplementedSpecVersion, - // IP4 added by first plugin - IPs: []*current.IPConfig{ - { - Version: "4", - Address: net.IPNet{ - IP: net.ParseIP("10.1.2.3"), - Mask: net.IPv4Mask(255, 255, 255, 0), - }, - }, - }, - // DNS injected by last plugin - DNS: types.DNS{ - Nameservers: []string{"1.2.3.4"}, - }, - })) + err = cniConfig.CheckNetworkList(ctx, netConfigList, runtimeConfig) + Expect(err).NotTo(HaveOccurred()) for i := 0; i < len(plugins); i++ { debug, err := noop_debug.ReadDebug(plugins[i].debugFilePath) Expect(err).NotTo(HaveOccurred()) - Expect(debug.Command).To(Equal("GET")) + Expect(debug.Command).To(Equal("CHECK")) + + // Ensure each plugin gets the prevResult from the cache + asMap := make(map[string]interface{}) + err = json.Unmarshal(debug.CmdArgs.StdinData, &asMap) + Expect(err).NotTo(HaveOccurred()) + Expect(asMap["prevResult"]).NotTo(BeNil()) + foo, err := json.Marshal(asMap["prevResult"]) + Expect(err).NotTo(HaveOccurred()) + Expect(foo).To(MatchJSON(ipResult)) - // Must explicitly match JSON due to dict element ordering - Expect(debug.CmdArgs.StdinData).To(MatchJSON(plugins[i].stdinData)) debug.CmdArgs.StdinData = nil Expect(debug.CmdArgs).To(Equal(expectedCmdArgs)) } }) + It("does not executes plugins with command CHECK when disableCheck is true", func() { + netConfigList.DisableCheck = true + err := cniConfig.CheckNetworkList(ctx, netConfigList, runtimeConfig) + Expect(err).NotTo(HaveOccurred()) + + for i := 0; i < len(plugins); i++ { + debug, err := noop_debug.ReadDebug(plugins[i].debugFilePath) + Expect(err).NotTo(HaveOccurred()) + Expect(debug.Command).To(Equal("")) + } + }) + Context("when the configuration version", func() { var cacheFile string BeforeEach(func() { - cacheFile = resultCacheFilePath(cacheDirPath, netConfigList.Name, runtimeConfig.ContainerID) + cacheFile = resultCacheFilePath(cacheDirPath, netConfigList.Name, runtimeConfig) err := os.MkdirAll(filepath.Dir(cacheFile), 0700) Expect(err).NotTo(HaveOccurred()) }) @@ -1005,7 +1035,7 @@ var _ = Describe("Invoking plugins", func() { err := ioutil.WriteFile(cacheFile, []byte(cachedJson), 0600) Expect(err).NotTo(HaveOccurred()) - _, err = cniConfig.GetNetworkList(netConfigList, runtimeConfig) + err = cniConfig.CheckNetworkList(ctx, netConfigList, runtimeConfig) Expect(err).NotTo(HaveOccurred()) // Match the first plugin's stdin config to the cached result JSON @@ -1022,7 +1052,7 @@ var _ = Describe("Invoking plugins", func() { }) Context("is less than 0.4.0", func() { - It("fails as GET is not supported before 0.4.0", func() { + It("fails as CHECK is not supported before 0.4.0", func() { // Set an older CNI version confList := make(map[string]interface{}) err := json.Unmarshal(netConfigList.Bytes, &confList) @@ -1033,8 +1063,8 @@ var _ = Describe("Invoking plugins", func() { netConfigList, err = libcni.ConfListFromBytes(newBytes) Expect(err).NotTo(HaveOccurred()) - _, err = cniConfig.GetNetworkList(netConfigList, runtimeConfig) - Expect(err).To(MatchError("configuration version \"0.3.1\" does not support the GET command")) + err = cniConfig.CheckNetworkList(ctx, netConfigList, runtimeConfig) + Expect(err).To(MatchError("configuration version \"0.3.1\" does not support the CHECK command")) }) }) }) @@ -1045,7 +1075,7 @@ var _ = Describe("Invoking plugins", func() { }) It("returns the error", func() { - _, err := cniConfig.GetNetworkList(netConfigList, runtimeConfig) + err := cniConfig.CheckNetworkList(ctx, netConfigList, runtimeConfig) Expect(err).To(MatchError(ContainSubstring(`failed to find plugin "does-not-exist"`))) }) }) @@ -1056,30 +1086,28 @@ var _ = Describe("Invoking plugins", func() { Expect(plugins[1].debug.WriteDebug(plugins[1].debugFilePath)).To(Succeed()) }) It("unmarshals and returns the error", func() { - result, err := cniConfig.GetNetworkList(netConfigList, runtimeConfig) - Expect(result).To(BeNil()) + err := cniConfig.CheckNetworkList(ctx, netConfigList, runtimeConfig) Expect(err).To(MatchError("plugin error: banana")) }) }) Context("when the cached result is invalid", func() { It("returns an error", func() { - cacheFile := resultCacheFilePath(cacheDirPath, netConfigList.Name, runtimeConfig.ContainerID) + cacheFile := resultCacheFilePath(cacheDirPath, netConfigList.Name, runtimeConfig) err := os.MkdirAll(filepath.Dir(cacheFile), 0700) Expect(err).NotTo(HaveOccurred()) err = ioutil.WriteFile(cacheFile, []byte("adfadsfasdfasfdsafaf"), 0600) Expect(err).NotTo(HaveOccurred()) - result, err := cniConfig.GetNetworkList(netConfigList, runtimeConfig) - Expect(result).To(BeNil()) - Expect(err).To(MatchError("failed to get network 'some-list' cached result: decoding version from network config: invalid character 'a' looking for beginning of value")) + err = cniConfig.CheckNetworkList(ctx, netConfigList, runtimeConfig) + Expect(err).To(MatchError("failed to get network \"some-list\" cached result: decoding version from network config: invalid character 'a' looking for beginning of value")) }) }) }) Describe("DelNetworkList", func() { It("executes all the plugins in reverse order with command DEL", func() { - err := cniConfig.DelNetworkList(netConfigList, runtimeConfig) + err := cniConfig.DelNetworkList(ctx, netConfigList, runtimeConfig) Expect(err).NotTo(HaveOccurred()) for i := 0; i < len(plugins); i++ { @@ -1092,13 +1120,18 @@ var _ = Describe("Invoking plugins", func() { debug.CmdArgs.StdinData = nil Expect(debug.CmdArgs).To(Equal(expectedCmdArgs)) } + + // Ensure the cached result no longer exists + cachedResult, err := cniConfig.GetNetworkListCachedResult(netConfigList, runtimeConfig) + Expect(err).NotTo(HaveOccurred()) + Expect(cachedResult).To(BeNil()) }) Context("when the configuration version", func() { var cacheFile string BeforeEach(func() { - cacheFile = resultCacheFilePath(cacheDirPath, netConfigList.Name, runtimeConfig.ContainerID) + cacheFile = resultCacheFilePath(cacheDirPath, netConfigList.Name, runtimeConfig) err := os.MkdirAll(filepath.Dir(cacheFile), 0700) Expect(err).NotTo(HaveOccurred()) }) @@ -1113,7 +1146,7 @@ var _ = Describe("Invoking plugins", func() { err := ioutil.WriteFile(cacheFile, []byte(cachedJson), 0600) Expect(err).NotTo(HaveOccurred()) - err = cniConfig.DelNetworkList(netConfigList, runtimeConfig) + err = cniConfig.DelNetworkList(ctx, netConfigList, runtimeConfig) Expect(err).NotTo(HaveOccurred()) // Match the first plugin's stdin config to the cached result JSON @@ -1148,7 +1181,7 @@ var _ = Describe("Invoking plugins", func() { netConfigList, err = libcni.ConfListFromBytes(newBytes) Expect(err).NotTo(HaveOccurred()) - err = cniConfig.DelNetworkList(netConfigList, runtimeConfig) + err = cniConfig.DelNetworkList(ctx, netConfigList, runtimeConfig) Expect(err).NotTo(HaveOccurred()) // Make sure first plugin does not receive a prevResult @@ -1165,7 +1198,7 @@ var _ = Describe("Invoking plugins", func() { }) It("returns the error", func() { - err := cniConfig.DelNetworkList(netConfigList, runtimeConfig) + err := cniConfig.DelNetworkList(ctx, netConfigList, runtimeConfig) Expect(err).To(MatchError(ContainSubstring(`failed to find plugin "does-not-exist"`))) }) }) @@ -1176,24 +1209,313 @@ var _ = Describe("Invoking plugins", func() { Expect(plugins[1].debug.WriteDebug(plugins[1].debugFilePath)).To(Succeed()) }) It("unmarshals and returns the error", func() { - err := cniConfig.DelNetworkList(netConfigList, runtimeConfig) + err := cniConfig.DelNetworkList(ctx, netConfigList, runtimeConfig) Expect(err).To(MatchError("plugin error: banana")) }) }) Context("when the cached result is invalid", func() { It("returns an error", func() { - cacheFile := resultCacheFilePath(cacheDirPath, netConfigList.Name, runtimeConfig.ContainerID) + cacheFile := resultCacheFilePath(cacheDirPath, netConfigList.Name, runtimeConfig) err := os.MkdirAll(filepath.Dir(cacheFile), 0700) Expect(err).NotTo(HaveOccurred()) err = ioutil.WriteFile(cacheFile, []byte("adfadsfasdfasfdsafaf"), 0600) Expect(err).NotTo(HaveOccurred()) - err = cniConfig.DelNetworkList(netConfigList, runtimeConfig) - Expect(err).To(MatchError("failed to get network 'some-list' cached result: decoding version from network config: invalid character 'a' looking for beginning of value")) + err = cniConfig.DelNetworkList(ctx, netConfigList, runtimeConfig) + Expect(err).To(MatchError("failed to get network \"some-list\" cached result: decoding version from network config: invalid character 'a' looking for beginning of value")) + }) + }) + }) + Describe("ValidateNetworkList", func() { + It("Checks that all plugins exist", func() { + caps, err := cniConfig.ValidateNetworkList(ctx, netConfigList) + Expect(err).NotTo(HaveOccurred()) + Expect(caps).To(ConsistOf("portMappings", "otherCapability")) + + netConfigList.Plugins[1].Network.Type = "nope" + _, err = cniConfig.ValidateNetworkList(ctx, netConfigList) + Expect(err).To(MatchError("[failed to find plugin \"nope\" in path [" + cniConfig.Path[0] + "]]")) + }) + + It("Checks that the plugins support the needed version", func() { + netConfigList.CNIVersion = "broken" + _, err := cniConfig.ValidateNetworkList(ctx, netConfigList) + + // The config list is just noop 3 times, so we get 3 errors + Expect(err).To(MatchError("[plugin noop does not support config version \"broken\" plugin noop does not support config version \"broken\" plugin noop does not support config version \"broken\"]")) + }) + }) + }) + + Describe("Invoking a sleep plugin", func() { + var ( + debugFilePath string + debug *noop_debug.Debug + cniBinPath string + pluginConfig string + cniConfig *libcni.CNIConfig + netConfig *libcni.NetworkConfig + runtimeConfig *libcni.RuntimeConf + netConfigList *libcni.NetworkConfigList + ) + + BeforeEach(func() { + debugFile, err := ioutil.TempFile("", "cni_debug") + Expect(err).NotTo(HaveOccurred()) + Expect(debugFile.Close()).To(Succeed()) + debugFilePath = debugFile.Name() + + debug = &noop_debug.Debug{ + ReportResult: `{ "ips": [{ "version": "4", "address": "10.1.2.3/24" }], "dns": {} }`, + } + Expect(debug.WriteDebug(debugFilePath)).To(Succeed()) + + portMappings := []portMapping{ + {HostPort: 8080, ContainerPort: 80, Protocol: "tcp"}, + } + + pluginConfig = fmt.Sprintf(`{ + "type": "sleep", + "name": "apitest", + "some-key": "some-value", + "cniVersion": "%s", + "capabilities": { "portMappings": true } + }`, current.ImplementedSpecVersion) + + cniBinPath = filepath.Dir(pluginPaths["sleep"]) + cniConfig = libcni.NewCNIConfig([]string{cniBinPath}, nil) + netConfig, err = libcni.ConfFromBytes([]byte(pluginConfig)) + runtimeConfig = &libcni.RuntimeConf{ + ContainerID: "some-container-id", + NetNS: "/some/netns/path", + IfName: "some-eth0", + Args: [][2]string{{"DEBUG", debugFilePath}}, + } + + // inject runtime args into the expected plugin config + conf := make(map[string]interface{}) + err = json.Unmarshal([]byte(pluginConfig), &conf) + Expect(err).NotTo(HaveOccurred()) + conf["runtimeConfig"] = map[string]interface{}{ + "portMappings": portMappings, + } + + configList := []byte(fmt.Sprintf(`{ + "name": "some-list", + "cniVersion": "%s", + "plugins": [ + %s + ] +}`, current.ImplementedSpecVersion, pluginConfig)) + + netConfigList, err = libcni.ConfListFromBytes(configList) + Expect(err).NotTo(HaveOccurred()) + + }) + + AfterEach(func() { + Expect(os.RemoveAll(debugFilePath)).To(Succeed()) + }) + + Describe("AddNetwork", func() { + Context("when the plugin timeout", func() { + It("returns the timeout error", func() { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*2) + result, err := cniConfig.AddNetwork(ctx, netConfig, runtimeConfig) + cancel() + Expect(result).To(BeNil()) + Expect(err).To(MatchError(ContainSubstring("netplugin failed with no error message"))) + }) + + }) + }) + + Describe("DelNetwork", func() { + Context("when the plugin timeout", func() { + It("returns the timeout error", func() { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*2) + err := cniConfig.DelNetwork(ctx, netConfig, runtimeConfig) + cancel() + Expect(err).To(MatchError(ContainSubstring("netplugin failed with no error message"))) + }) + + }) + }) + + Describe("CheckNetwork", func() { + Context("when the plugin timeout", func() { + It("returns the timeout error", func() { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*2) + err := cniConfig.CheckNetwork(ctx, netConfig, runtimeConfig) + cancel() + Expect(err).To(MatchError(ContainSubstring("netplugin failed with no error message"))) + }) + + }) + }) + + Describe("GetVersionInfo", func() { + Context("when the plugin timeout", func() { + It("returns the timeout error", func() { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*2) + result, err := cniConfig.GetVersionInfo(ctx, "sleep") + cancel() + Expect(result).To(BeNil()) + Expect(err).To(MatchError(ContainSubstring("netplugin failed with no error message"))) + }) + + }) + }) + + Describe("ValidateNetwork", func() { + Context("when the plugin timeout", func() { + It("returns the timeout error", func() { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*2) + _, err := cniConfig.ValidateNetwork(ctx, netConfig) + cancel() + Expect(err).To(MatchError(ContainSubstring("netplugin failed with no error message"))) + }) + + }) + }) + + Describe("AddNetworkList", func() { + Context("when the plugin timeout", func() { + It("returns the timeout error", func() { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*2) + result, err := cniConfig.AddNetworkList(ctx, netConfigList, runtimeConfig) + cancel() + Expect(result).To(BeNil()) + Expect(err).To(MatchError(ContainSubstring("netplugin failed with no error message"))) + }) + + }) + }) + + Describe("DelNetworkList", func() { + Context("when the plugin timeout", func() { + It("returns the timeout error", func() { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*2) + err := cniConfig.DelNetworkList(ctx, netConfigList, runtimeConfig) + cancel() + Expect(err).To(MatchError(ContainSubstring("netplugin failed with no error message"))) + }) + + }) + }) + + Describe("CheckNetworkList", func() { + Context("when the plugin timeout", func() { + It("returns the timeout error", func() { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*2) + err := cniConfig.CheckNetworkList(ctx, netConfigList, runtimeConfig) + cancel() + Expect(err).To(MatchError(ContainSubstring("netplugin failed with no error message"))) + }) + + }) + }) + + Describe("ValidateNetworkList", func() { + Context("when the plugin timeout", func() { + It("returns the timeout error", func() { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*2) + _, err := cniConfig.ValidateNetworkList(ctx, netConfigList) + cancel() + Expect(err).To(MatchError(ContainSubstring("netplugin failed with no error message"))) }) + }) }) }) + + Describe("Result cache operations", func() { + var ( + debugFilePath string + debug *noop_debug.Debug + cniBinPath string + pluginConfig string + cniConfig *libcni.CNIConfig + netConfig *libcni.NetworkConfig + runtimeConfig *libcni.RuntimeConf + + ctx context.Context + ) + firstIP := "10.1.2.3/24" + firstIfname := "eth0" + secondIP := "10.1.2.5/24" + secondIfname := "eth1" + + BeforeEach(func() { + debugFile, err := ioutil.TempFile("", "cni_debug") + Expect(err).NotTo(HaveOccurred()) + Expect(debugFile.Close()).To(Succeed()) + debugFilePath = debugFile.Name() + + debug = &noop_debug.Debug{ + ReportResult: fmt.Sprintf(`{ + "cniVersion": "%s", + "ips": [{"version": "4", "address": "%s"}] + }`, current.ImplementedSpecVersion, firstIP), + } + Expect(debug.WriteDebug(debugFilePath)).To(Succeed()) + + cniBinPath = filepath.Dir(pluginPaths["noop"]) + pluginConfig = fmt.Sprintf(`{ + "type": "noop", + "name": "cachetest", + "cniVersion": "%s" + }`, current.ImplementedSpecVersion) + cniConfig = libcni.NewCNIConfig([]string{cniBinPath}, nil) + netConfig, err = libcni.ConfFromBytes([]byte(pluginConfig)) + Expect(err).NotTo(HaveOccurred()) + runtimeConfig = &libcni.RuntimeConf{ + ContainerID: "some-container-id", + NetNS: "/some/netns/path", + IfName: firstIfname, + CacheDir: cacheDirPath, + Args: [][2]string{{"DEBUG", debugFilePath}}, + } + ctx = context.TODO() + }) + + AfterEach(func() { + Expect(os.RemoveAll(debugFilePath)).To(Succeed()) + }) + + It("creates separate cache files for multiple attachments to the same network", func() { + _, err := cniConfig.AddNetwork(ctx, netConfig, runtimeConfig) + Expect(err).NotTo(HaveOccurred()) + + debug.ReportResult = fmt.Sprintf(`{ + "cniVersion": "%s", + "ips": [{"version": "4", "address": "%s"}] + }`, current.ImplementedSpecVersion, secondIP) + Expect(debug.WriteDebug(debugFilePath)).To(Succeed()) + runtimeConfig.IfName = secondIfname + _, err = cniConfig.AddNetwork(ctx, netConfig, runtimeConfig) + Expect(err).NotTo(HaveOccurred()) + + resultsDir := filepath.Join(cacheDirPath, "results") + files, err := ioutil.ReadDir(resultsDir) + Expect(err).NotTo(HaveOccurred()) + Expect(len(files)).To(Equal(2)) + var foundFirst, foundSecond bool + for _, f := range files { + data, err := ioutil.ReadFile(filepath.Join(resultsDir, f.Name())) + Expect(err).NotTo(HaveOccurred()) + if strings.HasSuffix(f.Name(), firstIfname) { + foundFirst = true + Expect(strings.Contains(string(data), firstIP)).To(BeTrue()) + } else if strings.HasSuffix(f.Name(), secondIfname) { + foundSecond = true + Expect(strings.Contains(string(data), secondIP)).To(BeTrue()) + } + } + Expect(foundFirst).To(BeTrue()) + Expect(foundSecond).To(BeTrue()) + }) + }) }) diff --git a/vendor/github.com/containernetworking/cni/libcni/backwards_compatibility_test.go b/vendor/github.com/containernetworking/cni/libcni/backwards_compatibility_test.go index 6480193ca..3aab01a64 100644 --- a/vendor/github.com/containernetworking/cni/libcni/backwards_compatibility_test.go +++ b/vendor/github.com/containernetworking/cni/libcni/backwards_compatibility_test.go @@ -15,6 +15,7 @@ package libcni_test import ( + "context" "fmt" "io/ioutil" "os" @@ -61,7 +62,7 @@ var _ = Describe("Backwards compatibility", func() { cniConfig := libcni.NewCNIConfig([]string{filepath.Dir(pluginPath)}, nil) - result, err := cniConfig.AddNetwork(netConf, runtimeConf) + result, err := cniConfig.AddNetwork(context.TODO(), netConf, runtimeConf) Expect(err).NotTo(HaveOccurred()) Expect(result).To(Equal(legacy_examples.ExpectedResult)) diff --git a/vendor/github.com/containernetworking/cni/libcni/conf.go b/vendor/github.com/containernetworking/cni/libcni/conf.go index 9834d715b..ea56c509d 100644 --- a/vendor/github.com/containernetworking/cni/libcni/conf.go +++ b/vendor/github.com/containernetworking/cni/libcni/conf.go @@ -83,10 +83,19 @@ func ConfListFromBytes(bytes []byte) (*NetworkConfigList, error) { } } + disableCheck := false + if rawDisableCheck, ok := rawList["disableCheck"]; ok { + disableCheck, ok = rawDisableCheck.(bool) + if !ok { + return nil, fmt.Errorf("error parsing configuration list: invalid disableCheck type %T", rawDisableCheck) + } + } + list := &NetworkConfigList{ - Name: name, - CNIVersion: cniVersion, - Bytes: bytes, + Name: name, + DisableCheck: disableCheck, + CNIVersion: cniVersion, + Bytes: bytes, } var plugins []interface{} diff --git a/vendor/github.com/containernetworking/cni/libcni/conf_test.go b/vendor/github.com/containernetworking/cni/libcni/conf_test.go index c931a5abd..e645829cb 100644 --- a/vendor/github.com/containernetworking/cni/libcni/conf_test.go +++ b/vendor/github.com/containernetworking/cni/libcni/conf_test.go @@ -202,6 +202,7 @@ var _ = Describe("Loading configuration from disk", func() { configList = []byte(`{ "name": "some-list", "cniVersion": "0.2.0", + "disableCheck": true, "plugins": [ { "type": "host-local", @@ -228,8 +229,9 @@ var _ = Describe("Loading configuration from disk", func() { netConfigList, err := libcni.LoadConfList(configDir, "some-list") Expect(err).NotTo(HaveOccurred()) Expect(netConfigList).To(Equal(&libcni.NetworkConfigList{ - Name: "some-list", - CNIVersion: "0.2.0", + Name: "some-list", + CNIVersion: "0.2.0", + DisableCheck: true, Plugins: []*libcni.NetworkConfig{ { Network: &types.NetConf{Type: "host-local"}, diff --git a/vendor/github.com/containernetworking/cni/libcni/libcni_suite_test.go b/vendor/github.com/containernetworking/cni/libcni/libcni_suite_test.go index 057567f8a..641221531 100644 --- a/vendor/github.com/containernetworking/cni/libcni/libcni_suite_test.go +++ b/vendor/github.com/containernetworking/cni/libcni/libcni_suite_test.go @@ -31,7 +31,8 @@ func TestLibcni(t *testing.T) { } var pluginPackages = map[string]string{ - "noop": "github.com/containernetworking/cni/plugins/test/noop", + "noop": "github.com/containernetworking/cni/plugins/test/noop", + "sleep": "github.com/containernetworking/cni/plugins/test/sleep", } var pluginPaths map[string]string diff --git a/vendor/github.com/containernetworking/cni/pkg/invoke/delegate.go b/vendor/github.com/containernetworking/cni/pkg/invoke/delegate.go index 21efdf802..30b4672f1 100644 --- a/vendor/github.com/containernetworking/cni/pkg/invoke/delegate.go +++ b/vendor/github.com/containernetworking/cni/pkg/invoke/delegate.go @@ -15,6 +15,7 @@ package invoke import ( + "context" "fmt" "os" "path/filepath" @@ -22,54 +23,53 @@ import ( "github.com/containernetworking/cni/pkg/types" ) -func delegateAddOrGet(command, delegatePlugin string, netconf []byte, exec Exec) (types.Result, error) { +func delegateCommon(expectedCommand, 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 { - return nil, err + return "", nil, err } - return ExecPluginWithResult(pluginPath, netconf, ArgsFromEnv(), exec) + return pluginPath, exec, nil } // DelegateAdd calls the given delegate plugin with the CNI ADD action and // JSON configuration -func DelegateAdd(delegatePlugin string, netconf []byte, exec Exec) (types.Result, error) { - if os.Getenv("CNI_COMMAND") != "ADD" { - return nil, fmt.Errorf("CNI_COMMAND is not ADD") +func DelegateAdd(ctx context.Context, delegatePlugin string, netconf []byte, exec Exec) (types.Result, error) { + pluginPath, realExec, err := delegateCommon("ADD", delegatePlugin, exec) + if err != nil { + return nil, err } - return delegateAddOrGet("ADD", delegatePlugin, netconf, exec) + + return ExecPluginWithResult(ctx, pluginPath, netconf, ArgsFromEnv(), realExec) } -// DelegateGet calls the given delegate plugin with the CNI GET action and +// DelegateCheck calls the given delegate plugin with the CNI CHECK action and // JSON configuration -func DelegateGet(delegatePlugin string, netconf []byte, exec Exec) (types.Result, error) { - if os.Getenv("CNI_COMMAND") != "GET" { - return nil, fmt.Errorf("CNI_COMMAND is not GET") +func DelegateCheck(ctx context.Context, delegatePlugin string, netconf []byte, exec Exec) error { + pluginPath, realExec, err := delegateCommon("CHECK", delegatePlugin, exec) + if err != nil { + return err } - return delegateAddOrGet("GET", delegatePlugin, netconf, exec) + + return ExecPluginWithoutResult(ctx, pluginPath, netconf, ArgsFromEnv(), realExec) } // DelegateDel calls the given delegate plugin with the CNI DEL action and // JSON configuration -func DelegateDel(delegatePlugin string, netconf []byte, exec Exec) error { - if exec == nil { - exec = defaultExec - } - - if os.Getenv("CNI_COMMAND") != "DEL" { - return fmt.Errorf("CNI_COMMAND is not DEL") - } - - paths := filepath.SplitList(os.Getenv("CNI_PATH")) - pluginPath, err := exec.FindInPath(delegatePlugin, paths) +func DelegateDel(ctx context.Context, delegatePlugin string, netconf []byte, exec Exec) error { + pluginPath, realExec, err := delegateCommon("DEL", delegatePlugin, exec) if err != nil { return err } - return ExecPluginWithoutResult(pluginPath, netconf, ArgsFromEnv(), exec) + return ExecPluginWithoutResult(ctx, pluginPath, netconf, ArgsFromEnv(), realExec) } diff --git a/vendor/github.com/containernetworking/cni/pkg/invoke/delegate_test.go b/vendor/github.com/containernetworking/cni/pkg/invoke/delegate_test.go index 24a1fb77b..78fb04d9a 100644 --- a/vendor/github.com/containernetworking/cni/pkg/invoke/delegate_test.go +++ b/vendor/github.com/containernetworking/cni/pkg/invoke/delegate_test.go @@ -15,6 +15,7 @@ package invoke_test import ( + "context" "encoding/json" "io/ioutil" "net" @@ -36,6 +37,7 @@ var _ = Describe("Delegate", func() { debugFileName string debugBehavior *debug.Debug expectedResult *current.Result + ctx context.Context ) BeforeEach(func() { @@ -67,7 +69,7 @@ var _ = Describe("Delegate", func() { } Expect(debugBehavior.WriteDebug(debugFileName)).To(Succeed()) pluginName = "noop" - + ctx = context.TODO() os.Setenv("CNI_ARGS", "DEBUG="+debugFileName) os.Setenv("CNI_PATH", filepath.Dir(pathToPlugin)) os.Setenv("CNI_NETNS", "/tmp/some/netns/path") @@ -89,7 +91,7 @@ var _ = Describe("Delegate", func() { }) It("finds and execs the named plugin", func() { - result, err := invoke.DelegateAdd(pluginName, netConf, nil) + result, err := invoke.DelegateAdd(ctx, pluginName, netConf, nil) Expect(err).NotTo(HaveOccurred()) Expect(result).To(Equal(expectedResult)) @@ -105,7 +107,7 @@ var _ = Describe("Delegate", func() { }) It("aborts and returns a useful error", func() { - _, err := invoke.DelegateAdd(pluginName, netConf, nil) + _, err := invoke.DelegateAdd(ctx, pluginName, netConf, nil) Expect(err).To(MatchError("CNI_COMMAND is not ADD")) }) }) @@ -116,36 +118,35 @@ var _ = Describe("Delegate", func() { }) It("returns a useful error", func() { - _, err := invoke.DelegateAdd(pluginName, netConf, nil) + _, err := invoke.DelegateAdd(ctx, pluginName, netConf, nil) Expect(err).To(MatchError(HavePrefix("failed to find plugin"))) }) }) }) - Describe("DelegateGet", func() { + Describe("DelegateCheck", func() { BeforeEach(func() { - os.Setenv("CNI_COMMAND", "GET") + os.Setenv("CNI_COMMAND", "CHECK") }) It("finds and execs the named plugin", func() { - result, err := invoke.DelegateGet(pluginName, netConf, nil) + err := invoke.DelegateCheck(ctx, pluginName, netConf, nil) Expect(err).NotTo(HaveOccurred()) - Expect(result).To(Equal(expectedResult)) pluginInvocation, err := debug.ReadDebug(debugFileName) Expect(err).NotTo(HaveOccurred()) - Expect(pluginInvocation.Command).To(Equal("GET")) + Expect(pluginInvocation.Command).To(Equal("CHECK")) Expect(pluginInvocation.CmdArgs.IfName).To(Equal("eth7")) }) - Context("if the delegation isn't part of an existing GET command", func() { + Context("if the delegation isn't part of an existing CHECK command", func() { BeforeEach(func() { os.Setenv("CNI_COMMAND", "NOPE") }) It("aborts and returns a useful error", func() { - _, err := invoke.DelegateGet(pluginName, netConf, nil) - Expect(err).To(MatchError("CNI_COMMAND is not GET")) + err := invoke.DelegateCheck(ctx, pluginName, netConf, nil) + Expect(err).To(MatchError("CNI_COMMAND is not CHECK")) }) }) @@ -155,7 +156,7 @@ var _ = Describe("Delegate", func() { }) It("returns a useful error", func() { - _, err := invoke.DelegateGet(pluginName, netConf, nil) + err := invoke.DelegateCheck(ctx, pluginName, netConf, nil) Expect(err).To(MatchError(HavePrefix("failed to find plugin"))) }) }) @@ -167,7 +168,7 @@ var _ = Describe("Delegate", func() { }) It("finds and execs the named plugin", func() { - err := invoke.DelegateDel(pluginName, netConf, nil) + err := invoke.DelegateDel(ctx, pluginName, netConf, nil) Expect(err).NotTo(HaveOccurred()) pluginInvocation, err := debug.ReadDebug(debugFileName) @@ -182,7 +183,7 @@ var _ = Describe("Delegate", func() { }) It("aborts and returns a useful error", func() { - err := invoke.DelegateDel(pluginName, netConf, nil) + err := invoke.DelegateDel(ctx, pluginName, netConf, nil) Expect(err).To(MatchError("CNI_COMMAND is not DEL")) }) }) @@ -193,7 +194,7 @@ var _ = Describe("Delegate", func() { }) It("returns a useful error", func() { - err := invoke.DelegateDel(pluginName, netConf, nil) + err := invoke.DelegateDel(ctx, pluginName, netConf, nil) Expect(err).To(MatchError(HavePrefix("failed to find plugin"))) }) }) diff --git a/vendor/github.com/containernetworking/cni/pkg/invoke/exec.go b/vendor/github.com/containernetworking/cni/pkg/invoke/exec.go index cf019d3a0..8e6d30b82 100644 --- a/vendor/github.com/containernetworking/cni/pkg/invoke/exec.go +++ b/vendor/github.com/containernetworking/cni/pkg/invoke/exec.go @@ -15,6 +15,7 @@ package invoke import ( + "context" "fmt" "os" @@ -26,7 +27,7 @@ import ( // and executing a CNI plugin. Tests may provide a fake implementation // to avoid writing fake plugins to temporary directories during the test. type Exec interface { - ExecPlugin(pluginPath string, stdinData []byte, environ []string) ([]byte, error) + ExecPlugin(ctx context.Context, pluginPath string, stdinData []byte, environ []string) ([]byte, error) FindInPath(plugin string, paths []string) (string, error) Decode(jsonBytes []byte) (version.PluginInfo, error) } @@ -72,12 +73,12 @@ type Exec interface { // return "", fmt.Errorf("failed to find plugin %s in paths %v", plugin, paths) //} -func ExecPluginWithResult(pluginPath string, netconf []byte, args CNIArgs, exec Exec) (types.Result, error) { +func ExecPluginWithResult(ctx context.Context, pluginPath string, netconf []byte, args CNIArgs, exec Exec) (types.Result, error) { if exec == nil { exec = defaultExec } - stdoutBytes, err := exec.ExecPlugin(pluginPath, netconf, args.AsEnv()) + stdoutBytes, err := exec.ExecPlugin(ctx, pluginPath, netconf, args.AsEnv()) if err != nil { return nil, err } @@ -92,11 +93,11 @@ func ExecPluginWithResult(pluginPath string, netconf []byte, args CNIArgs, exec return version.NewResult(confVersion, stdoutBytes) } -func ExecPluginWithoutResult(pluginPath string, netconf []byte, args CNIArgs, exec Exec) error { +func ExecPluginWithoutResult(ctx context.Context, pluginPath string, netconf []byte, args CNIArgs, exec Exec) error { if exec == nil { exec = defaultExec } - _, err := exec.ExecPlugin(pluginPath, netconf, args.AsEnv()) + _, err := exec.ExecPlugin(ctx, pluginPath, netconf, args.AsEnv()) return err } @@ -104,7 +105,7 @@ func ExecPluginWithoutResult(pluginPath string, netconf []byte, args CNIArgs, ex // For recent-enough plugins, it uses the information returned by the VERSION // command. For older plugins which do not recognize that command, it reports // version 0.1.0 -func GetVersionInfo(pluginPath string, exec Exec) (version.PluginInfo, error) { +func GetVersionInfo(ctx context.Context, pluginPath string, exec Exec) (version.PluginInfo, error) { if exec == nil { exec = defaultExec } @@ -117,7 +118,7 @@ func GetVersionInfo(pluginPath string, exec Exec) (version.PluginInfo, error) { Path: "dummy", } stdin := []byte(fmt.Sprintf(`{"cniVersion":%q}`, version.Current())) - stdoutBytes, err := exec.ExecPlugin(pluginPath, stdin, args.AsEnv()) + stdoutBytes, err := exec.ExecPlugin(ctx, pluginPath, stdin, args.AsEnv()) if err != nil { if err.Error() == "unknown CNI_COMMAND: VERSION" { return version.PluginSupports("0.1.0"), nil diff --git a/vendor/github.com/containernetworking/cni/pkg/invoke/exec_test.go b/vendor/github.com/containernetworking/cni/pkg/invoke/exec_test.go index c126747f2..b9528e2b1 100644 --- a/vendor/github.com/containernetworking/cni/pkg/invoke/exec_test.go +++ b/vendor/github.com/containernetworking/cni/pkg/invoke/exec_test.go @@ -15,6 +15,7 @@ package invoke_test import ( + "context" "encoding/json" "errors" @@ -36,6 +37,7 @@ var _ = Describe("Executing a plugin, unit tests", func() { pluginPath string netconf []byte cniargs *fakes.CNIArgs + ctx context.Context ) BeforeEach(func() { @@ -56,11 +58,12 @@ var _ = Describe("Executing a plugin, unit tests", func() { netconf = []byte(`{ "some": "stdin", "cniVersion": "0.3.1" }`) cniargs = &fakes.CNIArgs{} cniargs.AsEnvCall.Returns.Env = []string{"SOME=ENV"} + ctx = context.TODO() }) Describe("returning a result", func() { It("unmarshals the result bytes into the Result type", func() { - r, err := invoke.ExecPluginWithResult(pluginPath, netconf, cniargs, pluginExec) + r, err := invoke.ExecPluginWithResult(ctx, pluginPath, netconf, cniargs, pluginExec) Expect(err).NotTo(HaveOccurred()) result, err := current.GetResult(r) @@ -70,7 +73,7 @@ var _ = Describe("Executing a plugin, unit tests", func() { }) It("passes its arguments through to the rawExec", func() { - invoke.ExecPluginWithResult(pluginPath, netconf, cniargs, pluginExec) + invoke.ExecPluginWithResult(ctx, pluginPath, netconf, cniargs, pluginExec) Expect(rawExec.ExecPluginCall.Received.PluginPath).To(Equal(pluginPath)) Expect(rawExec.ExecPluginCall.Received.StdinData).To(Equal(netconf)) Expect(rawExec.ExecPluginCall.Received.Environ).To(Equal([]string{"SOME=ENV"})) @@ -81,7 +84,7 @@ var _ = Describe("Executing a plugin, unit tests", func() { rawExec.ExecPluginCall.Returns.Error = errors.New("banana") }) It("returns the error", func() { - _, err := invoke.ExecPluginWithResult(pluginPath, netconf, cniargs, pluginExec) + _, err := invoke.ExecPluginWithResult(ctx, pluginPath, netconf, cniargs, pluginExec) Expect(err).To(MatchError("banana")) }) }) @@ -90,14 +93,14 @@ var _ = Describe("Executing a plugin, unit tests", func() { // pluginPath should not exist on-disk so we expect an error. // This test simply tests that the default exec handler // is run when the exec interface is nil. - _, err := invoke.ExecPluginWithResult(pluginPath, netconf, cniargs, nil) + _, err := invoke.ExecPluginWithResult(ctx, pluginPath, netconf, cniargs, nil) Expect(err).To(HaveOccurred()) }) }) Describe("without returning a result", func() { It("passes its arguments through to the rawExec", func() { - invoke.ExecPluginWithoutResult(pluginPath, netconf, cniargs, pluginExec) + invoke.ExecPluginWithoutResult(ctx, pluginPath, netconf, cniargs, pluginExec) Expect(rawExec.ExecPluginCall.Received.PluginPath).To(Equal(pluginPath)) Expect(rawExec.ExecPluginCall.Received.StdinData).To(Equal(netconf)) Expect(rawExec.ExecPluginCall.Received.Environ).To(Equal([]string{"SOME=ENV"})) @@ -108,7 +111,7 @@ var _ = Describe("Executing a plugin, unit tests", func() { rawExec.ExecPluginCall.Returns.Error = errors.New("banana") }) It("returns the error", func() { - err := invoke.ExecPluginWithoutResult(pluginPath, netconf, cniargs, pluginExec) + err := invoke.ExecPluginWithoutResult(ctx, pluginPath, netconf, cniargs, pluginExec) Expect(err).To(MatchError("banana")) }) }) @@ -117,7 +120,7 @@ var _ = Describe("Executing a plugin, unit tests", func() { // pluginPath should not exist on-disk so we expect an error. // This test simply tests that the default exec handler // is run when the exec interface is nil. - err := invoke.ExecPluginWithoutResult(pluginPath, netconf, cniargs, nil) + err := invoke.ExecPluginWithoutResult(ctx, pluginPath, netconf, cniargs, nil) Expect(err).To(HaveOccurred()) }) }) @@ -128,7 +131,7 @@ var _ = Describe("Executing a plugin, unit tests", func() { }) It("execs the plugin with the command VERSION", func() { - invoke.GetVersionInfo(pluginPath, pluginExec) + invoke.GetVersionInfo(ctx, pluginPath, pluginExec) Expect(rawExec.ExecPluginCall.Received.PluginPath).To(Equal(pluginPath)) Expect(rawExec.ExecPluginCall.Received.Environ).To(ContainElement("CNI_COMMAND=VERSION")) expectedStdin, _ := json.Marshal(map[string]string{"cniVersion": version.Current()}) @@ -136,7 +139,7 @@ var _ = Describe("Executing a plugin, unit tests", func() { }) It("decodes and returns the version info", func() { - versionInfo, err := invoke.GetVersionInfo(pluginPath, pluginExec) + versionInfo, err := invoke.GetVersionInfo(ctx, pluginPath, pluginExec) Expect(err).NotTo(HaveOccurred()) Expect(versionInfo.SupportedVersions()).To(Equal([]string{"0.42.0"})) Expect(versionDecoder.DecodeCall.Received.JSONBytes).To(MatchJSON(`{ "some": "version-info" }`)) @@ -147,7 +150,7 @@ var _ = Describe("Executing a plugin, unit tests", func() { rawExec.ExecPluginCall.Returns.Error = errors.New("banana") }) It("returns the error", func() { - _, err := invoke.GetVersionInfo(pluginPath, pluginExec) + _, err := invoke.GetVersionInfo(ctx, pluginPath, pluginExec) Expect(err).To(MatchError("banana")) }) }) @@ -158,13 +161,13 @@ var _ = Describe("Executing a plugin, unit tests", func() { }) It("interprets the error as a 0.1.0 version", func() { - versionInfo, err := invoke.GetVersionInfo(pluginPath, pluginExec) + versionInfo, err := invoke.GetVersionInfo(ctx, pluginPath, pluginExec) Expect(err).NotTo(HaveOccurred()) Expect(versionInfo.SupportedVersions()).To(ConsistOf("0.1.0")) }) It("sets dummy values for env vars required by very old plugins", func() { - invoke.GetVersionInfo(pluginPath, pluginExec) + invoke.GetVersionInfo(ctx, pluginPath, pluginExec) env := rawExec.ExecPluginCall.Received.Environ Expect(env).To(ContainElement("CNI_NETNS=dummy")) diff --git a/vendor/github.com/containernetworking/cni/pkg/invoke/fakes/raw_exec.go b/vendor/github.com/containernetworking/cni/pkg/invoke/fakes/raw_exec.go index 4a30ec1e7..a19236ceb 100644 --- a/vendor/github.com/containernetworking/cni/pkg/invoke/fakes/raw_exec.go +++ b/vendor/github.com/containernetworking/cni/pkg/invoke/fakes/raw_exec.go @@ -14,6 +14,8 @@ package fakes +import "context" + type RawExec struct { ExecPluginCall struct { Received struct { @@ -38,7 +40,7 @@ type RawExec struct { } } -func (e *RawExec) ExecPlugin(pluginPath string, stdinData []byte, environ []string) ([]byte, error) { +func (e *RawExec) ExecPlugin(ctx context.Context, pluginPath string, stdinData []byte, environ []string) ([]byte, error) { e.ExecPluginCall.Received.PluginPath = pluginPath e.ExecPluginCall.Received.StdinData = stdinData e.ExecPluginCall.Received.Environ = environ diff --git a/vendor/github.com/containernetworking/cni/pkg/invoke/get_version_integration_test.go b/vendor/github.com/containernetworking/cni/pkg/invoke/get_version_integration_test.go index bf552362a..b72104181 100644 --- a/vendor/github.com/containernetworking/cni/pkg/invoke/get_version_integration_test.go +++ b/vendor/github.com/containernetworking/cni/pkg/invoke/get_version_integration_test.go @@ -15,6 +15,7 @@ package invoke_test import ( + "context" "io/ioutil" "os" "path/filepath" @@ -51,7 +52,7 @@ var _ = Describe("GetVersion, integration tests", func() { DescribeTable("correctly reporting plugin versions", func(gitRef string, pluginSource string, expectedVersions version.PluginInfo) { Expect(testhelpers.BuildAt([]byte(pluginSource), gitRef, pluginPath)).To(Succeed()) - versionInfo, err := invoke.GetVersionInfo(pluginPath, nil) + versionInfo, err := invoke.GetVersionInfo(context.TODO(), pluginPath, nil) Expect(err).NotTo(HaveOccurred()) Expect(versionInfo.SupportedVersions()).To(ConsistOf(expectedVersions.SupportedVersions())) @@ -72,7 +73,7 @@ var _ = Describe("GetVersion, integration tests", func() { version.PluginSupports("0.2.0", "0.999.0"), ), - Entry("historical: before GET was introduced", + Entry("historical: before CHECK was introduced", git_ref_v031, plugin_source_v020_custom_versions, version.PluginSupports("0.2.0", "0.999.0"), ), @@ -80,14 +81,14 @@ var _ = Describe("GetVersion, integration tests", func() { // this entry tracks the current behavior. Before you change it, ensure // that its previous behavior is captured in the most recent "historical" entry Entry("current", - "HEAD", plugin_source_v040_get, + "HEAD", plugin_source_v040_check, version.PluginSupports("0.2.0", "0.4.0", "0.999.0"), ), ) }) -// A 0.4.0 plugin that supports GET -const plugin_source_v040_get = `package main +// A 0.4.0 plugin that supports CHECK +const plugin_source_v040_check = `package main import ( "github.com/containernetworking/cni/pkg/skel" diff --git a/vendor/github.com/containernetworking/cni/pkg/invoke/os_unix.go b/vendor/github.com/containernetworking/cni/pkg/invoke/os_unix.go index bab5737a9..9bcfb4553 100644 --- a/vendor/github.com/containernetworking/cni/pkg/invoke/os_unix.go +++ b/vendor/github.com/containernetworking/cni/pkg/invoke/os_unix.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -// +build darwin dragonfly freebsd linux netbsd opensbd solaris +// +build darwin dragonfly freebsd linux netbsd openbsd solaris package invoke 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 a598f09c2..ad8498ba2 100644 --- a/vendor/github.com/containernetworking/cni/pkg/invoke/raw_exec.go +++ b/vendor/github.com/containernetworking/cni/pkg/invoke/raw_exec.go @@ -16,6 +16,7 @@ package invoke import ( "bytes" + "context" "encoding/json" "fmt" "io" @@ -28,17 +29,13 @@ type RawExec struct { Stderr io.Writer } -func (e *RawExec) ExecPlugin(pluginPath string, stdinData []byte, environ []string) ([]byte, error) { +func (e *RawExec) ExecPlugin(ctx context.Context, pluginPath string, stdinData []byte, environ []string) ([]byte, error) { stdout := &bytes.Buffer{} - - c := exec.Cmd{ - Env: environ, - Path: pluginPath, - Args: []string{pluginPath}, - Stdin: bytes.NewBuffer(stdinData), - Stdout: stdout, - Stderr: e.Stderr, - } + c := exec.CommandContext(ctx, pluginPath) + c.Env = environ + c.Stdin = bytes.NewBuffer(stdinData) + c.Stdout = stdout + c.Stderr = e.Stderr if err := c.Run(); err != nil { return nil, pluginErr(err, stdout.Bytes()) } @@ -49,7 +46,9 @@ func (e *RawExec) ExecPlugin(pluginPath string, stdinData []byte, environ []stri 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/invoke/raw_exec_test.go b/vendor/github.com/containernetworking/cni/pkg/invoke/raw_exec_test.go index e372e2f79..dbc4ee830 100644 --- a/vendor/github.com/containernetworking/cni/pkg/invoke/raw_exec_test.go +++ b/vendor/github.com/containernetworking/cni/pkg/invoke/raw_exec_test.go @@ -16,6 +16,7 @@ package invoke_test import ( "bytes" + "context" "io/ioutil" "os" @@ -34,6 +35,7 @@ var _ = Describe("RawExec", func() { environ []string stdin []byte execer *invoke.RawExec + ctx context.Context ) const reportResult = `{ "some": "result" }` @@ -60,6 +62,7 @@ var _ = Describe("RawExec", func() { } stdin = []byte(`{"name": "raw-exec-test", "some":"stdin-json", "cniVersion": "0.3.1"}`) execer = &invoke.RawExec{} + ctx = context.TODO() }) AfterEach(func() { @@ -67,7 +70,7 @@ var _ = Describe("RawExec", func() { }) It("runs the plugin with the given stdin and environment", func() { - _, err := execer.ExecPlugin(pathToPlugin, stdin, environ) + _, err := execer.ExecPlugin(ctx, pathToPlugin, stdin, environ) Expect(err).NotTo(HaveOccurred()) debug, err := noop_debug.ReadDebug(debugFileName) @@ -78,7 +81,7 @@ var _ = Describe("RawExec", func() { }) It("returns the resulting stdout as bytes", func() { - resultBytes, err := execer.ExecPlugin(pathToPlugin, stdin, environ) + resultBytes, err := execer.ExecPlugin(ctx, pathToPlugin, stdin, environ) Expect(err).NotTo(HaveOccurred()) Expect(resultBytes).To(BeEquivalentTo(reportResult)) @@ -93,7 +96,7 @@ var _ = Describe("RawExec", func() { }) It("forwards any stderr bytes to the Stderr writer", func() { - _, err := execer.ExecPlugin(pathToPlugin, stdin, environ) + _, err := execer.ExecPlugin(ctx, pathToPlugin, stdin, environ) Expect(err).NotTo(HaveOccurred()) Expect(stderrBuffer.String()).To(Equal("some stderr message")) @@ -107,7 +110,7 @@ var _ = Describe("RawExec", func() { }) It("wraps and returns the error", func() { - _, err := execer.ExecPlugin(pathToPlugin, stdin, environ) + _, err := execer.ExecPlugin(ctx, pathToPlugin, stdin, environ) Expect(err).To(HaveOccurred()) Expect(err).To(MatchError("banana")) }) @@ -115,7 +118,7 @@ var _ = Describe("RawExec", func() { Context("when the system is unable to execute the plugin", func() { It("returns the error", func() { - _, err := execer.ExecPlugin("/tmp/some/invalid/plugin/path", stdin, environ) + _, err := execer.ExecPlugin(ctx, "/tmp/some/invalid/plugin/path", stdin, environ) Expect(err).To(HaveOccurred()) Expect(err).To(MatchError(ContainSubstring("/tmp/some/invalid/plugin/path"))) }) diff --git a/vendor/github.com/containernetworking/cni/pkg/skel/skel.go b/vendor/github.com/containernetworking/cni/pkg/skel/skel.go index e565c85d0..af56b8a1c 100644 --- a/vendor/github.com/containernetworking/cni/pkg/skel/skel.go +++ b/vendor/github.com/containernetworking/cni/pkg/skel/skel.go @@ -24,6 +24,7 @@ import ( "io/ioutil" "log" "os" + "strings" "github.com/containernetworking/cni/pkg/types" "github.com/containernetworking/cni/pkg/version" @@ -52,6 +53,15 @@ type dispatcher struct { type reqForCmdEntry map[string]bool +// internal only error to indicate lack of required environment variables +type missingEnvError struct { + msg string +} + +func (e missingEnvError) Error() string { + return e.msg +} + func (t *dispatcher) getCmdArgsFromEnv() (string, *CmdArgs, error) { var cmd, contID, netns, ifName, args, path string @@ -64,71 +74,71 @@ func (t *dispatcher) getCmdArgsFromEnv() (string, *CmdArgs, error) { "CNI_COMMAND", &cmd, reqForCmdEntry{ - "ADD": true, - "GET": true, - "DEL": true, + "ADD": true, + "CHECK": true, + "DEL": true, }, }, { "CNI_CONTAINERID", &contID, reqForCmdEntry{ - "ADD": true, - "GET": true, - "DEL": true, + "ADD": true, + "CHECK": true, + "DEL": true, }, }, { "CNI_NETNS", &netns, reqForCmdEntry{ - "ADD": true, - "GET": true, - "DEL": false, + "ADD": true, + "CHECK": true, + "DEL": false, }, }, { "CNI_IFNAME", &ifName, reqForCmdEntry{ - "ADD": true, - "GET": true, - "DEL": true, + "ADD": true, + "CHECK": true, + "DEL": true, }, }, { "CNI_ARGS", &args, reqForCmdEntry{ - "ADD": false, - "GET": false, - "DEL": false, + "ADD": false, + "CHECK": false, + "DEL": false, }, }, { "CNI_PATH", &path, reqForCmdEntry{ - "ADD": true, - "GET": true, - "DEL": true, + "ADD": true, + "CHECK": true, + "DEL": true, }, }, } - argsMissing := false + argsMissing := make([]string, 0) for _, v := range vars { *v.val = t.Getenv(v.name) if *v.val == "" { if v.reqForCmd[cmd] || v.name == "CNI_COMMAND" { - fmt.Fprintf(t.Stderr, "%v env variable missing\n", v.name) - argsMissing = true + argsMissing = append(argsMissing, v.name) } } } - if argsMissing { - return "", nil, fmt.Errorf("required env variables missing") + if len(argsMissing) > 0 { + joined := strings.Join(argsMissing, ",") + return "", nil, missingEnvError{fmt.Sprintf("required env variables [%s] missing", joined)} } if cmd == "VERSION" { @@ -188,12 +198,13 @@ func validateConfig(jsonBytes []byte) error { return nil } -func (t *dispatcher) pluginMain(cmdAdd, cmdGet, cmdDel func(_ *CmdArgs) error, versionInfo version.PluginInfo, about string) *types.Error { +func (t *dispatcher) pluginMain(cmdAdd, cmdCheck, cmdDel func(_ *CmdArgs) error, versionInfo version.PluginInfo, about string) *types.Error { cmd, cmdArgs, err := t.getCmdArgsFromEnv() if err != nil { // Print the about string to stderr when no command is set - if t.Getenv("CNI_COMMAND") == "" && about != "" { + if _, ok := err.(missingEnvError); ok && t.Getenv("CNI_COMMAND") == "" && about != "" { fmt.Fprintln(t.Stderr, about) + return nil } return createTypedError(err.Error()) } @@ -208,7 +219,7 @@ func (t *dispatcher) pluginMain(cmdAdd, cmdGet, cmdDel func(_ *CmdArgs) error, v switch cmd { case "ADD": err = t.checkVersionAndCall(cmdArgs, versionInfo, cmdAdd) - case "GET": + case "CHECK": configVersion, err := t.ConfVersionDecoder.Decode(cmdArgs.StdinData) if err != nil { return createTypedError(err.Error()) @@ -218,7 +229,7 @@ func (t *dispatcher) pluginMain(cmdAdd, cmdGet, cmdDel func(_ *CmdArgs) error, v } else if !gtet { return &types.Error{ Code: types.ErrIncompatibleCNIVersion, - Msg: "config version does not allow GET", + Msg: "config version does not allow CHECK", } } for _, pluginVersion := range versionInfo.SupportedVersions() { @@ -226,7 +237,7 @@ func (t *dispatcher) pluginMain(cmdAdd, cmdGet, cmdDel func(_ *CmdArgs) error, v if err != nil { return createTypedError(err.Error()) } else if gtet { - if err := t.checkVersionAndCall(cmdArgs, versionInfo, cmdGet); err != nil { + if err := t.checkVersionAndCall(cmdArgs, versionInfo, cmdCheck); err != nil { return createTypedError(err.Error()) } return nil @@ -234,7 +245,7 @@ func (t *dispatcher) pluginMain(cmdAdd, cmdGet, cmdDel func(_ *CmdArgs) error, v } return &types.Error{ Code: types.ErrIncompatibleCNIVersion, - Msg: "plugin version does not allow GET", + Msg: "plugin version does not allow CHECK", } case "DEL": err = t.checkVersionAndCall(cmdArgs, versionInfo, cmdDel) @@ -255,7 +266,7 @@ func (t *dispatcher) pluginMain(cmdAdd, cmdGet, cmdDel func(_ *CmdArgs) error, v } // PluginMainWithError is the core "main" for a plugin. It accepts -// callback functions for add, get, and del CNI commands and returns an error. +// callback functions for add, check, and del CNI commands and returns an error. // // The caller must also specify what CNI spec versions the plugin supports. // @@ -266,13 +277,13 @@ func (t *dispatcher) pluginMain(cmdAdd, cmdGet, cmdDel func(_ *CmdArgs) error, v // // To let this package automatically handle errors and call os.Exit(1) for you, // use PluginMain() instead. -func PluginMainWithError(cmdAdd, cmdGet, cmdDel func(_ *CmdArgs) error, versionInfo version.PluginInfo, about string) *types.Error { +func PluginMainWithError(cmdAdd, cmdCheck, cmdDel func(_ *CmdArgs) error, versionInfo version.PluginInfo, about string) *types.Error { return (&dispatcher{ Getenv: os.Getenv, Stdin: os.Stdin, Stdout: os.Stdout, Stderr: os.Stderr, - }).pluginMain(cmdAdd, cmdGet, cmdDel, versionInfo, about) + }).pluginMain(cmdAdd, cmdCheck, cmdDel, versionInfo, about) } // PluginMain is the core "main" for a plugin which includes automatic error handling. @@ -280,14 +291,14 @@ func PluginMainWithError(cmdAdd, cmdGet, cmdDel func(_ *CmdArgs) error, versionI // The caller must also specify what CNI spec versions the plugin supports. // // The caller can specify an "about" string, which is printed on stderr -// when no CNI_COMMAND is specified. The reccomended output is "CNI plugin v" +// when no CNI_COMMAND is specified. The recommended output is "CNI plugin v" // -// When an error occurs in either cmdAdd, cmdGet, or cmdDel, PluginMain will print the error +// When an error occurs in either cmdAdd, cmdCheck, or cmdDel, PluginMain will print the error // as JSON to stdout and call os.Exit(1). // // To have more control over error handling, use PluginMainWithError() instead. -func PluginMain(cmdAdd, cmdGet, cmdDel func(_ *CmdArgs) error, versionInfo version.PluginInfo, about string) { - if e := PluginMainWithError(cmdAdd, cmdGet, cmdDel, versionInfo, about); e != nil { +func PluginMain(cmdAdd, cmdCheck, cmdDel func(_ *CmdArgs) error, versionInfo version.PluginInfo, about string) { + if e := PluginMainWithError(cmdAdd, cmdCheck, cmdDel, versionInfo, about); e != nil { if err := e.Print(); err != nil { log.Print("Error writing error JSON to stdout: ", err) } diff --git a/vendor/github.com/containernetworking/cni/pkg/skel/skel_test.go b/vendor/github.com/containernetworking/cni/pkg/skel/skel_test.go index c261f2247..95e1b309e 100644 --- a/vendor/github.com/containernetworking/cni/pkg/skel/skel_test.go +++ b/vendor/github.com/containernetworking/cni/pkg/skel/skel_test.go @@ -47,13 +47,13 @@ func (c *fakeCmd) Func(args *CmdArgs) error { var _ = Describe("dispatching to the correct callback", func() { var ( - environment map[string]string - stdinData string - stdout, stderr *bytes.Buffer - cmdAdd, cmdGet, cmdDel *fakeCmd - dispatch *dispatcher - expectedCmdArgs *CmdArgs - versionInfo version.PluginInfo + environment map[string]string + stdinData string + stdout, stderr *bytes.Buffer + cmdAdd, cmdCheck, cmdDel *fakeCmd + dispatch *dispatcher + expectedCmdArgs *CmdArgs + versionInfo version.PluginInfo ) BeforeEach(func() { @@ -77,7 +77,7 @@ var _ = Describe("dispatching to the correct callback", func() { Stderr: stderr, } cmdAdd = &fakeCmd{} - cmdGet = &fakeCmd{} + cmdCheck = &fakeCmd{} cmdDel = &fakeCmd{} expectedCmdArgs = &CmdArgs{ ContainerID: "some-container-id", @@ -92,13 +92,12 @@ var _ = Describe("dispatching to the correct callback", func() { var envVarChecker = func(envVar string, isRequired bool) { delete(environment, envVar) - err := dispatch.pluginMain(cmdAdd.Func, cmdGet.Func, cmdDel.Func, versionInfo, "") + err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "") if isRequired { Expect(err).To(Equal(&types.Error{ Code: 100, - Msg: "required env variables missing", + Msg: "required env variables [" + envVar + "] missing", })) - Expect(stderr.String()).To(ContainSubstring(envVar + " env variable missing\n")) } else { Expect(err).NotTo(HaveOccurred()) } @@ -106,20 +105,20 @@ var _ = Describe("dispatching to the correct callback", func() { Context("when the CNI_COMMAND is ADD", func() { It("extracts env vars and stdin data and calls cmdAdd", func() { - err := dispatch.pluginMain(cmdAdd.Func, cmdGet.Func, cmdDel.Func, versionInfo, "") + err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "") Expect(err).NotTo(HaveOccurred()) Expect(cmdAdd.CallCount).To(Equal(1)) - Expect(cmdGet.CallCount).To(Equal(0)) + Expect(cmdCheck.CallCount).To(Equal(0)) Expect(cmdDel.CallCount).To(Equal(0)) Expect(cmdAdd.Received.CmdArgs).To(Equal(expectedCmdArgs)) }) - It("does not call cmdGet or cmdDel", func() { - err := dispatch.pluginMain(cmdAdd.Func, cmdGet.Func, cmdDel.Func, versionInfo, "") + It("does not call cmdCheck or cmdDel", func() { + err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "") Expect(err).NotTo(HaveOccurred()) - Expect(cmdGet.CallCount).To(Equal(0)) + Expect(cmdCheck.CallCount).To(Equal(0)) Expect(cmdDel.CallCount).To(Equal(0)) }) @@ -140,13 +139,13 @@ var _ = Describe("dispatching to the correct callback", func() { }) It("reports that all of them are missing, not just the first", func() { - Expect(dispatch.pluginMain(cmdAdd.Func, cmdGet.Func, cmdDel.Func, versionInfo, "")).NotTo(Succeed()) - - log := stderr.String() - Expect(log).To(ContainSubstring("CNI_NETNS env variable missing\n")) - Expect(log).To(ContainSubstring("CNI_IFNAME env variable missing\n")) - Expect(log).To(ContainSubstring("CNI_PATH env variable missing\n")) + err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "") + Expect(err).To(HaveOccurred()) + Expect(err).To(Equal(&types.Error{ + Code: 100, + Msg: "required env variables [CNI_NETNS,CNI_IFNAME,CNI_PATH] missing", + })) }) }) @@ -163,7 +162,7 @@ var _ = Describe("dispatching to the correct callback", func() { It("infers the config is 0.1.0 and calls the cmdAdd callback", func() { - err := dispatch.pluginMain(cmdAdd.Func, cmdGet.Func, cmdDel.Func, versionInfo, "") + err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "") Expect(err).NotTo(HaveOccurred()) Expect(cmdAdd.CallCount).To(Equal(1)) @@ -177,39 +176,39 @@ var _ = Describe("dispatching to the correct callback", func() { }) It("immediately returns a useful error", func() { - err := dispatch.pluginMain(cmdAdd.Func, cmdGet.Func, cmdDel.Func, versionInfo, "") + err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "") Expect(err.Code).To(Equal(types.ErrIncompatibleCNIVersion)) // see https://github.com/containernetworking/cni/blob/master/SPEC.md#well-known-error-codes Expect(err.Msg).To(Equal("incompatible CNI versions")) Expect(err.Details).To(Equal(`config is "0.1.0", plugin supports ["4.3.2"]`)) }) It("does not call either callback", func() { - dispatch.pluginMain(cmdAdd.Func, cmdGet.Func, cmdDel.Func, versionInfo, "") + dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "") Expect(cmdAdd.CallCount).To(Equal(0)) - Expect(cmdGet.CallCount).To(Equal(0)) + Expect(cmdCheck.CallCount).To(Equal(0)) Expect(cmdDel.CallCount).To(Equal(0)) }) }) }) }) - Context("when the CNI_COMMAND is GET", func() { + Context("when the CNI_COMMAND is CHECK", func() { BeforeEach(func() { - environment["CNI_COMMAND"] = "GET" + environment["CNI_COMMAND"] = "CHECK" }) - It("extracts env vars and stdin data and calls cmdGet", func() { - err := dispatch.pluginMain(cmdAdd.Func, cmdGet.Func, cmdDel.Func, versionInfo, "") + It("extracts env vars and stdin data and calls cmdCheck", func() { + err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "") Expect(err).NotTo(HaveOccurred()) Expect(cmdAdd.CallCount).To(Equal(0)) - Expect(cmdGet.CallCount).To(Equal(1)) + Expect(cmdCheck.CallCount).To(Equal(1)) Expect(cmdDel.CallCount).To(Equal(0)) - Expect(cmdGet.Received.CmdArgs).To(Equal(expectedCmdArgs)) + Expect(cmdCheck.Received.CmdArgs).To(Equal(expectedCmdArgs)) }) It("does not call cmdAdd or cmdDel", func() { - err := dispatch.pluginMain(cmdAdd.Func, cmdGet.Func, cmdDel.Func, versionInfo, "") + err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "") Expect(err).NotTo(HaveOccurred()) Expect(cmdAdd.CallCount).To(Equal(0)) @@ -233,23 +232,24 @@ var _ = Describe("dispatching to the correct callback", func() { }) It("reports that all of them are missing, not just the first", func() { - Expect(dispatch.pluginMain(cmdAdd.Func, cmdGet.Func, cmdDel.Func, versionInfo, "")).NotTo(Succeed()) - log := stderr.String() - Expect(log).To(ContainSubstring("CNI_NETNS env variable missing\n")) - Expect(log).To(ContainSubstring("CNI_IFNAME env variable missing\n")) - Expect(log).To(ContainSubstring("CNI_PATH env variable missing\n")) + err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "") + Expect(err).To(HaveOccurred()) + Expect(err).To(Equal(&types.Error{ + Code: 100, + Msg: "required env variables [CNI_NETNS,CNI_IFNAME,CNI_PATH] missing", + })) }) }) Context("when cniVersion is less than 0.4.0", func() { It("immediately returns a useful error", func() { dispatch.Stdin = strings.NewReader(`{ "name": "skel-test", "cniVersion": "0.3.0", "some": "config" }`) - err := dispatch.pluginMain(cmdAdd.Func, cmdGet.Func, cmdDel.Func, versionInfo, "") + err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "") Expect(err.Code).To(Equal(types.ErrIncompatibleCNIVersion)) // see https://github.com/containernetworking/cni/blob/master/SPEC.md#well-known-error-codes - Expect(err.Msg).To(Equal("config version does not allow GET")) + Expect(err.Msg).To(Equal("config version does not allow CHECK")) Expect(cmdAdd.CallCount).To(Equal(0)) - Expect(cmdGet.CallCount).To(Equal(0)) + Expect(cmdCheck.CallCount).To(Equal(0)) Expect(cmdDel.CallCount).To(Equal(0)) }) }) @@ -258,11 +258,11 @@ var _ = Describe("dispatching to the correct callback", func() { It("immediately returns a useful error", func() { dispatch.Stdin = strings.NewReader(`{ "name": "skel-test", "cniVersion": "0.4.0", "some": "config" }`) versionInfo = version.PluginSupports("0.1.0", "0.2.0", "0.3.0") - err := dispatch.pluginMain(cmdAdd.Func, cmdGet.Func, cmdDel.Func, versionInfo, "") + err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "") Expect(err.Code).To(Equal(types.ErrIncompatibleCNIVersion)) // see https://github.com/containernetworking/cni/blob/master/SPEC.md#well-known-error-codes - Expect(err.Msg).To(Equal("plugin version does not allow GET")) + Expect(err.Msg).To(Equal("plugin version does not allow CHECK")) Expect(cmdAdd.CallCount).To(Equal(0)) - Expect(cmdGet.CallCount).To(Equal(0)) + Expect(cmdCheck.CallCount).To(Equal(0)) Expect(cmdDel.CallCount).To(Equal(0)) }) }) @@ -271,10 +271,10 @@ var _ = Describe("dispatching to the correct callback", func() { It("immediately returns a useful error", func() { dispatch.Stdin = strings.NewReader(`{ "cniVersion": "adsfsadf", "some": "config" }`) versionInfo = version.PluginSupports("0.1.0", "0.2.0", "0.3.0") - err := dispatch.pluginMain(cmdAdd.Func, cmdGet.Func, cmdDel.Func, versionInfo, "") + err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "") Expect(err.Code).To(Equal(uint(100))) Expect(cmdAdd.CallCount).To(Equal(0)) - Expect(cmdGet.CallCount).To(Equal(0)) + Expect(cmdCheck.CallCount).To(Equal(0)) Expect(cmdDel.CallCount).To(Equal(0)) }) }) @@ -283,10 +283,10 @@ var _ = Describe("dispatching to the correct callback", func() { It("immediately returns a useful error", func() { dispatch.Stdin = strings.NewReader(`{ "cniVersion": "0.4.0", "some": "config" }`) versionInfo = version.PluginSupports("0.1.0", "0.2.0", "adsfasdf") - err := dispatch.pluginMain(cmdAdd.Func, cmdGet.Func, cmdDel.Func, versionInfo, "") + err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "") Expect(err.Code).To(Equal(uint(100))) Expect(cmdAdd.CallCount).To(Equal(0)) - Expect(cmdGet.CallCount).To(Equal(0)) + Expect(cmdCheck.CallCount).To(Equal(0)) Expect(cmdDel.CallCount).To(Equal(0)) }) }) @@ -298,7 +298,7 @@ var _ = Describe("dispatching to the correct callback", func() { }) It("calls cmdDel with the env vars and stdin data", func() { - err := dispatch.pluginMain(cmdAdd.Func, cmdGet.Func, cmdDel.Func, versionInfo, "") + err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "") Expect(err).NotTo(HaveOccurred()) Expect(cmdDel.CallCount).To(Equal(1)) @@ -306,7 +306,7 @@ var _ = Describe("dispatching to the correct callback", func() { }) It("does not call cmdAdd", func() { - err := dispatch.pluginMain(cmdAdd.Func, cmdGet.Func, cmdDel.Func, versionInfo, "") + err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "") Expect(err).NotTo(HaveOccurred()) Expect(cmdAdd.CallCount).To(Equal(0)) @@ -328,7 +328,7 @@ var _ = Describe("dispatching to the correct callback", func() { }) It("prints the version to stdout", func() { - err := dispatch.pluginMain(cmdAdd.Func, cmdGet.Func, cmdDel.Func, versionInfo, "") + err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "") Expect(err).NotTo(HaveOccurred()) Expect(stdout).To(MatchJSON(fmt.Sprintf(`{ @@ -338,7 +338,7 @@ var _ = Describe("dispatching to the correct callback", func() { }) It("does not call cmdAdd or cmdDel", func() { - err := dispatch.pluginMain(cmdAdd.Func, cmdGet.Func, cmdDel.Func, versionInfo, "") + err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "") Expect(err).NotTo(HaveOccurred()) Expect(cmdAdd.CallCount).To(Equal(0)) @@ -358,7 +358,7 @@ var _ = Describe("dispatching to the correct callback", func() { r := &BadReader{} dispatch.Stdin = r - err := dispatch.pluginMain(cmdAdd.Func, cmdGet.Func, cmdDel.Func, versionInfo, "") + err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "") Expect(err).NotTo(HaveOccurred()) Expect(r.ReadCount).To(Equal(0)) @@ -375,14 +375,14 @@ var _ = Describe("dispatching to the correct callback", func() { }) It("does not call any cmd callback", func() { - dispatch.pluginMain(cmdAdd.Func, cmdGet.Func, cmdDel.Func, versionInfo, "") + dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "") Expect(cmdAdd.CallCount).To(Equal(0)) Expect(cmdDel.CallCount).To(Equal(0)) }) It("returns an error", func() { - err := dispatch.pluginMain(cmdAdd.Func, cmdGet.Func, cmdDel.Func, versionInfo, "") + err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "") Expect(err).To(Equal(&types.Error{ Code: 100, @@ -392,25 +392,51 @@ var _ = Describe("dispatching to the correct callback", func() { It("prints the about string when the command is blank", func() { environment["CNI_COMMAND"] = "" - dispatch.pluginMain(cmdAdd.Func, cmdGet.Func, cmdDel.Func, versionInfo, "test framework v42") + dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "test framework v42") Expect(stderr.String()).To(ContainSubstring("test framework v42")) }) }) + Context("when the CNI_COMMAND is missing", func() { + It("prints the about string to stderr", func() { + environment = map[string]string{} + err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "AWESOME PLUGIN") + Expect(err).NotTo(HaveOccurred()) + + Expect(cmdAdd.CallCount).To(Equal(0)) + Expect(cmdDel.CallCount).To(Equal(0)) + log := stderr.String() + Expect(log).To(Equal("AWESOME PLUGIN\n")) + }) + + It("fails if there is no about string", func() { + environment = map[string]string{} + err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "") + Expect(err).To(HaveOccurred()) + + Expect(cmdAdd.CallCount).To(Equal(0)) + Expect(cmdDel.CallCount).To(Equal(0)) + Expect(err).To(Equal(&types.Error{ + Code: 100, + Msg: "required env variables [CNI_COMMAND] missing", + })) + }) + }) + Context("when stdin cannot be read", func() { BeforeEach(func() { dispatch.Stdin = &BadReader{} }) It("does not call any cmd callback", func() { - dispatch.pluginMain(cmdAdd.Func, cmdGet.Func, cmdDel.Func, versionInfo, "") + dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "") Expect(cmdAdd.CallCount).To(Equal(0)) Expect(cmdDel.CallCount).To(Equal(0)) }) It("wraps and returns the error", func() { - err := dispatch.pluginMain(cmdAdd.Func, cmdGet.Func, cmdDel.Func, versionInfo, "") + err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "") Expect(err).To(Equal(&types.Error{ Code: 100, @@ -429,7 +455,7 @@ var _ = Describe("dispatching to the correct callback", func() { }) It("returns the error as-is", func() { - err := dispatch.pluginMain(cmdAdd.Func, cmdGet.Func, cmdDel.Func, versionInfo, "") + err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "") Expect(err).To(Equal(&types.Error{ Code: 1234, @@ -444,7 +470,7 @@ var _ = Describe("dispatching to the correct callback", func() { }) It("wraps and returns the error", func() { - err := dispatch.pluginMain(cmdAdd.Func, cmdGet.Func, cmdDel.Func, versionInfo, "") + err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "") Expect(err).To(Equal(&types.Error{ Code: 100, diff --git a/vendor/github.com/containernetworking/cni/pkg/types/020/types.go b/vendor/github.com/containernetworking/cni/pkg/types/020/types.go index 2833aba78..53256167f 100644 --- a/vendor/github.com/containernetworking/cni/pkg/types/020/types.go +++ b/vendor/github.com/containernetworking/cni/pkg/types/020/types.go @@ -17,6 +17,7 @@ package types020 import ( "encoding/json" "fmt" + "io" "net" "os" @@ -73,11 +74,15 @@ func (r *Result) GetAsVersion(version string) (types.Result, error) { } func (r *Result) Print() error { + return r.PrintTo(os.Stdout) +} + +func (r *Result) PrintTo(writer io.Writer) error { data, err := json.MarshalIndent(r, "", " ") if err != nil { return err } - _, err = os.Stdout.Write(data) + _, err = writer.Write(data) return err } diff --git a/vendor/github.com/containernetworking/cni/pkg/types/current/types.go b/vendor/github.com/containernetworking/cni/pkg/types/current/types.go index 92980c1a7..7267a2e6d 100644 --- a/vendor/github.com/containernetworking/cni/pkg/types/current/types.go +++ b/vendor/github.com/containernetworking/cni/pkg/types/current/types.go @@ -17,6 +17,7 @@ package current import ( "encoding/json" "fmt" + "io" "net" "os" @@ -75,13 +76,9 @@ func convertFrom020(result types.Result) (*Result, error) { Gateway: oldResult.IP4.Gateway, }) for _, route := range oldResult.IP4.Routes { - gw := route.GW - if gw == nil { - gw = oldResult.IP4.Gateway - } newResult.Routes = append(newResult.Routes, &types.Route{ Dst: route.Dst, - GW: gw, + GW: route.GW, }) } } @@ -93,21 +90,13 @@ func convertFrom020(result types.Result) (*Result, error) { Gateway: oldResult.IP6.Gateway, }) for _, route := range oldResult.IP6.Routes { - gw := route.GW - if gw == nil { - gw = oldResult.IP6.Gateway - } newResult.Routes = append(newResult.Routes, &types.Route{ Dst: route.Dst, - GW: gw, + GW: route.GW, }) } } - if len(newResult.IPs) == 0 { - return nil, fmt.Errorf("cannot convert: no valid IP addresses") - } - return newResult, nil } @@ -206,11 +195,15 @@ func (r *Result) GetAsVersion(version string) (types.Result, error) { } func (r *Result) Print() error { + return r.PrintTo(os.Stdout) +} + +func (r *Result) PrintTo(writer io.Writer) error { data, err := json.MarshalIndent(r, "", " ") if err != nil { return err } - _, err = os.Stdout.Write(data) + _, err = writer.Write(data) return err } diff --git a/vendor/github.com/containernetworking/cni/pkg/types/current/types_test.go b/vendor/github.com/containernetworking/cni/pkg/types/current/types_test.go index 29bd22fab..9e24d6579 100644 --- a/vendor/github.com/containernetworking/cni/pkg/types/current/types_test.go +++ b/vendor/github.com/containernetworking/cni/pkg/types/current/types_test.go @@ -16,6 +16,7 @@ package current_test import ( "encoding/json" + "github.com/containernetworking/cni/pkg/types/020" "io/ioutil" "net" "os" @@ -214,6 +215,126 @@ var _ = Describe("Current types operations", func() { }`)) }) + It("correctly round-trips a 0.2.0 Result with route gateways", func() { + ipv4, err := types.ParseCIDR("1.2.3.30/24") + Expect(err).NotTo(HaveOccurred()) + Expect(ipv4).NotTo(BeNil()) + + routegwv4, routev4, err := net.ParseCIDR("15.5.6.8/24") + Expect(err).NotTo(HaveOccurred()) + Expect(routev4).NotTo(BeNil()) + Expect(routegwv4).NotTo(BeNil()) + + ipv6, err := types.ParseCIDR("abcd:1234:ffff::cdde/64") + Expect(err).NotTo(HaveOccurred()) + Expect(ipv6).NotTo(BeNil()) + + routegwv6, routev6, err := net.ParseCIDR("1111:dddd::aaaa/80") + Expect(err).NotTo(HaveOccurred()) + Expect(routev6).NotTo(BeNil()) + Expect(routegwv6).NotTo(BeNil()) + + // Set every field of the struct to ensure source compatibility + res := &types020.Result{ + CNIVersion: types020.ImplementedSpecVersion, + IP4: &types020.IPConfig{ + IP: *ipv4, + Gateway: net.ParseIP("1.2.3.1"), + Routes: []types.Route{ + {Dst: *routev4, GW: routegwv4}, + }, + }, + IP6: &types020.IPConfig{ + IP: *ipv6, + Gateway: net.ParseIP("abcd:1234:ffff::1"), + Routes: []types.Route{ + {Dst: *routev6, GW: routegwv6}, + }, + }, + DNS: types.DNS{ + Nameservers: []string{"1.2.3.4", "1::cafe"}, + Domain: "acompany.com", + Search: []string{"somedomain.com", "otherdomain.net"}, + Options: []string{"foo", "bar"}, + }, + } + + Expect(res.String()).To(Equal("IP4:{IP:{IP:1.2.3.30 Mask:ffffff00} Gateway:1.2.3.1 Routes:[{Dst:{IP:15.5.6.0 Mask:ffffff00} GW:15.5.6.8}]}, IP6:{IP:{IP:abcd:1234:ffff::cdde Mask:ffffffffffffffff0000000000000000} Gateway:abcd:1234:ffff::1 Routes:[{Dst:{IP:1111:dddd:: Mask:ffffffffffffffffffff000000000000} GW:1111:dddd::aaaa}]}, DNS:{Nameservers:[1.2.3.4 1::cafe] Domain:acompany.com Search:[somedomain.com otherdomain.net] Options:[foo bar]}")) + + // Convert to current + newRes, err := current.NewResultFromResult(res) + Expect(err).NotTo(HaveOccurred()) + // Convert back to 0.2.0 + oldRes, err := newRes.GetAsVersion("0.2.0") + Expect(err).NotTo(HaveOccurred()) + + // Match JSON so we can figure out what's wrong if something fails the test + origJson, err := json.Marshal(res) + Expect(err).NotTo(HaveOccurred()) + oldJson, err := json.Marshal(oldRes) + Expect(err).NotTo(HaveOccurred()) + Expect(oldJson).To(MatchJSON(origJson)) + }) + + It("correctly round-trips a 0.2.0 Result without route gateways", func() { + ipv4, err := types.ParseCIDR("1.2.3.30/24") + Expect(err).NotTo(HaveOccurred()) + Expect(ipv4).NotTo(BeNil()) + + _, routev4, err := net.ParseCIDR("15.5.6.0/24") + Expect(err).NotTo(HaveOccurred()) + Expect(routev4).NotTo(BeNil()) + + ipv6, err := types.ParseCIDR("abcd:1234:ffff::cdde/64") + Expect(err).NotTo(HaveOccurred()) + Expect(ipv6).NotTo(BeNil()) + + _, routev6, err := net.ParseCIDR("1111:dddd::aaaa/80") + Expect(err).NotTo(HaveOccurred()) + Expect(routev6).NotTo(BeNil()) + + // Set every field of the struct to ensure source compatibility + res := &types020.Result{ + CNIVersion: types020.ImplementedSpecVersion, + IP4: &types020.IPConfig{ + IP: *ipv4, + Gateway: net.ParseIP("1.2.3.1"), + Routes: []types.Route{ + {Dst: *routev4}, + }, + }, + IP6: &types020.IPConfig{ + IP: *ipv6, + Gateway: net.ParseIP("abcd:1234:ffff::1"), + Routes: []types.Route{ + {Dst: *routev6}, + }, + }, + DNS: types.DNS{ + Nameservers: []string{"1.2.3.4", "1::cafe"}, + Domain: "acompany.com", + Search: []string{"somedomain.com", "otherdomain.net"}, + Options: []string{"foo", "bar"}, + }, + } + + Expect(res.String()).To(Equal("IP4:{IP:{IP:1.2.3.30 Mask:ffffff00} Gateway:1.2.3.1 Routes:[{Dst:{IP:15.5.6.0 Mask:ffffff00} GW:}]}, IP6:{IP:{IP:abcd:1234:ffff::cdde Mask:ffffffffffffffff0000000000000000} Gateway:abcd:1234:ffff::1 Routes:[{Dst:{IP:1111:dddd:: Mask:ffffffffffffffffffff000000000000} GW:}]}, DNS:{Nameservers:[1.2.3.4 1::cafe] Domain:acompany.com Search:[somedomain.com otherdomain.net] Options:[foo bar]}")) + + // Convert to current + newRes, err := current.NewResultFromResult(res) + Expect(err).NotTo(HaveOccurred()) + // Convert back to 0.2.0 + oldRes, err := newRes.GetAsVersion("0.2.0") + Expect(err).NotTo(HaveOccurred()) + + // Match JSON so we can figure out what's wrong if something fails the test + origJson, err := json.Marshal(res) + Expect(err).NotTo(HaveOccurred()) + oldJson, err := json.Marshal(oldRes) + Expect(err).NotTo(HaveOccurred()) + Expect(oldJson).To(MatchJSON(origJson)) + }) + It("correctly marshals and unmarshals interface index 0", func() { ipc := ¤t.IPConfig{ Version: "4", @@ -237,7 +358,7 @@ var _ = Describe("Current types operations", func() { Expect(recovered).To(Equal(ipc)) }) - Context("when unmarshaling json fails", func() { + Context("when unmarshalling json fails", func() { It("returns an error", func() { recovered := ¤t.IPConfig{} err := json.Unmarshal([]byte(`{"address": 5}`), &recovered) diff --git a/vendor/github.com/containernetworking/cni/pkg/types/types.go b/vendor/github.com/containernetworking/cni/pkg/types/types.go index 4684a3207..d0d11006a 100644 --- a/vendor/github.com/containernetworking/cni/pkg/types/types.go +++ b/vendor/github.com/containernetworking/cni/pkg/types/types.go @@ -18,6 +18,7 @@ import ( "encoding/json" "errors" "fmt" + "io" "net" "os" ) @@ -65,6 +66,9 @@ type NetConf struct { Capabilities map[string]bool `json:"capabilities,omitempty"` IPAM IPAM `json:"ipam,omitempty"` DNS DNS `json:"dns"` + + RawPrevResult map[string]interface{} `json:"prevResult,omitempty"` + PrevResult Result `json:"-"` } type IPAM struct { @@ -75,15 +79,16 @@ type IPAM struct { type NetConfList struct { CNIVersion string `json:"cniVersion,omitempty"` - Name string `json:"name,omitempty"` - Plugins []*NetConf `json:"plugins,omitempty"` + Name string `json:"name,omitempty"` + DisableCheck bool `json:"disableCheck,omitempty"` + Plugins []*NetConf `json:"plugins,omitempty"` } type ResultFactoryFunc func([]byte) (Result, error) // Result is an interface that provides the result of plugin execution type Result interface { - // The highest CNI specification result verison the result supports + // The highest CNI specification result version the result supports // without having to convert Version() string @@ -94,6 +99,9 @@ type Result interface { // Prints the result in JSON format to stdout Print() error + // Prints the result in JSON format to provided writer + PrintTo(writer io.Writer) error + // Returns a JSON string representation of the result String() string } diff --git a/vendor/github.com/containernetworking/cni/pkg/version/conf_test.go b/vendor/github.com/containernetworking/cni/pkg/version/conf_test.go index 881c57ade..2d8d62ed7 100644 --- a/vendor/github.com/containernetworking/cni/pkg/version/conf_test.go +++ b/vendor/github.com/containernetworking/cni/pkg/version/conf_test.go @@ -32,7 +32,7 @@ var _ = Describe("Decoding the version of network config", func() { configBytes = []byte(`{ "cniVersion": "4.3.2" }`) }) - Context("when the version is explict", func() { + Context("when the version is explicit", func() { It("returns the version", func() { version, err := decoder.Decode(configBytes) Expect(err).NotTo(HaveOccurred()) diff --git a/vendor/github.com/containernetworking/cni/pkg/version/legacy_examples/example_runtime.go b/vendor/github.com/containernetworking/cni/pkg/version/legacy_examples/example_runtime.go index 055d7930f..532c96e6d 100644 --- a/vendor/github.com/containernetworking/cni/pkg/version/legacy_examples/example_runtime.go +++ b/vendor/github.com/containernetworking/cni/pkg/version/legacy_examples/example_runtime.go @@ -139,7 +139,7 @@ func (c *ExampleNetConf) Cleanup() { } // V010_Runtime creates a simple noop network configuration, then -// executes libcni against the the noop test plugin. +// executes libcni against the noop test plugin. var V010_Runtime = ExampleRuntime{ NetConfs: []string{"unversioned", "0.1.0"}, Example: Example{ diff --git a/vendor/github.com/containernetworking/cni/pkg/version/plugin.go b/vendor/github.com/containernetworking/cni/pkg/version/plugin.go index 612335a81..1df427243 100644 --- a/vendor/github.com/containernetworking/cni/pkg/version/plugin.go +++ b/vendor/github.com/containernetworking/cni/pkg/version/plugin.go @@ -86,9 +86,13 @@ func (*PluginDecoder) Decode(jsonBytes []byte) (PluginInfo, error) { // minor, and micro numbers or returns an error func ParseVersion(version string) (int, int, int, error) { var major, minor, micro int + if version == "" { + return -1, -1, -1, fmt.Errorf("invalid version %q: the version is empty", version) + } + parts := strings.Split(version, ".") - if len(parts) == 0 || len(parts) >= 4 { - return -1, -1, -1, fmt.Errorf("invalid version %q: too many or too few parts", version) + if len(parts) >= 4 { + return -1, -1, -1, fmt.Errorf("invalid version %q: too many parts", version) } major, err := strconv.Atoi(parts[0]) @@ -114,7 +118,7 @@ func ParseVersion(version string) (int, int, int, error) { } // GreaterThanOrEqualTo takes two string versions, parses them into major/minor/micro -// nubmers, and compares them to determine whether the first version is greater +// numbers, and compares them to determine whether the first version is greater // than or equal to the second func GreaterThanOrEqualTo(version, otherVersion string) (bool, error) { firstMajor, firstMinor, firstMicro, err := ParseVersion(version) diff --git a/vendor/github.com/containernetworking/cni/pkg/version/plugin_test.go b/vendor/github.com/containernetworking/cni/pkg/version/plugin_test.go index bf25085d3..a98f14e7e 100644 --- a/vendor/github.com/containernetworking/cni/pkg/version/plugin_test.go +++ b/vendor/github.com/containernetworking/cni/pkg/version/plugin_test.go @@ -92,7 +92,7 @@ var _ = Describe("Decoding versions reported by a plugin", func() { }) It("returns an error for malformed versions", func() { - badVersions := []string{"asdfasdf", "asdf.", ".asdfas", "asdf.adsf.", "0.", "..", "1.2.3.4.5"} + badVersions := []string{"asdfasdf", "asdf.", ".asdfas", "asdf.adsf.", "0.", "..", "1.2.3.4.5", ""} for _, v := range badVersions { _, _, _, err := version.ParseVersion(v) Expect(err).To(HaveOccurred()) diff --git a/vendor/github.com/containernetworking/cni/pkg/version/version.go b/vendor/github.com/containernetworking/cni/pkg/version/version.go index c8e46d55b..8f3508e61 100644 --- a/vendor/github.com/containernetworking/cni/pkg/version/version.go +++ b/vendor/github.com/containernetworking/cni/pkg/version/version.go @@ -15,6 +15,7 @@ package version import ( + "encoding/json" "fmt" "github.com/containernetworking/cni/pkg/types" @@ -59,3 +60,24 @@ func NewResult(version string, resultBytes []byte) (types.Result, error) { return nil, fmt.Errorf("unsupported CNI result version %q", version) } + +// ParsePrevResult parses a prevResult in a NetConf structure and sets +// the NetConf's PrevResult member to the parsed Result object. +func ParsePrevResult(conf *types.NetConf) error { + if conf.RawPrevResult == nil { + return nil + } + + resultBytes, err := json.Marshal(conf.RawPrevResult) + if err != nil { + return fmt.Errorf("could not serialize prevResult: %v", err) + } + + conf.RawPrevResult = nil + conf.PrevResult, err = NewResult(conf.CNIVersion, resultBytes) + if err != nil { + return fmt.Errorf("could not parse prevResult: %v", err) + } + + return nil +} diff --git a/vendor/github.com/containernetworking/cni/pkg/version/version_test.go b/vendor/github.com/containernetworking/cni/pkg/version/version_test.go new file mode 100644 index 000000000..1a34e9abb --- /dev/null +++ b/vendor/github.com/containernetworking/cni/pkg/version/version_test.go @@ -0,0 +1,131 @@ +// Copyright 2018 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 version_test + +import ( + "encoding/json" + "net" + "reflect" + + "github.com/containernetworking/cni/pkg/types" + "github.com/containernetworking/cni/pkg/types/current" + "github.com/containernetworking/cni/pkg/version" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("Version operations", func() { + Context("when a prevResult is available", func() { + It("parses the prevResult", func() { + rawBytes := []byte(`{ + "cniVersion": "0.3.0", + "interfaces": [ + { + "name": "eth0", + "mac": "00:11:22:33:44:55", + "sandbox": "/proc/3553/ns/net" + } + ], + "ips": [ + { + "version": "4", + "interface": 0, + "address": "1.2.3.30/24", + "gateway": "1.2.3.1" + } + ] + }`) + var raw map[string]interface{} + err := json.Unmarshal(rawBytes, &raw) + Expect(err).NotTo(HaveOccurred()) + + conf := &types.NetConf{ + CNIVersion: "0.3.0", + Name: "foobar", + Type: "baz", + RawPrevResult: raw, + } + + err = version.ParsePrevResult(conf) + Expect(err).NotTo(HaveOccurred()) + + expectedResult := ¤t.Result{ + CNIVersion: "0.3.0", + Interfaces: []*current.Interface{ + { + Name: "eth0", + Mac: "00:11:22:33:44:55", + Sandbox: "/proc/3553/ns/net", + }, + }, + IPs: []*current.IPConfig{ + { + Version: "4", + Interface: current.Int(0), + Address: net.IPNet{ + IP: net.ParseIP("1.2.3.30"), + Mask: net.IPv4Mask(255, 255, 255, 0), + }, + Gateway: net.ParseIP("1.2.3.1"), + }, + }, + } + Expect(reflect.DeepEqual(conf.PrevResult, expectedResult)).To(BeTrue()) + }) + + It("fails if the prevResult version is unknown", func() { + conf := &types.NetConf{ + CNIVersion: "0.3.0", + Name: "foobar", + Type: "baz", + RawPrevResult: map[string]interface{}{ + "cniVersion": "5678.456", + }, + } + + err := version.ParsePrevResult(conf) + Expect(err).NotTo(HaveOccurred()) + }) + + It("fails if the prevResult is invalid", func() { + conf := &types.NetConf{ + CNIVersion: "0.3.0", + Name: "foobar", + Type: "baz", + RawPrevResult: map[string]interface{}{ + "adsfasdfasdfasdfasdfaf": nil, + }, + } + + err := version.ParsePrevResult(conf) + Expect(err).NotTo(HaveOccurred()) + }) + }) + + Context("when a prevResult is not available", func() { + It("does not fail", func() { + conf := &types.NetConf{ + CNIVersion: "0.3.0", + Name: "foobar", + Type: "baz", + } + + err := version.ParsePrevResult(conf) + Expect(err).NotTo(HaveOccurred()) + Expect(conf.PrevResult).To(BeNil()) + }) + }) +}) diff --git a/vendor/github.com/containernetworking/cni/plugins/test/noop/debug/debug.go b/vendor/github.com/containernetworking/cni/plugins/test/noop/debug/debug.go index f400800f6..86310fd8a 100644 --- a/vendor/github.com/containernetworking/cni/plugins/test/noop/debug/debug.go +++ b/vendor/github.com/containernetworking/cni/plugins/test/noop/debug/debug.go @@ -35,7 +35,7 @@ type Debug struct { // Command stores the CNI command that the plugin received Command string - // CmdArgs stores the CNI Args and Env Vars that the plugin recieved + // CmdArgs stores the CNI Args and Env Vars that the plugin received CmdArgs skel.CmdArgs } diff --git a/vendor/github.com/containernetworking/cni/plugins/test/noop/main.go b/vendor/github.com/containernetworking/cni/plugins/test/noop/main.go index f2da357b6..5521b9a03 100644 --- a/vendor/github.com/containernetworking/cni/plugins/test/noop/main.go +++ b/vendor/github.com/containernetworking/cni/plugins/test/noop/main.go @@ -173,8 +173,8 @@ func cmdAdd(args *skel.CmdArgs) error { return debugBehavior(args, "ADD") } -func cmdGet(args *skel.CmdArgs) error { - return debugBehavior(args, "GET") +func cmdCheck(args *skel.CmdArgs) error { + return debugBehavior(args, "CHECK") } func cmdDel(args *skel.CmdArgs) error { @@ -212,5 +212,5 @@ func main() { } supportedVersions := debugGetSupportedVersions(stdinData) - skel.PluginMain(cmdAdd, cmdGet, cmdDel, version.PluginSupports(supportedVersions...), "CNI nnop plugin v0.7.0") + skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.PluginSupports(supportedVersions...), "CNI noop plugin v0.7.0") } diff --git a/vendor/github.com/containernetworking/cni/plugins/test/sleep/main.go b/vendor/github.com/containernetworking/cni/plugins/test/sleep/main.go new file mode 100644 index 000000000..9fcecae72 --- /dev/null +++ b/vendor/github.com/containernetworking/cni/plugins/test/sleep/main.go @@ -0,0 +1,27 @@ +// Copyright 2018 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. + +/* +main plugin is a CNI plugin designed for test the plugin running timeout. +*/ + +package main + +import ( + "time" +) + +func main() { + time.Sleep(60 * time.Second) +} diff --git a/vendor/github.com/containernetworking/cni/scripts/docker-run.sh b/vendor/github.com/containernetworking/cni/scripts/docker-run.sh index a8e12795f..b6cd42503 100755 --- a/vendor/github.com/containernetworking/cni/scripts/docker-run.sh +++ b/vendor/github.com/containernetworking/cni/scripts/docker-run.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # Run a docker container with network namespace set up by the # CNI plugins. diff --git a/vendor/github.com/containernetworking/plugins/.appveyor.yml b/vendor/github.com/containernetworking/plugins/.appveyor.yml deleted file mode 100644 index ea06455d3..000000000 --- a/vendor/github.com/containernetworking/plugins/.appveyor.yml +++ /dev/null @@ -1,28 +0,0 @@ -clone_folder: c:\gopath\src\github.com\containernetworking\plugins - -environment: - GOPATH: c:\gopath - -install: - - echo %PATH% - - echo %GOPATH% - - set PATH=%GOPATH%\bin;c:\go\bin;%PATH% - - go version - - go env - -build: off - -test_script: - - ps: | - go list ./... | Select-String -Pattern (Get-Content "./plugins/linux_only.txt") -NotMatch > "to_test.txt" - echo "Will test:" - Get-Content "to_test.txt" - foreach ($pkg in Get-Content "to_test.txt") { - if ($pkg) { - echo $pkg - go test -v $pkg - if ($LastExitCode -ne 0) { - throw "test failed" - } - } - } diff --git a/vendor/github.com/containernetworking/plugins/.gitignore b/vendor/github.com/containernetworking/plugins/.gitignore index b33641168..6e7ccd20b 100644 --- a/vendor/github.com/containernetworking/plugins/.gitignore +++ b/vendor/github.com/containernetworking/plugins/.gitignore @@ -26,5 +26,5 @@ _testmain.go bin/ gopath/ .vagrant - +.idea /release-* diff --git a/vendor/github.com/containernetworking/plugins/.travis.yml b/vendor/github.com/containernetworking/plugins/.travis.yml index e6ab0e99e..68e6d4c05 100644 --- a/vendor/github.com/containernetworking/plugins/.travis.yml +++ b/vendor/github.com/containernetworking/plugins/.travis.yml @@ -3,12 +3,13 @@ sudo: required dist: trusty go: - - 1.9.x - 1.10.x + - 1.11.x env: global: - PATH=$GOROOT/bin:$GOPATH/bin:$PATH + - CGO_ENABLED=0 matrix: - TARGET=amd64 - TARGET=arm @@ -18,17 +19,27 @@ env: matrix: fast_finish: true + include: + - os: windows + env: TARGET=amd64 + go: 1.10.x + - os: windows + env: TARGET=amd64 + go: 1.11.x install: - go get github.com/onsi/ginkgo/ginkgo - go get github.com/containernetworking/cni/cnitool + - go get golang.org/x/tools/cmd/cover + - go get github.com/modocache/gover + - go get github.com/mattn/goveralls script: - | if [ "${TARGET}" == "amd64" ]; then - GOARCH="${TARGET}" ./test.sh + GOARCH="${TARGET}" ./test_${TRAVIS_OS_NAME}.sh else - GOARCH="${TARGET}" ./build.sh + GOARCH="${TARGET}" ./build_linux.sh fi notifications: diff --git a/vendor/github.com/containernetworking/plugins/CONTRIBUTING.md b/vendor/github.com/containernetworking/plugins/CONTRIBUTING.md index 0108d70e2..a56a1925a 100644 --- a/vendor/github.com/containernetworking/plugins/CONTRIBUTING.md +++ b/vendor/github.com/containernetworking/plugins/CONTRIBUTING.md @@ -26,9 +26,14 @@ are very busy and read the mailing lists. ## Getting Started - Fork the repository on GitHub -- Read the [README](README.md) for build and test instructions - Play with the project, submit bugs, submit pull requests! + +## Building + +Each plugin is compiled simply with `go build`. Two scripts, `build_linux.sh` and `build_windows.sh`, +are supplied which will build all the plugins for their respective OS. + ## Contribution workflow This is a rough outline of how to prepare a contribution: @@ -61,10 +66,12 @@ git clone https://github.com/containernetworking/plugins Next, boot the virtual machine and SSH in to run the tests: ```bash +cd ~/workspace/plugins vagrant up vagrant ssh # you're now in a shell in a virtual machine sudo su +go get github.com/onsi/ginkgo/ginkgo cd /go/src/github.com/containernetworking/plugins # to run the full test suite diff --git a/vendor/github.com/containernetworking/plugins/DCO b/vendor/github.com/containernetworking/plugins/DCO new file mode 100644 index 000000000..716561d5d --- /dev/null +++ b/vendor/github.com/containernetworking/plugins/DCO @@ -0,0 +1,36 @@ +Developer Certificate of Origin +Version 1.1 + +Copyright (C) 2004, 2006 The Linux Foundation and its contributors. +660 York Street, Suite 102, +San Francisco, CA 94110 USA + +Everyone is permitted to copy and distribute verbatim copies of this +license document, but changing it is not allowed. + + +Developer's Certificate of Origin 1.1 + +By making a contribution to this project, I certify that: + +(a) The contribution was created in whole or in part by me and I + have the right to submit it under the open source license + indicated in the file; or + +(b) The contribution is based upon previous work that, to the best + of my knowledge, is covered under an appropriate open source + license and I have the right under that license to submit that + work with modifications, whether created in whole or in part + by me, under the same open source license (unless I am + permitted to submit under a different license), as indicated + in the file; or + +(c) The contribution was provided directly to me by some other + person who certified (a), (b) or (c) and I have not modified + it. + +(d) I understand and agree that this project and the contribution + are public and that a record of the contribution (including all + personal information I submit with it, including my sign-off) is + maintained indefinitely and may be redistributed consistent with + this project or the open source license(s) involved. diff --git a/vendor/github.com/containernetworking/plugins/Godeps/Godeps.json b/vendor/github.com/containernetworking/plugins/Godeps/Godeps.json index ac72ae9dd..b9730b513 100644 --- a/vendor/github.com/containernetworking/plugins/Godeps/Godeps.json +++ b/vendor/github.com/containernetworking/plugins/Godeps/Godeps.json @@ -1,54 +1,123 @@ { "ImportPath": "github.com/containernetworking/plugins", "GoVersion": "go1.7", - "GodepVersion": "v79", + "GodepVersion": "v80", "Packages": [ "./..." ], "Deps": [ + { + "ImportPath": "github.com/Microsoft/go-winio", + "Comment": "v0.4.11", + "Rev": "97e4973ce50b2ff5f09635a57e2b88a037aae829" + }, + { + "ImportPath": "github.com/Microsoft/hcsshim", + "Comment": "v0.7.6", + "Rev": "e44e499d29527b244d6858772f1b9090eeaddc4e" + }, + { + "ImportPath": "github.com/Microsoft/hcsshim/internal/guid", + "Comment": "v0.7.4", + "Rev": "e44e499d29527b244d6858772f1b9090eeaddc4e" + }, + { + "ImportPath": "github.com/Microsoft/hcsshim/internal/hcs", + "Comment": "v0.7.4", + "Rev": "e44e499d29527b244d6858772f1b9090eeaddc4e" + }, + { + "ImportPath": "github.com/Microsoft/hcsshim/internal/hcserror", + "Comment": "v0.7.4", + "Rev": "e44e499d29527b244d6858772f1b9090eeaddc4e" + }, + { + "ImportPath": "github.com/Microsoft/hcsshim/internal/hns", + "Comment": "v0.7.4", + "Rev": "e44e499d29527b244d6858772f1b9090eeaddc4e" + }, + { + "ImportPath": "github.com/Microsoft/hcsshim/internal/interop", + "Comment": "v0.7.4", + "Rev": "e44e499d29527b244d6858772f1b9090eeaddc4e" + }, + { + "ImportPath": "github.com/Microsoft/hcsshim/internal/longpath", + "Comment": "v0.7.4", + "Rev": "e44e499d29527b244d6858772f1b9090eeaddc4e" + }, + { + "ImportPath": "github.com/Microsoft/hcsshim/internal/mergemaps", + "Comment": "v0.7.4", + "Rev": "e44e499d29527b244d6858772f1b9090eeaddc4e" + }, + { + "ImportPath": "github.com/Microsoft/hcsshim/internal/safefile", + "Comment": "v0.7.4", + "Rev": "e44e499d29527b244d6858772f1b9090eeaddc4e" + }, + { + "ImportPath": "github.com/Microsoft/hcsshim/internal/schema1", + "Comment": "v0.7.4", + "Rev": "e44e499d29527b244d6858772f1b9090eeaddc4e" + }, + { + "ImportPath": "github.com/Microsoft/hcsshim/internal/timeout", + "Comment": "v0.7.4", + "Rev": "e44e499d29527b244d6858772f1b9090eeaddc4e" + }, + { + "ImportPath": "github.com/Microsoft/hcsshim/internal/wclayer", + "Comment": "v0.7.4", + "Rev": "e44e499d29527b244d6858772f1b9090eeaddc4e" + }, { "ImportPath": "github.com/alexflint/go-filemutex", "Rev": "72bdc8eae2aef913234599b837f5dda445ca9bd9" }, + { + "ImportPath": "github.com/buger/jsonparser", + "Rev": "f4dd9f5a6b441265aefc1d44872a6f8c10f42b37" + }, { "ImportPath": "github.com/containernetworking/cni/libcni", - "Comment": "v0.6.0-rc1", - "Rev": "a2da8f8d7fd8e6dc25f336408a8ac86f050fbd88" + "Comment": "v0.7.0", + "Rev": "7d76556571b6cf1ab90d7026a73092ac8d5e0c23" }, { "ImportPath": "github.com/containernetworking/cni/pkg/invoke", - "Comment": "v0.6.0-rc1", - "Rev": "a2da8f8d7fd8e6dc25f336408a8ac86f050fbd88" + "Comment": "v0.7.0", + "Rev": "7d76556571b6cf1ab90d7026a73092ac8d5e0c23" }, { "ImportPath": "github.com/containernetworking/cni/pkg/skel", - "Comment": "v0.6.0-rc1", - "Rev": "a2da8f8d7fd8e6dc25f336408a8ac86f050fbd88" + "Comment": "v0.7.0", + "Rev": "7d76556571b6cf1ab90d7026a73092ac8d5e0c23" }, { "ImportPath": "github.com/containernetworking/cni/pkg/types", - "Comment": "v0.6.0-rc1", - "Rev": "a2da8f8d7fd8e6dc25f336408a8ac86f050fbd88" + "Comment": "v0.7.0", + "Rev": "7d76556571b6cf1ab90d7026a73092ac8d5e0c23" }, { "ImportPath": "github.com/containernetworking/cni/pkg/types/020", - "Comment": "v0.6.0-rc1", - "Rev": "a2da8f8d7fd8e6dc25f336408a8ac86f050fbd88" + "Comment": "v0.7.0", + "Rev": "7d76556571b6cf1ab90d7026a73092ac8d5e0c23" }, { "ImportPath": "github.com/containernetworking/cni/pkg/types/current", - "Comment": "v0.6.0-rc1", - "Rev": "a2da8f8d7fd8e6dc25f336408a8ac86f050fbd88" + "Comment": "v0.7.0", + "Rev": "7d76556571b6cf1ab90d7026a73092ac8d5e0c23" }, { "ImportPath": "github.com/containernetworking/cni/pkg/version", - "Comment": "v0.6.0-rc1", - "Rev": "a2da8f8d7fd8e6dc25f336408a8ac86f050fbd88" + "Comment": "v0.7.0", + "Rev": "7d76556571b6cf1ab90d7026a73092ac8d5e0c23" }, { "ImportPath": "github.com/coreos/go-iptables/iptables", - "Comment": "v0.3.0", - "Rev": "b5b1876b170881a8259f036445ee89c8669db386" + "Comment": "v0.4.1", + "Rev": "78b5fff24e6df8886ef8eca9411f683a884349a5" }, { "ImportPath": "github.com/coreos/go-systemd/activation", @@ -65,12 +134,29 @@ }, { "ImportPath": "github.com/d2g/dhcp4server", - "Rev": "1b74244053681c90de5cf1af3d6b5c93b74e3abb" + "Rev": "477b11cea4dcc56af002849238d4f9c1e093c744" + }, + { + "ImportPath": "github.com/d2g/dhcp4server/leasepool", + "Rev": "477b11cea4dcc56af002849238d4f9c1e093c744" + }, + { + "ImportPath": "github.com/d2g/dhcp4server/leasepool/memorypool", + "Rev": "477b11cea4dcc56af002849238d4f9c1e093c744" + }, + { + "ImportPath": "github.com/godbus/dbus", + "Comment": "v4.1.0-6-g885f9cc", + "Rev": "885f9cc04c9c1a6a61a2008e211d36c5737be3f5" }, { "ImportPath": "github.com/j-keck/arping", "Rev": "2cf9dc699c5640a7e2c81403a44127bf28033600" }, + { + "ImportPath": "github.com/juju/errors", + "Rev": "22422dad46e14561a0854ad42497a75af9b61909" + }, { "ImportPath": "github.com/mattn/go-shellwords", "Comment": "v1.0.3", @@ -230,33 +316,52 @@ "ImportPath": "github.com/safchain/ethtool", "Rev": "7ff1ba29eca231991280817541cb3903f6be15d1" }, + { + "ImportPath": "github.com/sirupsen/logrus", + "Comment": "v1.0.6", + "Rev": "3e01752db0189b9157070a0e1668a620f9a85da2" + }, { "ImportPath": "github.com/vishvananda/netlink", - "Rev": "6e453822d85ef5721799774b654d4d02fed62afb" + "Comment": "v1.0.0-40-g023a6da", + "Rev": "023a6dafdcdfa7068ac83b260ab7f03cd4131aca" }, { "ImportPath": "github.com/vishvananda/netlink/nl", - "Rev": "6e453822d85ef5721799774b654d4d02fed62afb" + "Comment": "v1.0.0-40-g023a6da", + "Rev": "023a6dafdcdfa7068ac83b260ab7f03cd4131aca" }, { "ImportPath": "github.com/vishvananda/netns", - "Rev": "54f0e4339ce73702a0607f49922aaa1e749b418d" + "Rev": "13995c7128ccc8e51e9a6bd2b551020a27180abd" + }, + { + "ImportPath": "golang.org/x/crypto/ssh/terminal", + "Rev": "7c1a557ab941a71c619514f229f0b27ccb0c27cf" }, { "ImportPath": "golang.org/x/net/bpf", - "Rev": "e90d6d0afc4c315a0d87a568ae68577cc15149a0" + "Rev": "49bb7cea24b1df9410e1712aa6433dae904ff66a" }, { "ImportPath": "golang.org/x/net/internal/iana", - "Rev": "e90d6d0afc4c315a0d87a568ae68577cc15149a0" + "Rev": "49bb7cea24b1df9410e1712aa6433dae904ff66a" }, { "ImportPath": "golang.org/x/net/ipv4", - "Rev": "e90d6d0afc4c315a0d87a568ae68577cc15149a0" + "Rev": "49bb7cea24b1df9410e1712aa6433dae904ff66a" }, { "ImportPath": "golang.org/x/sys/unix", - "Rev": "076b546753157f758b316e59bcb51e6807c04057" + "Rev": "66b7b1311ac80bbafcd2daeef9a5e6e2cd1e2399" + }, + { + "ImportPath": "golang.org/x/sys/windows", + "Rev": "66b7b1311ac80bbafcd2daeef9a5e6e2cd1e2399" + }, + { + "ImportPath": "golang.org/x/net/internal/socket", + "Rev": "49bb7cea24b1df9410e1712aa6433dae904ff66a" } ] } diff --git a/vendor/github.com/containernetworking/plugins/OWNERS.md b/vendor/github.com/containernetworking/plugins/OWNERS.md new file mode 100644 index 000000000..7afdcfcc1 --- /dev/null +++ b/vendor/github.com/containernetworking/plugins/OWNERS.md @@ -0,0 +1,8 @@ +# Owners +This is the official list of the CNI network plugins owners: +- Bryan Boreham (@bboreham) +- Casey Callendrello (@squeed) +- Dan Williams (@dcbw) +- Gabe Rosenhouse (@rosenhouse) +- Matt Dupre (@matthewdupre) +- Stefan Junker (@steveeJ) \ No newline at end of file diff --git a/vendor/github.com/containernetworking/plugins/README.md b/vendor/github.com/containernetworking/plugins/README.md index f0e444355..ca904a51d 100644 --- a/vendor/github.com/containernetworking/plugins/README.md +++ b/vendor/github.com/containernetworking/plugins/README.md @@ -1,26 +1,34 @@ -[![Linux Build Status](https://travis-ci.org/containernetworking/plugins.svg?branch=master)](https://travis-ci.org/containernetworking/plugins) -[![Windows Build Status](https://ci.appveyor.com/api/projects/status/kcuubx0chr76ev86/branch/master?svg=true)](https://ci.appveyor.com/project/cni-bot/plugins/branch/master) +[![Build Status](https://travis-ci.org/containernetworking/plugins.svg?branch=master)](https://travis-ci.org/containernetworking/plugins) # plugins Some CNI network plugins, maintained by the containernetworking team. For more information, see the individual READMEs. +Read [CONTRIBUTING](CONTRIBUTING.md) for build and test instructions. + ## Plugins supplied: ### Main: interface-creating * `bridge`: Creates a bridge, adds the host and the container to it. -* `ipvlan`: Adds an [ipvlan](https://www.kernel.org/doc/Documentation/networking/ipvlan.txt) interface in the container -* `loopback`: Creates a loopback interface -* `macvlan`: Creates a new MAC address, forwards all traffic to that to the container +* `ipvlan`: Adds an [ipvlan](https://www.kernel.org/doc/Documentation/networking/ipvlan.txt) interface in the container. +* `loopback`: Set the state of loopback interface to up. +* `macvlan`: Creates a new MAC address, forwards all traffic to that to the container. * `ptp`: Creates a veth pair. * `vlan`: Allocates a vlan device. - +* `host-device`: Move an already-existing device into a container. +#### Windows: windows specific +* `win-bridge`: Creates a bridge, adds the host and the container to it. +* `win-overlay`: Creates an overlay interface to the container. ### IPAM: IP address allocation * `dhcp`: Runs a daemon on the host to make DHCP requests on behalf of the container -* `host-local`: maintains a local database of allocated IPs +* `host-local`: Maintains a local database of allocated IPs +* `static`: Allocate a static IPv4/IPv6 addresses to container and it's useful in debugging purpose. ### Meta: other plugins -* `flannel`: generates an interface corresponding to a flannel config file +* `flannel`: Generates an interface corresponding to a flannel config file * `tuning`: Tweaks sysctl parameters of an existing interface * `portmap`: An iptables-based portmapping plugin. Maps ports from the host's address space to the container. +* `bandwidth`: Allows bandwidth-limiting through use of traffic control tbf (ingress/egress). +* `sbr`: A plugin that configures source based routing for an interface (from which it is chained). +* `firewall`: A firewall plugin which uses iptables or firewalld to add rules to allow traffic to/from the container. ### Sample The sample plugin provides an example for building your own plugin. diff --git a/vendor/github.com/containernetworking/plugins/Vagrantfile b/vendor/github.com/containernetworking/plugins/Vagrantfile index fa8fa3c9f..58de57651 100644 --- a/vendor/github.com/containernetworking/plugins/Vagrantfile +++ b/vendor/github.com/containernetworking/plugins/Vagrantfile @@ -8,14 +8,11 @@ Vagrant.configure(2) do |config| config.vm.provision "shell", inline: <<-SHELL set -e -x -u - apt-get update -y || (sleep 40 && apt-get update -y) - apt-get install -y git - - wget -qO- https://storage.googleapis.com/golang/go1.10.linux-amd64.tar.gz | tar -C /usr/local -xz - + apt-get install -y git gcc-multilib gcc-mingw-w64 + wget -qO- https://storage.googleapis.com/golang/go1.11.1.linux-amd64.tar.gz | tar -C /usr/local -xz echo 'export GOPATH=/go' >> /root/.bashrc echo 'export PATH=$PATH:/usr/local/go/bin:$GOPATH/bin' >> /root/.bashrc cd /go/src/github.com/containernetworking/plugins SHELL -end +end \ No newline at end of file diff --git a/vendor/github.com/containernetworking/plugins/build.sh b/vendor/github.com/containernetworking/plugins/build_linux.sh similarity index 70% rename from vendor/github.com/containernetworking/plugins/build.sh rename to vendor/github.com/containernetworking/plugins/build_linux.sh index f55eb117a..bd9a52eb6 100755 --- a/vendor/github.com/containernetworking/plugins/build.sh +++ b/vendor/github.com/containernetworking/plugins/build_linux.sh @@ -19,12 +19,14 @@ export GO="${GO:-go}" mkdir -p "${PWD}/bin" -echo "Building plugins" -PLUGINS="plugins/meta/* plugins/main/* plugins/ipam/* plugins/sample" +echo "Building plugins ${GOOS}" +PLUGINS="plugins/meta/* plugins/main/* plugins/ipam/*" for d in $PLUGINS; do if [ -d "$d" ]; then plugin="$(basename "$d")" - echo " $plugin" - $GO build -o "${PWD}/bin/$plugin" "$@" "$REPO_PATH"/$d + if [ $plugin != "windows" ]; then + echo " $plugin" + $GO build -o "${PWD}/bin/$plugin" "$@" "$REPO_PATH"/$d + fi fi done diff --git a/vendor/github.com/containernetworking/plugins/build_windows.sh b/vendor/github.com/containernetworking/plugins/build_windows.sh new file mode 100755 index 000000000..010f902d6 --- /dev/null +++ b/vendor/github.com/containernetworking/plugins/build_windows.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +set -e +cd $(dirname "$0") + +ORG_PATH="github.com/containernetworking" +export REPO_PATH="${ORG_PATH}/plugins" + +export GOPATH=$(mktemp -d) +mkdir -p ${GOPATH}/src/${ORG_PATH} +trap "{ rm -rf $GOPATH; }" EXIT +ln -s ${PWD} ${GOPATH}/src/${REPO_PATH} || exit 255 + +export GO="${GO:-go}" +export GOOS=windows + +PLUGINS=$(cat plugins/windows_only.txt) +for d in $PLUGINS; do + if [ -d "$d" ]; then + plugin="$(basename "$d").exe" + echo " $plugin" + $GO build -o "${PWD}/bin/$plugin" "$@" "$REPO_PATH"/$d + fi +done diff --git a/vendor/github.com/containernetworking/plugins/pkg/hns/endpoint_windows.go b/vendor/github.com/containernetworking/plugins/pkg/hns/endpoint_windows.go new file mode 100644 index 000000000..85a0f64eb --- /dev/null +++ b/vendor/github.com/containernetworking/plugins/pkg/hns/endpoint_windows.go @@ -0,0 +1,371 @@ +// Copyright 2017 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 hns + +import ( + "fmt" + "net" + "strings" + + "github.com/Microsoft/hcsshim" + "github.com/Microsoft/hcsshim/hcn" + "github.com/containernetworking/cni/pkg/types" + "github.com/containernetworking/cni/pkg/types/current" + "github.com/juju/errors" +) + +const ( + pauseContainerNetNS = "none" +) + +type EndpointInfo struct { + EndpointName string + DNS types.DNS + NetworkName string + NetworkId string + Gateway net.IP + IpAddress net.IP +} + +// GetSandboxContainerID returns the sandbox ID of this pod +func GetSandboxContainerID(containerID string, netNs string) string { + if len(netNs) != 0 && netNs != pauseContainerNetNS { + splits := strings.SplitN(netNs, ":", 2) + if len(splits) == 2 { + containerID = splits[1] + } + } + + return containerID +} + +// short function so we know when to return "" for a string +func GetIpString(ip *net.IP) string { + if len(*ip) == 0 { + return "" + } else { + return ip.String() + } +} + +func GenerateHnsEndpoint(epInfo *EndpointInfo, n *NetConf) (*hcsshim.HNSEndpoint, error) { + // run the IPAM plugin and get back the config to apply + hnsEndpoint, err := hcsshim.GetHNSEndpointByName(epInfo.EndpointName) + if err != nil && !hcsshim.IsNotExist(err) { + return nil, errors.Annotatef(err, "Attempt to get endpoint \"%v\" failed", epInfo.EndpointName) + } + + if hnsEndpoint != nil { + if hnsEndpoint.VirtualNetwork != epInfo.NetworkId { + _, err = hnsEndpoint.Delete() + if err != nil { + return nil, errors.Annotatef(err, "Failed to delete endpoint %v", epInfo.EndpointName) + } + hnsEndpoint = nil + } + } + + if hnsEndpoint == nil { + hnsEndpoint = &hcsshim.HNSEndpoint{ + Name: epInfo.EndpointName, + VirtualNetwork: epInfo.NetworkId, + DNSServerList: strings.Join(epInfo.DNS.Nameservers, ","), + DNSSuffix: strings.Join(epInfo.DNS.Search, ","), + GatewayAddress: GetIpString(&epInfo.Gateway), + IPAddress: epInfo.IpAddress, + Policies: n.MarshalPolicies(), + } + } + return hnsEndpoint, nil +} + +func GenerateHcnEndpoint(epInfo *EndpointInfo, n *NetConf) (*hcn.HostComputeEndpoint, error) { + // run the IPAM plugin and get back the config to apply + hcnEndpoint, err := hcn.GetEndpointByName(epInfo.EndpointName) + if err != nil && !hcn.IsNotFoundError(err) { + return nil, errors.Annotatef(err, "Attempt to get endpoint \"%v\" failed", epInfo.EndpointName) + } + + if hcnEndpoint != nil { + // If the endpont already exists, then we should return error unless + // the endpoint is based on a different network then delete + // should that fail return error + if !strings.EqualFold(hcnEndpoint.HostComputeNetwork, epInfo.NetworkId) { + err = hcnEndpoint.Delete() + if err != nil { + return nil, errors.Annotatef(err, "Failed to delete endpoint %v", epInfo.EndpointName) + hcnEndpoint = nil + + } + } else { + return nil, fmt.Errorf("Endpoint \"%v\" already exits", epInfo.EndpointName) + } + } + + if hcnEndpoint == nil { + routes := []hcn.Route{ + { + NextHop: GetIpString(&epInfo.Gateway), + DestinationPrefix: func() string { + destinationPrefix := "0.0.0.0/0" + if ipv6 := epInfo.Gateway.To4(); ipv6 == nil { + destinationPrefix = "::/0" + } + return destinationPrefix + }(), + }, + } + + hcnDns := hcn.Dns{ + Search: epInfo.DNS.Search, + ServerList: epInfo.DNS.Nameservers, + } + + hcnIpConfig := hcn.IpConfig{ + IpAddress: GetIpString(&epInfo.IpAddress), + } + ipConfigs := []hcn.IpConfig{hcnIpConfig} + + hcnEndpoint = &hcn.HostComputeEndpoint{ + SchemaVersion: hcn.Version{Major: 2}, + Name: epInfo.EndpointName, + HostComputeNetwork: epInfo.NetworkId, + Dns: hcnDns, + Routes: routes, + IpConfigurations: ipConfigs, + Policies: func() []hcn.EndpointPolicy { + if n.HcnPolicyArgs == nil { + n.HcnPolicyArgs = []hcn.EndpointPolicy{} + } + return n.HcnPolicyArgs + }(), + } + } + return hcnEndpoint, nil +} + +// ConstructEndpointName constructs enpointId which is used to identify an endpoint from HNS +// There is a special consideration for netNs name here, which is required for Windows Server 1709 +// containerID is the Id of the container on which the endpoint is worked on +func ConstructEndpointName(containerID string, netNs string, networkName string) string { + return GetSandboxContainerID(containerID, netNs) + "_" + networkName +} + +// DeprovisionEndpoint removes an endpoint from the container by sending a Detach request to HNS +// For shared endpoint, ContainerDetach is used +// for removing the endpoint completely, HotDetachEndpoint is used +func DeprovisionEndpoint(epName string, netns string, containerID string) error { + if len(netns) == 0 { + return nil + } + + hnsEndpoint, err := hcsshim.GetHNSEndpointByName(epName) + + if hcsshim.IsNotExist(err) { + return nil + } else if err != nil { + return errors.Annotatef(err, "failed to find HNSEndpoint %s", epName) + } + + if netns != pauseContainerNetNS { + // Shared endpoint removal. Do not remove the endpoint. + hnsEndpoint.ContainerDetach(containerID) + return nil + } + + // Do not consider this as failure, else this would leak endpoints + hcsshim.HotDetachEndpoint(containerID, hnsEndpoint.Id) + + // Do not return error + hnsEndpoint.Delete() + + return nil +} + +type EndpointMakerFunc func() (*hcsshim.HNSEndpoint, error) + +// ProvisionEndpoint provisions an endpoint to a container specified by containerID. +// If an endpoint already exists, the endpoint is reused. +// This call is idempotent +func ProvisionEndpoint(epName string, expectedNetworkId string, containerID string, netns string, makeEndpoint EndpointMakerFunc) (*hcsshim.HNSEndpoint, error) { + // On the second add call we expect that the endpoint already exists. If it + // does not then we should return an error. + if netns != pauseContainerNetNS { + _, err := hcsshim.GetHNSEndpointByName(epName) + if err != nil { + return nil, errors.Annotatef(err, "failed to find HNSEndpoint %s", epName) + } + } + + // check if endpoint already exists + createEndpoint := true + hnsEndpoint, err := hcsshim.GetHNSEndpointByName(epName) + if hnsEndpoint != nil && strings.EqualFold(hnsEndpoint.VirtualNetwork, expectedNetworkId) { + createEndpoint = false + } + + if createEndpoint { + if hnsEndpoint != nil { + if _, err = hnsEndpoint.Delete(); err != nil { + return nil, errors.Annotate(err, "failed to delete the stale HNSEndpoint") + } + } + + if hnsEndpoint, err = makeEndpoint(); err != nil { + return nil, errors.Annotate(err, "failed to make a new HNSEndpoint") + } + + if hnsEndpoint, err = hnsEndpoint.Create(); err != nil { + return nil, errors.Annotate(err, "failed to create the new HNSEndpoint") + } + + } + + // hot attach + if err := hcsshim.HotAttachEndpoint(containerID, hnsEndpoint.Id); err != nil { + if createEndpoint { + err := DeprovisionEndpoint(epName, netns, containerID) + if err != nil { + return nil, errors.Annotatef(err, "failed to Deprovsion after HotAttach failure") + } + } + if hcsshim.ErrComputeSystemDoesNotExist == err { + return hnsEndpoint, nil + } + return nil, err + } + + return hnsEndpoint, nil +} + +type HcnEndpointMakerFunc func() (*hcn.HostComputeEndpoint, error) + +func AddHcnEndpoint(epName string, expectedNetworkId string, namespace string, + makeEndpoint HcnEndpointMakerFunc) (*hcn.HostComputeEndpoint, error) { + + hcnEndpoint, err := makeEndpoint() + if err != nil { + return nil, errors.Annotate(err, "failed to make a new HNSEndpoint") + } + + if hcnEndpoint, err = hcnEndpoint.Create(); err != nil { + return nil, errors.Annotate(err, "failed to create the new HNSEndpoint") + } + + err = hcn.AddNamespaceEndpoint(namespace, hcnEndpoint.Id) + if err != nil { + err := RemoveHcnEndpoint(epName) + if err != nil { + return nil, errors.Annotatef(err, "failed to Remove Endpoint after AddNamespaceEndpoint failure") + } + return nil, errors.Annotatef(err, "Failed to Add endpoint to namespace") + } + return hcnEndpoint, nil + +} + +// ConstructResult constructs the CNI result for the endpoint +func ConstructResult(hnsNetwork *hcsshim.HNSNetwork, hnsEndpoint *hcsshim.HNSEndpoint) (*current.Result, error) { + resultInterface := ¤t.Interface{ + Name: hnsEndpoint.Name, + Mac: hnsEndpoint.MacAddress, + } + _, ipSubnet, err := net.ParseCIDR(hnsNetwork.Subnets[0].AddressPrefix) + if err != nil { + return nil, errors.Annotatef(err, "failed to parse CIDR from %s", hnsNetwork.Subnets[0].AddressPrefix) + } + + var ipVersion string + if ipv4 := hnsEndpoint.IPAddress.To4(); ipv4 != nil { + ipVersion = "4" + } else if ipv6 := hnsEndpoint.IPAddress.To16(); ipv6 != nil { + ipVersion = "6" + } else { + return nil, fmt.Errorf("IPAddress of HNSEndpoint %s isn't a valid ipv4 or ipv6 Address", hnsEndpoint.Name) + } + + resultIPConfig := ¤t.IPConfig{ + Version: ipVersion, + Address: net.IPNet{ + IP: hnsEndpoint.IPAddress, + Mask: ipSubnet.Mask}, + Gateway: net.ParseIP(hnsEndpoint.GatewayAddress), + } + result := ¤t.Result{} + result.Interfaces = []*current.Interface{resultInterface} + result.IPs = []*current.IPConfig{resultIPConfig} + result.DNS = types.DNS{ + Search: strings.Split(hnsEndpoint.DNSSuffix, ","), + Nameservers: strings.Split(hnsEndpoint.DNSServerList, ","), + } + + return result, nil +} + +// This version follows the v2 workflow of removing the endpoint from the namespace and deleting it +func RemoveHcnEndpoint(epName string) error { + hcnEndpoint, err := hcn.GetEndpointByName(epName) + if hcn.IsNotFoundError(err) { + return nil + } else if err != nil { + _ = fmt.Errorf("[win-cni] Failed to find endpoint %v, err:%v", epName, err) + return err + } + if hcnEndpoint != nil { + err = hcnEndpoint.Delete() + if err != nil { + return fmt.Errorf("[win-cni] Failed to delete endpoint %v, err:%v", epName, err) + } + } + return nil +} + +func ConstructHcnResult(hcnNetwork *hcn.HostComputeNetwork, hcnEndpoint *hcn.HostComputeEndpoint) (*current.Result, error) { + resultInterface := ¤t.Interface{ + Name: hcnEndpoint.Name, + Mac: hcnEndpoint.MacAddress, + } + _, ipSubnet, err := net.ParseCIDR(hcnNetwork.Ipams[0].Subnets[0].IpAddressPrefix) + if err != nil { + return nil, err + } + + var ipVersion string + ipAddress := net.ParseIP(hcnEndpoint.IpConfigurations[0].IpAddress) + if ipv4 := ipAddress.To4(); ipv4 != nil { + ipVersion = "4" + } else if ipv6 := ipAddress.To16(); ipv6 != nil { + ipVersion = "6" + } else { + return nil, fmt.Errorf("[win-cni] The IPAddress of hnsEndpoint isn't a valid ipv4 or ipv6 Address.") + } + + resultIPConfig := ¤t.IPConfig{ + Version: ipVersion, + Address: net.IPNet{ + IP: ipAddress, + Mask: ipSubnet.Mask}, + Gateway: net.ParseIP(hcnEndpoint.Routes[0].NextHop), + } + result := ¤t.Result{} + result.Interfaces = []*current.Interface{resultInterface} + result.IPs = []*current.IPConfig{resultIPConfig} + result.DNS = types.DNS{ + Search: hcnEndpoint.Dns.Search, + Nameservers: hcnEndpoint.Dns.ServerList, + } + + return result, nil +} diff --git a/vendor/github.com/containernetworking/plugins/pkg/hns/hns_suite_windows_test.go b/vendor/github.com/containernetworking/plugins/pkg/hns/hns_suite_windows_test.go new file mode 100644 index 000000000..af24522d4 --- /dev/null +++ b/vendor/github.com/containernetworking/plugins/pkg/hns/hns_suite_windows_test.go @@ -0,0 +1,13 @@ +package hns_test + +import ( + "testing" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +func TestHns(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Hns Suite") +} diff --git a/vendor/github.com/containernetworking/plugins/pkg/hns/netconf_suite_windows_test.go b/vendor/github.com/containernetworking/plugins/pkg/hns/netconf_suite_windows_test.go new file mode 100644 index 000000000..cc69e6fd5 --- /dev/null +++ b/vendor/github.com/containernetworking/plugins/pkg/hns/netconf_suite_windows_test.go @@ -0,0 +1,26 @@ +// Copyright 2017 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 hns + +import ( + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + "testing" +) + +func TestHns(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "HNS NetConf Suite") +} diff --git a/vendor/github.com/containernetworking/plugins/pkg/hns/netconf_windows.go b/vendor/github.com/containernetworking/plugins/pkg/hns/netconf_windows.go new file mode 100644 index 000000000..eabd37cba --- /dev/null +++ b/vendor/github.com/containernetworking/plugins/pkg/hns/netconf_windows.go @@ -0,0 +1,174 @@ +// Copyright 2017 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 hns + +import ( + "bytes" + "encoding/json" + "github.com/Microsoft/hcsshim/hcn" + "github.com/buger/jsonparser" + "github.com/containernetworking/cni/pkg/types" + "strings" +) + +// NetConf is the CNI spec +type NetConf struct { + types.NetConf + HcnPolicyArgs []hcn.EndpointPolicy `json:"HcnPolicyArgs,omitempty"` + Policies []policy `json:"policies,omitempty"` + RuntimeConfig RuntimeConfig `json:"runtimeConfig"` +} + +type RuntimeDNS struct { + Nameservers []string `json:"servers,omitempty"` + Search []string `json:"searches,omitempty"` +} + +type RuntimeConfig struct { + DNS RuntimeDNS `json:"dns"` +} + +type policy struct { + Name string `json:"name"` + Value json.RawMessage `json:"value"` +} + +// If runtime dns values are there use that else use cni conf supplied dns +func (n *NetConf) GetDNS() types.DNS { + dnsResult := n.DNS + if len(n.RuntimeConfig.DNS.Nameservers) > 0 { + dnsResult.Nameservers = n.RuntimeConfig.DNS.Nameservers + } + if len(n.RuntimeConfig.DNS.Search) > 0 { + dnsResult.Search = n.RuntimeConfig.DNS.Search + } + return dnsResult +} + +// MarshalPolicies converts the Endpoint policies in Policies +// to HNS specific policies as Json raw bytes +func (n *NetConf) MarshalPolicies() []json.RawMessage { + if n.Policies == nil { + n.Policies = make([]policy, 0) + } + + result := make([]json.RawMessage, 0, len(n.Policies)) + for _, p := range n.Policies { + if !strings.EqualFold(p.Name, "EndpointPolicy") { + continue + } + + result = append(result, p.Value) + } + + return result +} + +// ApplyOutboundNatPolicy applies NAT Policy in VFP using HNS +// Simultaneously an exception is added for the network that has to be Nat'd +func (n *NetConf) ApplyOutboundNatPolicy(nwToNat string) { + if n.Policies == nil { + n.Policies = make([]policy, 0) + } + + nwToNatBytes := []byte(nwToNat) + + for i, p := range n.Policies { + if !strings.EqualFold(p.Name, "EndpointPolicy") { + continue + } + + typeValue, err := jsonparser.GetUnsafeString(p.Value, "Type") + if err != nil || len(typeValue) == 0 { + continue + } + + if !strings.EqualFold(typeValue, "OutBoundNAT") { + continue + } + + exceptionListValue, dt, _, _ := jsonparser.Get(p.Value, "ExceptionList") + // OutBoundNAT must with ExceptionList, so don't need to judge jsonparser.NotExist + if dt == jsonparser.Array { + buf := bytes.Buffer{} + buf.WriteString(`{"Type": "OutBoundNAT", "ExceptionList": [`) + + jsonparser.ArrayEach(exceptionListValue, func(value []byte, dataType jsonparser.ValueType, offset int, err error) { + if dataType == jsonparser.String && len(value) != 0 { + if bytes.Compare(value, nwToNatBytes) != 0 { + buf.WriteByte('"') + buf.Write(value) + buf.WriteByte('"') + buf.WriteByte(',') + } + } + }) + + buf.WriteString(`"` + nwToNat + `"]}`) + + n.Policies[i] = policy{ + Name: "EndpointPolicy", + Value: buf.Bytes(), + } + } else { + n.Policies[i] = policy{ + Name: "EndpointPolicy", + Value: []byte(`{"Type": "OutBoundNAT", "ExceptionList": ["` + nwToNat + `"]}`), + } + } + + return + } + + // didn't find the policyArg, add it + n.Policies = append(n.Policies, policy{ + Name: "EndpointPolicy", + Value: []byte(`{"Type": "OutBoundNAT", "ExceptionList": ["` + nwToNat + `"]}`), + }) +} + +// ApplyDefaultPAPolicy is used to configure a endpoint PA policy in HNS +func (n *NetConf) ApplyDefaultPAPolicy(paAddress string) { + if n.Policies == nil { + n.Policies = make([]policy, 0) + } + + // if its already present, leave untouched + for i, p := range n.Policies { + if !strings.EqualFold(p.Name, "EndpointPolicy") { + continue + } + + paValue, dt, _, _ := jsonparser.Get(p.Value, "PA") + if dt == jsonparser.NotExist { + continue + } else if dt == jsonparser.String && len(paValue) != 0 { + // found it, don't override + return + } + + n.Policies[i] = policy{ + Name: "EndpointPolicy", + Value: []byte(`{"Type": "PA", "PA": "` + paAddress + `"}`), + } + return + } + + // didn't find the policyArg, add it + n.Policies = append(n.Policies, policy{ + Name: "EndpointPolicy", + Value: []byte(`{"Type": "PA", "PA": "` + paAddress + `"}`), + }) +} diff --git a/vendor/github.com/containernetworking/plugins/pkg/hns/netconf_windows_test.go b/vendor/github.com/containernetworking/plugins/pkg/hns/netconf_windows_test.go new file mode 100644 index 000000000..0108056f9 --- /dev/null +++ b/vendor/github.com/containernetworking/plugins/pkg/hns/netconf_windows_test.go @@ -0,0 +1,189 @@ +// Copyright 2017 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 hns + +import ( + "encoding/json" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("HNS NetConf", func() { + Describe("ApplyOutBoundNATPolicy", func() { + Context("when not set by user", func() { + It("sets it by adding a policy", func() { + + // apply it + n := NetConf{} + n.ApplyOutboundNatPolicy("192.168.0.0/16") + + addlArgs := n.Policies + Expect(addlArgs).Should(HaveLen(1)) + + policy := addlArgs[0] + Expect(policy.Name).Should(Equal("EndpointPolicy")) + + value := make(map[string]interface{}) + json.Unmarshal(policy.Value, &value) + + Expect(value).Should(HaveKey("Type")) + Expect(value).Should(HaveKey("ExceptionList")) + Expect(value["Type"]).Should(Equal("OutBoundNAT")) + + exceptionList := value["ExceptionList"].([]interface{}) + Expect(exceptionList).Should(HaveLen(1)) + Expect(exceptionList[0].(string)).Should(Equal("192.168.0.0/16")) + }) + }) + + Context("when set by user", func() { + It("appends exceptions to the existing policy", func() { + // first set it + n := NetConf{} + n.ApplyOutboundNatPolicy("192.168.0.0/16") + + // then attempt to update it + n.ApplyOutboundNatPolicy("10.244.0.0/16") + + // it should be unchanged! + addlArgs := n.Policies + Expect(addlArgs).Should(HaveLen(1)) + + policy := addlArgs[0] + Expect(policy.Name).Should(Equal("EndpointPolicy")) + + var value map[string]interface{} + json.Unmarshal(policy.Value, &value) + + Expect(value).Should(HaveKey("Type")) + Expect(value).Should(HaveKey("ExceptionList")) + Expect(value["Type"]).Should(Equal("OutBoundNAT")) + + exceptionList := value["ExceptionList"].([]interface{}) + Expect(exceptionList).Should(HaveLen(2)) + Expect(exceptionList[0].(string)).Should(Equal("192.168.0.0/16")) + Expect(exceptionList[1].(string)).Should(Equal("10.244.0.0/16")) + }) + }) + }) + + Describe("ApplyDefaultPAPolicy", func() { + Context("when not set by user", func() { + It("sets it by adding a policy", func() { + + n := NetConf{} + n.ApplyDefaultPAPolicy("192.168.0.1") + + addlArgs := n.Policies + Expect(addlArgs).Should(HaveLen(1)) + + policy := addlArgs[0] + Expect(policy.Name).Should(Equal("EndpointPolicy")) + + value := make(map[string]interface{}) + json.Unmarshal(policy.Value, &value) + + Expect(value).Should(HaveKey("Type")) + Expect(value["Type"]).Should(Equal("PA")) + + paAddress := value["PA"].(string) + Expect(paAddress).Should(Equal("192.168.0.1")) + }) + }) + + Context("when set by user", func() { + It("does not override", func() { + n := NetConf{} + n.ApplyDefaultPAPolicy("192.168.0.1") + n.ApplyDefaultPAPolicy("192.168.0.2") + + addlArgs := n.Policies + Expect(addlArgs).Should(HaveLen(1)) + + policy := addlArgs[0] + Expect(policy.Name).Should(Equal("EndpointPolicy")) + + value := make(map[string]interface{}) + json.Unmarshal(policy.Value, &value) + + Expect(value).Should(HaveKey("Type")) + Expect(value["Type"]).Should(Equal("PA")) + + paAddress := value["PA"].(string) + Expect(paAddress).Should(Equal("192.168.0.1")) + Expect(paAddress).ShouldNot(Equal("192.168.0.2")) + }) + }) + }) + + Describe("MarshalPolicies", func() { + Context("when not set by user", func() { + It("sets it by adding a policy", func() { + + n := NetConf{ + Policies: []policy{ + { + Name: "EndpointPolicy", + Value: []byte(`{"someKey": "someValue"}`), + }, + { + Name: "someOtherType", + Value: []byte(`{"someOtherKey": "someOtherValue"}`), + }, + }, + } + + result := n.MarshalPolicies() + Expect(len(result)).To(Equal(1)) + + policy := make(map[string]interface{}) + err := json.Unmarshal(result[0], &policy) + Expect(err).ToNot(HaveOccurred()) + Expect(policy).Should(HaveKey("someKey")) + Expect(policy["someKey"]).To(Equal("someValue")) + }) + }) + + Context("when set by user", func() { + It("appends exceptions to the existing policy", func() { + // first set it + n := NetConf{} + n.ApplyOutboundNatPolicy("192.168.0.0/16") + + // then attempt to update it + n.ApplyOutboundNatPolicy("10.244.0.0/16") + + // it should be unchanged! + addlArgs := n.Policies + Expect(addlArgs).Should(HaveLen(1)) + + policy := addlArgs[0] + Expect(policy.Name).Should(Equal("EndpointPolicy")) + + var value map[string]interface{} + json.Unmarshal(policy.Value, &value) + + Expect(value).Should(HaveKey("Type")) + Expect(value).Should(HaveKey("ExceptionList")) + Expect(value["Type"]).Should(Equal("OutBoundNAT")) + + exceptionList := value["ExceptionList"].([]interface{}) + Expect(exceptionList).Should(HaveLen(2)) + Expect(exceptionList[0].(string)).Should(Equal("192.168.0.0/16")) + Expect(exceptionList[1].(string)).Should(Equal("10.244.0.0/16")) + }) + }) + }) +}) diff --git a/vendor/github.com/containernetworking/plugins/pkg/ip/ipforward_linux_test.go b/vendor/github.com/containernetworking/plugins/pkg/ip/ipforward_linux_test.go index eeedcc2e0..c789bb899 100644 --- a/vendor/github.com/containernetworking/plugins/pkg/ip/ipforward_linux_test.go +++ b/vendor/github.com/containernetworking/plugins/pkg/ip/ipforward_linux_test.go @@ -12,6 +12,7 @@ import ( var _ = Describe("IpforwardLinux", func() { It("echo1 must not write the file if content is 1", func() { file, err := ioutil.TempFile(os.TempDir(), "containernetworking") + Expect(err).NotTo(HaveOccurred()) defer os.Remove(file.Name()) err = echo1(file.Name()) Expect(err).NotTo(HaveOccurred()) diff --git a/vendor/github.com/containernetworking/plugins/pkg/ip/ipmasq_linux.go b/vendor/github.com/containernetworking/plugins/pkg/ip/ipmasq_linux.go index 892667bdf..cc640a605 100644 --- a/vendor/github.com/containernetworking/plugins/pkg/ip/ipmasq_linux.go +++ b/vendor/github.com/containernetworking/plugins/pkg/ip/ipmasq_linux.go @@ -22,7 +22,7 @@ import ( ) // SetupIPMasq installs iptables rules to masquerade traffic -// coming from ipn and going outside of it +// coming from ip of ipn and going outside of ipn func SetupIPMasq(ipn *net.IPNet, chain string, comment string) error { isV6 := ipn.IP.To4() == nil @@ -70,7 +70,8 @@ func SetupIPMasq(ipn *net.IPNet, chain string, comment string) error { return err } - return ipt.AppendUnique("nat", "POSTROUTING", "-s", ipn.String(), "-j", chain, "-m", "comment", "--comment", comment) + // Packets from the specific IP of this network will hit the chain + return ipt.AppendUnique("nat", "POSTROUTING", "-s", ipn.IP.String(), "-j", chain, "-m", "comment", "--comment", comment) } // TeardownIPMasq undoes the effects of SetupIPMasq @@ -89,6 +90,12 @@ func TeardownIPMasq(ipn *net.IPNet, chain string, comment string) error { return fmt.Errorf("failed to locate iptables: %v", err) } + err = ipt.Delete("nat", "POSTROUTING", "-s", ipn.IP.String(), "-j", chain, "-m", "comment", "--comment", comment) + if err != nil && !isNotExist(err) { + return err + } + + // for downward compatibility err = ipt.Delete("nat", "POSTROUTING", "-s", ipn.String(), "-j", chain, "-m", "comment", "--comment", comment) if err != nil && !isNotExist(err) { return err diff --git a/vendor/github.com/containernetworking/plugins/pkg/ip/utils_linux.go b/vendor/github.com/containernetworking/plugins/pkg/ip/utils_linux.go new file mode 100644 index 000000000..7623c5e13 --- /dev/null +++ b/vendor/github.com/containernetworking/plugins/pkg/ip/utils_linux.go @@ -0,0 +1,120 @@ +// +build linux + +// Copyright 2016 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 ip + +import ( + "fmt" + "net" + + "github.com/containernetworking/cni/pkg/types" + "github.com/containernetworking/cni/pkg/types/current" + "github.com/vishvananda/netlink" +) + +func ValidateExpectedInterfaceIPs(ifName string, resultIPs []*current.IPConfig) error { + + // Ensure ips + for _, ips := range resultIPs { + ourAddr := netlink.Addr{IPNet: &ips.Address} + match := false + + link, err := netlink.LinkByName(ifName) + if err != nil { + return fmt.Errorf("Cannot find container link %v", ifName) + } + + addrList, err := netlink.AddrList(link, netlink.FAMILY_ALL) + if err != nil { + return fmt.Errorf("Cannot obtain List of IP Addresses") + } + + for _, addr := range addrList { + if addr.Equal(ourAddr) { + match = true + break + } + } + if match == false { + return fmt.Errorf("Failed to match addr %v on interface %v", ourAddr, ifName) + } + + // Convert the host/prefixlen to just prefix for route lookup. + _, ourPrefix, err := net.ParseCIDR(ourAddr.String()) + + findGwy := &netlink.Route{Dst: ourPrefix} + routeFilter := netlink.RT_FILTER_DST + var family int + + switch { + case ips.Version == "4": + family = netlink.FAMILY_V4 + case ips.Version == "6": + family = netlink.FAMILY_V6 + default: + return fmt.Errorf("Invalid IP Version %v for interface %v", ips.Version, ifName) + } + + gwy, err := netlink.RouteListFiltered(family, findGwy, routeFilter) + if err != nil { + return fmt.Errorf("Error %v trying to find Gateway %v for interface %v", err, ips.Gateway, ifName) + } + if gwy == nil { + return fmt.Errorf("Failed to find Gateway %v for interface %v", ips.Gateway, ifName) + } + } + + return nil +} + +func ValidateExpectedRoute(resultRoutes []*types.Route) error { + + // Ensure that each static route in prevResults is found in the routing table + for _, route := range resultRoutes { + find := &netlink.Route{Dst: &route.Dst, Gw: route.GW} + routeFilter := netlink.RT_FILTER_DST | netlink.RT_FILTER_GW + var family int + + switch { + case route.Dst.IP.To4() != nil: + family = netlink.FAMILY_V4 + // Default route needs Dst set to nil + if route.Dst.String() == "0.0.0.0/0" { + find = &netlink.Route{Dst: nil, Gw: route.GW} + routeFilter = netlink.RT_FILTER_DST + } + case len(route.Dst.IP) == net.IPv6len: + family = netlink.FAMILY_V6 + // Default route needs Dst set to nil + if route.Dst.String() == "::/0" { + find = &netlink.Route{Dst: nil, Gw: route.GW} + routeFilter = netlink.RT_FILTER_DST + } + default: + return fmt.Errorf("Invalid static route found %v", route) + } + + wasFound, err := netlink.RouteListFiltered(family, find, routeFilter) + if err != nil { + return fmt.Errorf("Expected Route %v not route table lookup error %v", route, err) + } + if wasFound == nil { + return fmt.Errorf("Expected Route %v not found in routing table", route) + } + } + + return nil +} diff --git a/vendor/github.com/containernetworking/plugins/pkg/ipam/ipam.go b/vendor/github.com/containernetworking/plugins/pkg/ipam/ipam.go index 904b25577..aeb25816b 100644 --- a/vendor/github.com/containernetworking/plugins/pkg/ipam/ipam.go +++ b/vendor/github.com/containernetworking/plugins/pkg/ipam/ipam.go @@ -15,14 +15,29 @@ package ipam import ( + "context" + "fmt" "github.com/containernetworking/cni/pkg/invoke" "github.com/containernetworking/cni/pkg/types" + "os" ) func ExecAdd(plugin string, netconf []byte) (types.Result, error) { - return invoke.DelegateAdd(plugin, netconf) + return invoke.DelegateAdd(context.TODO(), plugin, netconf, nil) +} + +func ExecCheck(plugin string, netconf []byte) error { + return invoke.DelegateCheck(context.TODO(), plugin, netconf, nil) } func ExecDel(plugin string, netconf []byte) error { - return invoke.DelegateDel(plugin, netconf) + cmd := os.Getenv("CNI_COMMAND") + if cmd == "" { + return fmt.Errorf("environment variable CNI_COMMAND must be specified.") + } + // Set CNI_COMMAND to DEL explicity. We might be deleting due to an ADD gone wrong. + // restore CNI_COMMAND to original value upon return. + os.Setenv("CNI_COMMAND", "DEL") + defer os.Setenv("CNI_COMMAND", cmd) + return invoke.DelegateDel(context.TODO(), plugin, netconf, nil) } diff --git a/vendor/github.com/containernetworking/plugins/pkg/ns/README.md b/vendor/github.com/containernetworking/plugins/pkg/ns/README.md index 751d5db02..1e265c7a0 100644 --- a/vendor/github.com/containernetworking/plugins/pkg/ns/README.md +++ b/vendor/github.com/containernetworking/plugins/pkg/ns/README.md @@ -24,13 +24,13 @@ err = targetNs.Do(func(hostNs ns.NetNS) error { Note this requirement to wrap every network call is very onerous - any libraries you call might call out to network services such as DNS, and all such calls need to be protected after you call `ns.Do()`. All goroutines spawned from within the `ns.Do` will not inherit the new namespace. The CNI plugins all exit very soon after calling `ns.Do()` which helps to minimize the problem. -When a new thread is spawned in Linux, it inherits the namepaces of its parent. In versions of go **prior to 1.10**, if the runtime spawns a new OS thread, it picks the parent randomly. If the chosen parent thread has been moved to a new namespace (even temporarily), the new OS thread will be permanently "stuck in the wrong namespace", and goroutines will non-deterministically switch namespaces as they are rescheduled. +When a new thread is spawned in Linux, it inherits the namespace of its parent. In versions of go **prior to 1.10**, if the runtime spawns a new OS thread, it picks the parent randomly. If the chosen parent thread has been moved to a new namespace (even temporarily), the new OS thread will be permanently "stuck in the wrong namespace", and goroutines will non-deterministically switch namespaces as they are rescheduled. In short, **there was no safe way to change network namespaces, even temporarily, from within a long-lived, multithreaded Go process**. If you wish to do this, you must use go 1.10 or greater. ### Creating network namespaces -Earlier versions of this library managed namespace creation, but as CNI does not actually utilize this feature (and it was essentialy unmaintained), it was removed. If you're writing a container runtime, you should implement namespace management yourself. However, there are some gotchas when doing so, especially around handling `/var/run/netns`. A reasonably correct reference implementation, borrowed from `rkt`, can be found in `pkg/testutils/netns_linux.go` if you're in need of a source of inspiration. +Earlier versions of this library managed namespace creation, but as CNI does not actually utilize this feature (and it was essentially unmaintained), it was removed. If you're writing a container runtime, you should implement namespace management yourself. However, there are some gotchas when doing so, especially around handling `/var/run/netns`. A reasonably correct reference implementation, borrowed from `rkt`, can be found in `pkg/testutils/netns_linux.go` if you're in need of a source of inspiration. ### Further Reading diff --git a/vendor/github.com/containernetworking/plugins/pkg/ns/ns_linux_test.go b/vendor/github.com/containernetworking/plugins/pkg/ns/ns_linux_test.go index b375802ce..26e1ecd1d 100644 --- a/vendor/github.com/containernetworking/plugins/pkg/ns/ns_linux_test.go +++ b/vendor/github.com/containernetworking/plugins/pkg/ns/ns_linux_test.go @@ -1,4 +1,4 @@ -// Copyright 2016 CNI authors +// Copyright 2016-2018 CNI authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -112,6 +112,7 @@ var _ = Describe("Linux namespace operations", func() { Expect(hostNSInode).To(Equal(origNSInode)) return nil }) + Expect(err).NotTo(HaveOccurred()) return nil }) Expect(err).NotTo(HaveOccurred()) @@ -224,6 +225,7 @@ var _ = Describe("Linux namespace operations", func() { Describe("IsNSorErr", func() { It("should detect a namespace", func() { createdNetNS, err := testutils.NewNS() + Expect(err).NotTo(HaveOccurred()) defer testutils.UnmountNS(createdNetNS) err = ns.IsNSorErr(createdNetNS.Path()) Expect(err).NotTo(HaveOccurred()) diff --git a/vendor/github.com/containernetworking/plugins/pkg/testutils/cmd.go b/vendor/github.com/containernetworking/plugins/pkg/testutils/cmd.go index b4e71fa53..ce600f683 100644 --- a/vendor/github.com/containernetworking/plugins/pkg/testutils/cmd.go +++ b/vendor/github.com/containernetworking/plugins/pkg/testutils/cmd.go @@ -81,6 +81,21 @@ func CmdAddWithArgs(args *skel.CmdArgs, f func() error) (types.Result, []byte, e return CmdAdd(args.Netns, args.ContainerID, args.IfName, args.StdinData, f) } +func CmdCheck(cniNetns, cniContainerID, cniIfname string, conf []byte, f func() error) error { + os.Setenv("CNI_COMMAND", "CHECK") + os.Setenv("CNI_PATH", os.Getenv("PATH")) + os.Setenv("CNI_NETNS", cniNetns) + os.Setenv("CNI_IFNAME", cniIfname) + os.Setenv("CNI_CONTAINERID", cniContainerID) + defer envCleanup() + + return f() +} + +func CmdCheckWithArgs(args *skel.CmdArgs, f func() error) error { + return CmdCheck(args.Netns, args.ContainerID, args.IfName, args.StdinData, f) +} + func CmdDel(cniNetns, cniContainerID, cniIfname string, f func() error) error { os.Setenv("CNI_COMMAND", "DEL") os.Setenv("CNI_PATH", os.Getenv("PATH")) diff --git a/vendor/github.com/containernetworking/plugins/pkg/utils/buildversion/buildversion.go b/vendor/github.com/containernetworking/plugins/pkg/utils/buildversion/buildversion.go new file mode 100644 index 000000000..734d4189f --- /dev/null +++ b/vendor/github.com/containernetworking/plugins/pkg/utils/buildversion/buildversion.go @@ -0,0 +1,26 @@ +// 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. + +// Buildversion is a destination for the linker trickery so we can auto +// set the build-version +package buildversion + +import "fmt" + +// This is overridden in the linker script +var BuildVersion = "version unknown" + +func BuildString(pluginName string) string { + return fmt.Sprintf("CNI %s plugin %s", pluginName, BuildVersion) +} diff --git a/vendor/github.com/containernetworking/plugins/plugins/ipam/dhcp/README.md b/vendor/github.com/containernetworking/plugins/plugins/ipam/dhcp/README.md index 0ec4c4cb8..8b46180fd 100644 --- a/vendor/github.com/containernetworking/plugins/plugins/ipam/dhcp/README.md +++ b/vendor/github.com/containernetworking/plugins/plugins/ipam/dhcp/README.md @@ -18,7 +18,7 @@ $ ./dhcp daemon If given `-pidfile ` arguments after 'daemon', the dhcp plugin will write its PID to the given file. -If given `-hostprefix ` arguments after 'daemon', the dhcp plugin will use this prefix for netns as `/`. It could be used in case of running dhcp daemon as container. +If given `-hostprefix ` arguments after 'daemon', the dhcp plugin will use this prefix for DHCP socket as `/run/cni/dhcp.sock`. You can use this prefix for references to the host filesystem, e.g. to access netns and the unix socket. Alternatively, you can use systemd socket activation protocol. Be sure that the .socket file uses /run/cni/dhcp.sock as the socket path. diff --git a/vendor/github.com/containernetworking/plugins/plugins/ipam/dhcp/client.go b/vendor/github.com/containernetworking/plugins/plugins/ipam/dhcp/client.go new file mode 100644 index 000000000..9704aab51 --- /dev/null +++ b/vendor/github.com/containernetworking/plugins/plugins/ipam/dhcp/client.go @@ -0,0 +1,121 @@ +package main + +import ( + "github.com/d2g/dhcp4" + "github.com/d2g/dhcp4client" +) + +const ( + MaxDHCPLen = 576 +) + +//Send the Discovery Packet to the Broadcast Channel +func DhcpSendDiscoverPacket(c *dhcp4client.Client, options dhcp4.Options) (dhcp4.Packet, error) { + discoveryPacket := c.DiscoverPacket() + + for opt, data := range options { + discoveryPacket.AddOption(opt, data) + } + + discoveryPacket.PadToMinSize() + return discoveryPacket, c.SendPacket(discoveryPacket) +} + +//Send Request Based On the offer Received. +func DhcpSendRequest(c *dhcp4client.Client, options dhcp4.Options, offerPacket *dhcp4.Packet) (dhcp4.Packet, error) { + requestPacket := c.RequestPacket(offerPacket) + + for opt, data := range options { + requestPacket.AddOption(opt, data) + } + + requestPacket.PadToMinSize() + + return requestPacket, c.SendPacket(requestPacket) +} + +//Send Decline to the received acknowledgement. +func DhcpSendDecline(c *dhcp4client.Client, acknowledgementPacket *dhcp4.Packet, options dhcp4.Options) (dhcp4.Packet, error) { + declinePacket := c.DeclinePacket(acknowledgementPacket) + + for opt, data := range options { + declinePacket.AddOption(opt, data) + } + + declinePacket.PadToMinSize() + + return declinePacket, c.SendPacket(declinePacket) +} + +//Lets do a Full DHCP Request. +func DhcpRequest(c *dhcp4client.Client, options dhcp4.Options) (bool, dhcp4.Packet, error) { + discoveryPacket, err := DhcpSendDiscoverPacket(c, options) + if err != nil { + return false, discoveryPacket, err + } + + offerPacket, err := c.GetOffer(&discoveryPacket) + if err != nil { + return false, offerPacket, err + } + + requestPacket, err := DhcpSendRequest(c, options, &offerPacket) + if err != nil { + return false, requestPacket, err + } + + acknowledgement, err := c.GetAcknowledgement(&requestPacket) + if err != nil { + return false, acknowledgement, err + } + + acknowledgementOptions := acknowledgement.ParseOptions() + if dhcp4.MessageType(acknowledgementOptions[dhcp4.OptionDHCPMessageType][0]) != dhcp4.ACK { + return false, acknowledgement, nil + } + + return true, acknowledgement, nil +} + +//Renew a lease backed on the Acknowledgement Packet. +//Returns Successful, The AcknoledgementPacket, Any Errors +func DhcpRenew(c *dhcp4client.Client, acknowledgement dhcp4.Packet, options dhcp4.Options) (bool, dhcp4.Packet, error) { + renewRequest := c.RenewalRequestPacket(&acknowledgement) + + for opt, data := range options { + renewRequest.AddOption(opt, data) + } + + renewRequest.PadToMinSize() + + err := c.SendPacket(renewRequest) + if err != nil { + return false, renewRequest, err + } + + newAcknowledgement, err := c.GetAcknowledgement(&renewRequest) + if err != nil { + return false, newAcknowledgement, err + } + + newAcknowledgementOptions := newAcknowledgement.ParseOptions() + if dhcp4.MessageType(newAcknowledgementOptions[dhcp4.OptionDHCPMessageType][0]) != dhcp4.ACK { + return false, newAcknowledgement, nil + } + + return true, newAcknowledgement, nil +} + +//Release a lease backed on the Acknowledgement Packet. +//Returns Any Errors +func DhcpRelease(c *dhcp4client.Client, acknowledgement dhcp4.Packet, options dhcp4.Options) error { + release := c.ReleasePacket(&acknowledgement) + + for opt, data := range options { + release.AddOption(opt, data) + } + + release.PadToMinSize() + + return c.SendPacket(release) +} diff --git a/vendor/github.com/containernetworking/plugins/plugins/ipam/dhcp/daemon.go b/vendor/github.com/containernetworking/plugins/plugins/ipam/dhcp/daemon.go index 2404db8f4..aeb87aaab 100644 --- a/vendor/github.com/containernetworking/plugins/plugins/ipam/dhcp/daemon.go +++ b/vendor/github.com/containernetworking/plugins/plugins/ipam/dhcp/daemon.go @@ -50,6 +50,10 @@ func newDHCP() *DHCP { } } +func generateClientID(containerID string, netName string, ifName string) string { + return containerID + "/" + netName + "/" + ifName +} + // Allocate acquires an IP from a DHCP server for a specified container. // The acquired lease will be maintained until Release() is called. func (d *DHCP) Allocate(args *skel.CmdArgs, result *current.Result) error { @@ -58,7 +62,7 @@ func (d *DHCP) Allocate(args *skel.CmdArgs, result *current.Result) error { return fmt.Errorf("error parsing netconf: %v", err) } - clientID := args.ContainerID + "/" + conf.Name + clientID := generateClientID(args.ContainerID, conf.Name, args.IfName) hostNetns := d.hostNetnsPrefix + args.Netns l, err := AcquireLease(clientID, hostNetns, args.IfName) if err != nil { @@ -71,7 +75,7 @@ func (d *DHCP) Allocate(args *skel.CmdArgs, result *current.Result) error { return err } - d.setLease(args.ContainerID, conf.Name, l) + d.setLease(clientID, l) result.IPs = []*current.IPConfig{{ Version: "4", @@ -91,43 +95,45 @@ func (d *DHCP) Release(args *skel.CmdArgs, reply *struct{}) error { return fmt.Errorf("error parsing netconf: %v", err) } - if l := d.getLease(args.ContainerID, conf.Name); l != nil { + clientID := generateClientID(args.ContainerID, conf.Name, args.IfName) + if l := d.getLease(clientID); l != nil { l.Stop() - d.clearLease(args.ContainerID, conf.Name) + d.clearLease(clientID) } return nil } -func (d *DHCP) getLease(contID, netName string) *DHCPLease { +func (d *DHCP) getLease(clientID string) *DHCPLease { d.mux.Lock() defer d.mux.Unlock() // TODO(eyakubovich): hash it to avoid collisions - l, ok := d.leases[contID+netName] + l, ok := d.leases[clientID] if !ok { return nil } return l } -func (d *DHCP) setLease(contID, netName string, l *DHCPLease) { +func (d *DHCP) setLease(clientID string, l *DHCPLease) { d.mux.Lock() defer d.mux.Unlock() // TODO(eyakubovich): hash it to avoid collisions - d.leases[contID+netName] = l + d.leases[clientID] = l } -func (d *DHCP) clearLease(contID, netName string) { +//func (d *DHCP) clearLease(contID, netName, ifName string) { +func (d *DHCP) clearLease(clientID string) { d.mux.Lock() defer d.mux.Unlock() // TODO(eyakubovich): hash it to avoid collisions - delete(d.leases, contID+netName) + delete(d.leases, clientID) } -func getListener() (net.Listener, error) { +func getListener(socketPath string) (net.Listener, error) { l, err := activation.Listeners() if err != nil { return nil, err @@ -151,7 +157,7 @@ func getListener() (net.Listener, error) { } } -func runDaemon(pidfilePath string, hostPrefix string) error { +func runDaemon(pidfilePath string, hostPrefix string, socketPath string) error { // since other goroutines (on separate threads) will change namespaces, // ensure the RPC server does not get scheduled onto those runtime.LockOSThread() @@ -166,7 +172,7 @@ func runDaemon(pidfilePath string, hostPrefix string) error { } } - l, err := getListener() + l, err := getListener(hostPrefix + socketPath) if err != nil { return fmt.Errorf("Error getting listener: %v", err) } diff --git a/vendor/github.com/containernetworking/plugins/plugins/ipam/dhcp/dhcp2_test.go b/vendor/github.com/containernetworking/plugins/plugins/ipam/dhcp/dhcp2_test.go new file mode 100644 index 000000000..fecd601e7 --- /dev/null +++ b/vendor/github.com/containernetworking/plugins/plugins/ipam/dhcp/dhcp2_test.go @@ -0,0 +1,183 @@ +// Copyright 2015-2018 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 main + +import ( + "fmt" + "net" + "os" + "os/exec" + "sync" + "time" + + "github.com/containernetworking/cni/pkg/skel" + "github.com/containernetworking/cni/pkg/types/current" + "github.com/containernetworking/plugins/pkg/ns" + "github.com/containernetworking/plugins/pkg/testutils" + + "github.com/vishvananda/netlink" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("DHCP Multiple Lease Operations", func() { + var originalNS, targetNS ns.NetNS + var dhcpServerStopCh chan bool + var dhcpServerDone *sync.WaitGroup + var clientCmd *exec.Cmd + var socketPath string + var tmpDir string + var serverIP net.IPNet + var err error + + BeforeEach(func() { + dhcpServerStopCh, serverIP, socketPath, originalNS, targetNS, err = dhcpSetupOriginalNS() + Expect(err).NotTo(HaveOccurred()) + + // Move the container side to the container's NS + err = targetNS.Do(func(_ ns.NetNS) error { + defer GinkgoRecover() + + link, err := netlink.LinkByName(contVethName0) + Expect(err).NotTo(HaveOccurred()) + err = netlink.LinkSetUp(link) + Expect(err).NotTo(HaveOccurred()) + + link1, err := netlink.LinkByName(contVethName1) + Expect(err).NotTo(HaveOccurred()) + err = netlink.LinkSetUp(link1) + Expect(err).NotTo(HaveOccurred()) + return nil + }) + + // Start the DHCP server + dhcpServerDone, err = dhcpServerStart(originalNS, net.IPv4(192, 168, 1, 5), serverIP.IP, 2, dhcpServerStopCh) + Expect(err).NotTo(HaveOccurred()) + + // Start the DHCP client daemon + dhcpPluginPath, err := exec.LookPath("dhcp") + Expect(err).NotTo(HaveOccurred()) + clientCmd = exec.Command(dhcpPluginPath, "daemon", "-socketpath", socketPath) + err = clientCmd.Start() + Expect(err).NotTo(HaveOccurred()) + Expect(clientCmd.Process).NotTo(BeNil()) + + // Wait up to 15 seconds for the client socket + Eventually(func() bool { + _, err := os.Stat(socketPath) + return err == nil + }, time.Second*15, time.Second/4).Should(BeTrue()) + }) + + AfterEach(func() { + dhcpServerStopCh <- true + dhcpServerDone.Wait() + clientCmd.Process.Kill() + clientCmd.Wait() + + Expect(originalNS.Close()).To(Succeed()) + Expect(targetNS.Close()).To(Succeed()) + defer os.RemoveAll(tmpDir) + }) + + It("configures multiple links with multiple ADD/DEL", func() { + conf := fmt.Sprintf(`{ + "cniVersion": "0.3.1", + "name": "mynet", + "type": "bridge", + "bridge": "%s", + "ipam": { + "type": "dhcp", + "daemonSocketPath": "%s" + } + }`, hostBridgeName, socketPath) + + args := &skel.CmdArgs{ + ContainerID: "dummy", + Netns: targetNS.Path(), + IfName: contVethName0, + StdinData: []byte(conf), + } + + var addResult *current.Result + err := originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + r, _, err := testutils.CmdAddWithArgs(args, func() error { + return cmdAdd(args) + }) + Expect(err).NotTo(HaveOccurred()) + + addResult, err = current.GetResult(r) + Expect(err).NotTo(HaveOccurred()) + Expect(len(addResult.IPs)).To(Equal(1)) + Expect(addResult.IPs[0].Address.String()).To(Equal("192.168.1.5/24")) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + + args = &skel.CmdArgs{ + ContainerID: "dummy", + Netns: targetNS.Path(), + IfName: contVethName1, + StdinData: []byte(conf), + } + + err = originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + r, _, err := testutils.CmdAddWithArgs(args, func() error { + return cmdAdd(args) + }) + Expect(err).NotTo(HaveOccurred()) + + addResult, err = current.GetResult(r) + Expect(err).NotTo(HaveOccurred()) + Expect(len(addResult.IPs)).To(Equal(1)) + Expect(addResult.IPs[0].Address.String()).To(Equal("192.168.1.6/24")) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + + args = &skel.CmdArgs{ + ContainerID: "dummy", + Netns: targetNS.Path(), + IfName: contVethName1, + StdinData: []byte(conf), + } + + err = originalNS.Do(func(ns.NetNS) error { + return testutils.CmdDelWithArgs(args, func() error { + return cmdDel(args) + }) + }) + Expect(err).NotTo(HaveOccurred()) + + args = &skel.CmdArgs{ + ContainerID: "dummy", + Netns: targetNS.Path(), + IfName: contVethName0, + StdinData: []byte(conf), + } + + err = originalNS.Do(func(ns.NetNS) error { + return testutils.CmdDelWithArgs(args, func() error { + return cmdDel(args) + }) + }) + Expect(err).NotTo(HaveOccurred()) + }) +}) diff --git a/vendor/github.com/containernetworking/plugins/plugins/ipam/dhcp/dhcp_test.go b/vendor/github.com/containernetworking/plugins/plugins/ipam/dhcp/dhcp_test.go index 5fe743533..560745ca2 100644 --- a/vendor/github.com/containernetworking/plugins/plugins/ipam/dhcp/dhcp_test.go +++ b/vendor/github.com/containernetworking/plugins/plugins/ipam/dhcp/dhcp_test.go @@ -1,4 +1,4 @@ -// Copyright 2015 CNI authors +// Copyright 2015-2018 CNI authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -16,9 +16,11 @@ package main import ( "fmt" + "io/ioutil" "net" "os" "os/exec" + "path/filepath" "sync" "time" @@ -38,12 +40,29 @@ import ( . "github.com/onsi/gomega" ) -func dhcpServerStart(netns ns.NetNS, leaseIP, serverIP net.IP, stopCh <-chan bool) (*sync.WaitGroup, error) { +func getTmpDir() (string, error) { + tmpDir, err := ioutil.TempDir(cniDirPrefix, "dhcp") + if err == nil { + tmpDir = filepath.ToSlash(tmpDir) + } + + return tmpDir, err +} + +func dhcpServerStart(netns ns.NetNS, leaseIP, serverIP net.IP, numLeases int, stopCh <-chan bool) (*sync.WaitGroup, error) { // Add the expected IP to the pool lp := memorypool.MemoryPool{} - err := lp.AddLease(leasepool.Lease{IP: dhcp4.IPAdd(net.IPv4(192, 168, 1, 5), 0)}) - if err != nil { - return nil, fmt.Errorf("error adding IP to DHCP pool: %v", err) + + Expect(numLeases).To(BeNumerically(">", 0)) + // Currently tests only need at most 2 + Expect(numLeases).To(BeNumerically("<=", 2)) + + // tests expect first lease to be at address 192.168.1.5 + for i := 5; i < numLeases+5; i++ { + err := lp.AddLease(leasepool.Lease{IP: dhcp4.IPAdd(net.IPv4(192, 168, 1, byte(i)), 0)}) + if err != nil { + return nil, fmt.Errorf("error adding IP to DHCP pool: %v", err) + } } dhcpServer, err := dhcp4server.New( @@ -96,17 +115,12 @@ func dhcpServerStart(netns ns.NetNS, leaseIP, serverIP net.IP, stopCh <-chan boo const ( hostVethName string = "dhcp0" contVethName string = "eth0" - pidfilePath string = "/var/run/cni/dhcp-client.pid" + cniDirPrefix string = "/var/run/cni" ) var _ = BeforeSuite(func() { - os.Remove(socketPath) - os.Remove(pidfilePath) -}) - -var _ = AfterSuite(func() { - os.Remove(socketPath) - os.Remove(pidfilePath) + err := os.MkdirAll(cniDirPrefix, 0700) + Expect(err).NotTo(HaveOccurred()) }) var _ = Describe("DHCP Operations", func() { @@ -114,10 +128,17 @@ var _ = Describe("DHCP Operations", func() { var dhcpServerStopCh chan bool var dhcpServerDone *sync.WaitGroup var clientCmd *exec.Cmd + var socketPath string + var tmpDir string + var err error BeforeEach(func() { dhcpServerStopCh = make(chan bool) + tmpDir, err = getTmpDir() + Expect(err).NotTo(HaveOccurred()) + socketPath = filepath.Join(tmpDir, "dhcp.sock") + // Create a new NetNS so we don't modify the host var err error originalNS, err = testutils.NewNS() @@ -157,6 +178,7 @@ var _ = Describe("DHCP Operations", func() { Mask: net.IPv4Mask(0, 0, 0, 0), }, }) + Expect(err).NotTo(HaveOccurred()) cont, err := netlink.LinkByName(contVethName) Expect(err).NotTo(HaveOccurred()) @@ -179,14 +201,13 @@ var _ = Describe("DHCP Operations", func() { }) // Start the DHCP server - dhcpServerDone, err = dhcpServerStart(originalNS, net.IPv4(192, 168, 1, 5), serverIP.IP, dhcpServerStopCh) + dhcpServerDone, err = dhcpServerStart(originalNS, net.IPv4(192, 168, 1, 5), serverIP.IP, 1, dhcpServerStopCh) Expect(err).NotTo(HaveOccurred()) // Start the DHCP client daemon - os.MkdirAll(pidfilePath, 0755) dhcpPluginPath, err := exec.LookPath("dhcp") Expect(err).NotTo(HaveOccurred()) - clientCmd = exec.Command(dhcpPluginPath, "daemon") + clientCmd = exec.Command(dhcpPluginPath, "daemon", "-socketpath", socketPath) err = clientCmd.Start() Expect(err).NotTo(HaveOccurred()) Expect(clientCmd.Process).NotTo(BeNil()) @@ -206,19 +227,19 @@ var _ = Describe("DHCP Operations", func() { Expect(originalNS.Close()).To(Succeed()) Expect(targetNS.Close()).To(Succeed()) - os.Remove(socketPath) - os.Remove(pidfilePath) + defer os.RemoveAll(tmpDir) }) It("configures and deconfigures a link with ADD/DEL", func() { - conf := `{ + conf := fmt.Sprintf(`{ "cniVersion": "0.3.1", "name": "mynet", "type": "ipvlan", "ipam": { - "type": "dhcp" + "type": "dhcp", + "daemonSocketPath": "%s" } -}` +}`, socketPath) args := &skel.CmdArgs{ ContainerID: "dummy", @@ -253,14 +274,15 @@ var _ = Describe("DHCP Operations", func() { }) It("correctly handles multiple DELs for the same container", func() { - conf := `{ + conf := fmt.Sprintf(`{ "cniVersion": "0.3.1", "name": "mynet", "type": "ipvlan", "ipam": { - "type": "dhcp" + "type": "dhcp", + "daemonSocketPath": "%s" } -}` +}`, socketPath) args := &skel.CmdArgs{ ContainerID: "dummy", @@ -317,3 +339,280 @@ var _ = Describe("DHCP Operations", func() { Expect(err).NotTo(HaveOccurred()) }) }) + +const ( + hostBridgeName string = "dhcpbr0" + hostVethName0 string = "br-eth0" + contVethName0 string = "eth0" + hostVethName1 string = "br-eth1" + contVethName1 string = "eth1" +) + +func dhcpSetupOriginalNS() (chan bool, net.IPNet, string, ns.NetNS, ns.NetNS, error) { + var originalNS, targetNS ns.NetNS + var dhcpServerStopCh chan bool + var socketPath string + var br *netlink.Bridge + var tmpDir string + var err error + + dhcpServerStopCh = make(chan bool) + + tmpDir, err = getTmpDir() + Expect(err).NotTo(HaveOccurred()) + socketPath = filepath.Join(tmpDir, "dhcp.sock") + + // Create a new NetNS so we don't modify the host + originalNS, err = testutils.NewNS() + Expect(err).NotTo(HaveOccurred()) + + targetNS, err = testutils.NewNS() + Expect(err).NotTo(HaveOccurred()) + + serverIP := net.IPNet{ + IP: net.IPv4(192, 168, 1, 1), + Mask: net.IPv4Mask(255, 255, 255, 0), + } + + // Use (original) NS + err = originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + // Create bridge in the "host" (original) NS + br = &netlink.Bridge{ + LinkAttrs: netlink.LinkAttrs{ + Name: hostBridgeName, + }, + } + + err = netlink.LinkAdd(br) + Expect(err).NotTo(HaveOccurred()) + + address := &netlink.Addr{IPNet: &net.IPNet{ + IP: net.IPv4(192, 168, 1, 1), + Mask: net.IPv4Mask(255, 255, 255, 0), + }} + err = netlink.AddrAdd(br, address) + Expect(err).NotTo(HaveOccurred()) + + err = netlink.LinkSetUp(br) + Expect(err).NotTo(HaveOccurred()) + + err = netlink.RouteAdd(&netlink.Route{ + LinkIndex: br.Attrs().Index, + Scope: netlink.SCOPE_UNIVERSE, + Dst: &net.IPNet{ + IP: net.IPv4(0, 0, 0, 0), + Mask: net.IPv4Mask(0, 0, 0, 0), + }, + }) + Expect(err).NotTo(HaveOccurred()) + + // Create veth pair eth0 + vethLinkAttrs := netlink.NewLinkAttrs() + vethLinkAttrs.Name = hostVethName0 + + veth := &netlink.Veth{ + LinkAttrs: vethLinkAttrs, + PeerName: contVethName0, + } + err = netlink.LinkAdd(veth) + Expect(err).NotTo(HaveOccurred()) + + err = netlink.LinkSetUp(veth) + Expect(err).NotTo(HaveOccurred()) + + bridgeLink, err := netlink.LinkByName(hostBridgeName) + Expect(err).NotTo(HaveOccurred()) + + hostVethLink, err := netlink.LinkByName(hostVethName0) + Expect(err).NotTo(HaveOccurred()) + + err = netlink.LinkSetMaster(hostVethLink, bridgeLink.(*netlink.Bridge)) + Expect(err).NotTo(HaveOccurred()) + + cont, err := netlink.LinkByName(contVethName0) + Expect(err).NotTo(HaveOccurred()) + err = netlink.LinkSetNsFd(cont, int(targetNS.Fd())) + Expect(err).NotTo(HaveOccurred()) + + // Create veth path - eth1 + vethLinkAttrs1 := netlink.NewLinkAttrs() + vethLinkAttrs1.Name = hostVethName1 + + veth1 := &netlink.Veth{ + LinkAttrs: vethLinkAttrs1, + PeerName: contVethName1, + } + err = netlink.LinkAdd(veth1) + Expect(err).NotTo(HaveOccurred()) + + err = netlink.LinkSetUp(veth1) + Expect(err).NotTo(HaveOccurred()) + + bridgeLink, err = netlink.LinkByName(hostBridgeName) + Expect(err).NotTo(HaveOccurred()) + + hostVethLink1, err := netlink.LinkByName(hostVethName1) + Expect(err).NotTo(HaveOccurred()) + + err = netlink.LinkSetMaster(hostVethLink1, bridgeLink.(*netlink.Bridge)) + Expect(err).NotTo(HaveOccurred()) + + cont1, err := netlink.LinkByName(contVethName1) + Expect(err).NotTo(HaveOccurred()) + + err = netlink.LinkSetNsFd(cont1, int(targetNS.Fd())) + Expect(err).NotTo(HaveOccurred()) + + return nil + }) + + return dhcpServerStopCh, serverIP, socketPath, originalNS, targetNS, err +} + +var _ = Describe("DHCP Lease Unavailable Operations", func() { + var originalNS, targetNS ns.NetNS + var dhcpServerStopCh chan bool + var dhcpServerDone *sync.WaitGroup + var clientCmd *exec.Cmd + var socketPath string + var tmpDir string + var serverIP net.IPNet + var err error + + BeforeEach(func() { + dhcpServerStopCh, serverIP, socketPath, originalNS, targetNS, err = dhcpSetupOriginalNS() + Expect(err).NotTo(HaveOccurred()) + + // Move the container side to the container's NS + err = targetNS.Do(func(_ ns.NetNS) error { + defer GinkgoRecover() + + link, err := netlink.LinkByName(contVethName0) + Expect(err).NotTo(HaveOccurred()) + err = netlink.LinkSetUp(link) + Expect(err).NotTo(HaveOccurred()) + + link1, err := netlink.LinkByName(contVethName1) + Expect(err).NotTo(HaveOccurred()) + err = netlink.LinkSetUp(link1) + Expect(err).NotTo(HaveOccurred()) + return nil + }) + + // Start the DHCP server + dhcpServerDone, err = dhcpServerStart(originalNS, net.IPv4(192, 168, 1, 5), serverIP.IP, 1, dhcpServerStopCh) + Expect(err).NotTo(HaveOccurred()) + + // Start the DHCP client daemon + dhcpPluginPath, err := exec.LookPath("dhcp") + Expect(err).NotTo(HaveOccurred()) + clientCmd = exec.Command(dhcpPluginPath, "daemon", "-socketpath", socketPath) + err = clientCmd.Start() + Expect(err).NotTo(HaveOccurred()) + Expect(clientCmd.Process).NotTo(BeNil()) + + // Wait up to 15 seconds for the client socket + Eventually(func() bool { + _, err := os.Stat(socketPath) + return err == nil + }, time.Second*15, time.Second/4).Should(BeTrue()) + }) + + AfterEach(func() { + dhcpServerStopCh <- true + dhcpServerDone.Wait() + clientCmd.Process.Kill() + clientCmd.Wait() + + Expect(originalNS.Close()).To(Succeed()) + Expect(targetNS.Close()).To(Succeed()) + defer os.RemoveAll(tmpDir) + }) + + It("Configures multiple links with multiple ADD with second lease unavailable", func() { + conf := fmt.Sprintf(`{ + "cniVersion": "0.3.1", + "name": "mynet", + "type": "bridge", + "bridge": "%s", + "ipam": { + "type": "dhcp", + "daemonSocketPath": "%s" + } + }`, hostBridgeName, socketPath) + + args := &skel.CmdArgs{ + ContainerID: "dummy", + Netns: targetNS.Path(), + IfName: contVethName0, + StdinData: []byte(conf), + } + + var addResult *current.Result + err := originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + r, _, err := testutils.CmdAddWithArgs(args, func() error { + return cmdAdd(args) + }) + Expect(err).NotTo(HaveOccurred()) + + addResult, err = current.GetResult(r) + Expect(err).NotTo(HaveOccurred()) + Expect(len(addResult.IPs)).To(Equal(1)) + Expect(addResult.IPs[0].Address.String()).To(Equal("192.168.1.5/24")) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + + args = &skel.CmdArgs{ + ContainerID: "dummy", + Netns: targetNS.Path(), + IfName: contVethName1, + StdinData: []byte(conf), + } + + err = originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + _, _, err := testutils.CmdAddWithArgs(args, func() error { + return cmdAdd(args) + }) + Expect(err).To(HaveOccurred()) + println(err.Error()) + Expect(err.Error()).To(Equal("error calling DHCP.Allocate: no more tries")) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + + args = &skel.CmdArgs{ + ContainerID: "dummy", + Netns: targetNS.Path(), + IfName: contVethName1, + StdinData: []byte(conf), + } + + err = originalNS.Do(func(ns.NetNS) error { + return testutils.CmdDelWithArgs(args, func() error { + return cmdDel(args) + }) + }) + Expect(err).NotTo(HaveOccurred()) + + args = &skel.CmdArgs{ + ContainerID: "dummy", + Netns: targetNS.Path(), + IfName: contVethName0, + StdinData: []byte(conf), + } + + err = originalNS.Do(func(ns.NetNS) error { + return testutils.CmdDelWithArgs(args, func() error { + return cmdDel(args) + }) + }) + Expect(err).NotTo(HaveOccurred()) + }) +}) diff --git a/vendor/github.com/containernetworking/plugins/plugins/ipam/dhcp/lease.go b/vendor/github.com/containernetworking/plugins/plugins/ipam/dhcp/lease.go index c4c1b1cdf..dc2a9d92c 100644 --- a/vendor/github.com/containernetworking/plugins/plugins/ipam/dhcp/lease.go +++ b/vendor/github.com/containernetworking/plugins/plugins/ipam/dhcp/lease.go @@ -115,7 +115,7 @@ func (l *DHCPLease) Stop() { } func (l *DHCPLease) acquire() error { - c, err := newDHCPClient(l.link) + c, err := newDHCPClient(l.link, l.clientID) if err != nil { return err } @@ -128,8 +128,12 @@ func (l *DHCPLease) acquire() error { } } + opts := make(dhcp4.Options) + opts[dhcp4.OptionClientIdentifier] = []byte(l.clientID) + opts[dhcp4.OptionParameterRequestList] = []byte{byte(dhcp4.OptionRouter), byte(dhcp4.OptionSubnetMask)} + pkt, err := backoffRetry(func() (*dhcp4.Packet, error) { - ok, ack, err := c.Request() + ok, ack, err := DhcpRequest(c, opts) switch { case err != nil: return nil, err @@ -238,14 +242,17 @@ func (l *DHCPLease) downIface() { } func (l *DHCPLease) renew() error { - c, err := newDHCPClient(l.link) + c, err := newDHCPClient(l.link, l.clientID) if err != nil { return err } defer c.Close() + opts := make(dhcp4.Options) + opts[dhcp4.OptionClientIdentifier] = []byte(l.clientID) + pkt, err := backoffRetry(func() (*dhcp4.Packet, error) { - ok, ack, err := c.Renew(*l.ack) + ok, ack, err := DhcpRenew(c, *l.ack, opts) switch { case err != nil: return nil, err @@ -266,13 +273,16 @@ func (l *DHCPLease) renew() error { func (l *DHCPLease) release() error { log.Printf("%v: releasing lease", l.clientID) - c, err := newDHCPClient(l.link) + c, err := newDHCPClient(l.link, l.clientID) if err != nil { return err } defer c.Close() - if err = c.Release(*l.ack); err != nil { + opts := make(dhcp4.Options) + opts[dhcp4.OptionClientIdentifier] = []byte(l.clientID) + + if err = DhcpRelease(c, *l.ack, opts); err != nil { return fmt.Errorf("failed to send DHCPRELEASE") } @@ -344,7 +354,7 @@ func backoffRetry(f func() (*dhcp4.Packet, error)) (*dhcp4.Packet, error) { return nil, errNoMoreTries } -func newDHCPClient(link netlink.Link) (*dhcp4client.Client, error) { +func newDHCPClient(link netlink.Link, clientID string) (*dhcp4client.Client, error) { pktsock, err := dhcp4client.NewPacketSock(link.Attrs().Index) if err != nil { return nil, err diff --git a/vendor/github.com/containernetworking/plugins/plugins/ipam/dhcp/main.go b/vendor/github.com/containernetworking/plugins/plugins/ipam/dhcp/main.go index a0400a6da..08b148ca4 100644 --- a/vendor/github.com/containernetworking/plugins/plugins/ipam/dhcp/main.go +++ b/vendor/github.com/containernetworking/plugins/plugins/ipam/dhcp/main.go @@ -15,6 +15,7 @@ package main import ( + "encoding/json" "flag" "fmt" "log" @@ -26,25 +27,32 @@ import ( "github.com/containernetworking/cni/pkg/types" "github.com/containernetworking/cni/pkg/types/current" "github.com/containernetworking/cni/pkg/version" + bv "github.com/containernetworking/plugins/pkg/utils/buildversion" ) -const socketPath = "/run/cni/dhcp.sock" +const defaultSocketPath = "/run/cni/dhcp.sock" func main() { if len(os.Args) > 1 && os.Args[1] == "daemon" { var pidfilePath string var hostPrefix string + var socketPath string daemonFlags := flag.NewFlagSet("daemon", flag.ExitOnError) daemonFlags.StringVar(&pidfilePath, "pidfile", "", "optional path to write daemon PID to") - daemonFlags.StringVar(&hostPrefix, "hostprefix", "", "optional prefix to netns") + daemonFlags.StringVar(&hostPrefix, "hostprefix", "", "optional prefix to host root") + daemonFlags.StringVar(&socketPath, "socketpath", "", "optional dhcp server socketpath") daemonFlags.Parse(os.Args[2:]) - if err := runDaemon(pidfilePath, hostPrefix); err != nil { + if socketPath == "" { + socketPath = defaultSocketPath + } + + if err := runDaemon(pidfilePath, hostPrefix, socketPath); err != nil { log.Printf(err.Error()) os.Exit(1) } } else { - skel.PluginMain(cmdAdd, cmdDel, version.All) + skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("dhcp")) } } @@ -72,7 +80,50 @@ func cmdDel(args *skel.CmdArgs) error { return nil } +func cmdCheck(args *skel.CmdArgs) error { + // TODO: implement + //return fmt.Errorf("not implemented") + // Plugin must return result in same version as specified in netconf + versionDecoder := &version.ConfigDecoder{} + //confVersion, err := versionDecoder.Decode(args.StdinData) + _, err := versionDecoder.Decode(args.StdinData) + if err != nil { + return err + } + + result := ¤t.Result{} + if err := rpcCall("DHCP.Allocate", args, result); err != nil { + return err + } + + return nil +} + +type SocketPathConf struct { + DaemonSocketPath string `json:"daemonSocketPath,omitempty"` +} + +type TempNetConf struct { + IPAM SocketPathConf `json:"ipam,omitempty"` +} + +func getSocketPath(stdinData []byte) (string, error) { + conf := TempNetConf{} + if err := json.Unmarshal(stdinData, &conf); err != nil { + return "", fmt.Errorf("error parsing socket path conf: %v", err) + } + if conf.IPAM.DaemonSocketPath == "" { + return defaultSocketPath, nil + } + return conf.IPAM.DaemonSocketPath, nil +} + func rpcCall(method string, args *skel.CmdArgs, result interface{}) error { + socketPath, err := getSocketPath(args.StdinData) + if err != nil { + return fmt.Errorf("error obtaining socketPath: %v", err) + } + client, err := rpc.DialHTTP("unix", socketPath) if err != nil { return fmt.Errorf("error dialing DHCP daemon: %v", err) diff --git a/vendor/github.com/containernetworking/plugins/plugins/ipam/dhcp/options.go b/vendor/github.com/containernetworking/plugins/plugins/ipam/dhcp/options.go index 6e2e05c6e..910e1cc61 100644 --- a/vendor/github.com/containernetworking/plugins/plugins/ipam/dhcp/options.go +++ b/vendor/github.com/containernetworking/plugins/plugins/ipam/dhcp/options.go @@ -98,7 +98,7 @@ func parseCIDRRoutes(opts dhcp4.Options) []*types.Route { } routes = append(routes, rt) - opt = opt[octets+5 : len(opt)] + opt = opt[octets+5:] } } return routes diff --git a/vendor/github.com/containernetworking/plugins/plugins/ipam/dhcp/options_test.go b/vendor/github.com/containernetworking/plugins/plugins/ipam/dhcp/options_test.go index 9f2904bc3..961070c23 100644 --- a/vendor/github.com/containernetworking/plugins/plugins/ipam/dhcp/options_test.go +++ b/vendor/github.com/containernetworking/plugins/plugins/ipam/dhcp/options_test.go @@ -24,14 +24,14 @@ import ( func validateRoutes(t *testing.T, routes []*types.Route) { expected := []*types.Route{ - &types.Route{ + { Dst: net.IPNet{ IP: net.IPv4(10, 0, 0, 0), Mask: net.CIDRMask(8, 32), }, GW: net.IPv4(10, 1, 2, 3), }, - &types.Route{ + { Dst: net.IPNet{ IP: net.IPv4(192, 168, 1, 0), Mask: net.CIDRMask(24, 32), diff --git a/vendor/github.com/containernetworking/plugins/plugins/ipam/dhcp/systemd/cni-dhcp.service b/vendor/github.com/containernetworking/plugins/plugins/ipam/dhcp/systemd/cni-dhcp.service new file mode 100644 index 000000000..d6d7bceac --- /dev/null +++ b/vendor/github.com/containernetworking/plugins/plugins/ipam/dhcp/systemd/cni-dhcp.service @@ -0,0 +1,11 @@ +[Unit] +Description=CNI DHCP service +Documentation=https://github.com/containernetworking/plugins/tree/master/plugins/ipam/dhcp +After=network.target cni-dhcp.socket +Requires=cni-dhcp.socket + +[Service] +ExecStart=/opt/cni/bin/dhcp daemon + +[Install] +WantedBy=multi-user.target diff --git a/vendor/github.com/containernetworking/plugins/plugins/ipam/dhcp/systemd/cni-dhcp.socket b/vendor/github.com/containernetworking/plugins/plugins/ipam/dhcp/systemd/cni-dhcp.socket new file mode 100644 index 000000000..8276f91ed --- /dev/null +++ b/vendor/github.com/containernetworking/plugins/plugins/ipam/dhcp/systemd/cni-dhcp.socket @@ -0,0 +1,14 @@ +[Unit] +Description=CNI DHCP service socket +Documentation=https://github.com/containernetworking/plugins/tree/master/plugins/ipam/dhcp +PartOf=cni-dhcp.service + +[Socket] +ListenStream=/run/cni/dhcp.sock +SocketMode=0660 +SocketUser=root +SocketGroup=root +RemoveOnStop=true + +[Install] +WantedBy=sockets.target diff --git a/vendor/github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator/allocator.go b/vendor/github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator/allocator.go index 1d2964b9b..d1c2b1018 100644 --- a/vendor/github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator/allocator.go +++ b/vendor/github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator/allocator.go @@ -41,7 +41,7 @@ func NewIPAllocator(s *RangeSet, store backend.Store, id int) *IPAllocator { } // Get alocates an IP -func (a *IPAllocator) Get(id string, requestedIP net.IP) (*current.IPConfig, error) { +func (a *IPAllocator) Get(id string, ifname string, requestedIP net.IP) (*current.IPConfig, error) { a.store.Lock() defer a.store.Unlock() @@ -62,7 +62,7 @@ func (a *IPAllocator) Get(id string, requestedIP net.IP) (*current.IPConfig, err return nil, fmt.Errorf("requested ip %s is subnet's gateway", requestedIP.String()) } - reserved, err := a.store.Reserve(id, requestedIP, a.rangeID) + reserved, err := a.store.Reserve(id, ifname, requestedIP, a.rangeID) if err != nil { return nil, err } @@ -83,7 +83,7 @@ func (a *IPAllocator) Get(id string, requestedIP net.IP) (*current.IPConfig, err break } - reserved, err := a.store.Reserve(id, reservedIP.IP, a.rangeID) + reserved, err := a.store.Reserve(id, ifname, reservedIP.IP, a.rangeID) if err != nil { return nil, err } @@ -110,11 +110,11 @@ func (a *IPAllocator) Get(id string, requestedIP net.IP) (*current.IPConfig, err } // Release clears all IPs allocated for the container with given ID -func (a *IPAllocator) Release(id string) error { +func (a *IPAllocator) Release(id string, ifname string) error { a.store.Lock() defer a.store.Unlock() - return a.store.ReleaseByID(id) + return a.store.ReleaseByID(id, ifname) } type RangeIter struct { diff --git a/vendor/github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator/allocator_test.go b/vendor/github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator/allocator_test.go index 436aaa521..5888c14b0 100644 --- a/vendor/github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator/allocator_test.go +++ b/vendor/github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator/allocator_test.go @@ -70,7 +70,7 @@ func (t AllocatorTestCase) run(idx int) (*current.IPConfig, error) { rangeID: "rangeid", } - return alloc.Get("ID", nil) + return alloc.Get("ID", "eth0", nil) } var _ = Describe("host-local ip allocator", func() { @@ -88,8 +88,8 @@ var _ = Describe("host-local ip allocator", func() { It("should loop correctly from the end", func() { a := mkalloc() - a.store.Reserve("ID", net.IP{192, 168, 1, 6}, a.rangeID) - a.store.ReleaseByID("ID") + a.store.Reserve("ID", "eth0", net.IP{192, 168, 1, 6}, a.rangeID) + a.store.ReleaseByID("ID", "eth0") r, _ := a.GetIter() Expect(r.nextip()).To(Equal(net.IP{192, 168, 1, 2})) Expect(r.nextip()).To(Equal(net.IP{192, 168, 1, 3})) @@ -100,8 +100,8 @@ var _ = Describe("host-local ip allocator", func() { }) It("should loop correctly from the middle", func() { a := mkalloc() - a.store.Reserve("ID", net.IP{192, 168, 1, 3}, a.rangeID) - a.store.ReleaseByID("ID") + a.store.Reserve("ID", "eth0", net.IP{192, 168, 1, 3}, a.rangeID) + a.store.ReleaseByID("ID", "eth0") r, _ := a.GetIter() Expect(r.nextip()).To(Equal(net.IP{192, 168, 1, 4})) Expect(r.nextip()).To(Equal(net.IP{192, 168, 1, 5})) @@ -221,28 +221,28 @@ var _ = Describe("host-local ip allocator", func() { It("should not allocate the broadcast address", func() { alloc := mkalloc() for i := 2; i < 7; i++ { - res, err := alloc.Get("ID", nil) + res, err := alloc.Get("ID", "eth0", nil) Expect(err).ToNot(HaveOccurred()) s := fmt.Sprintf("192.168.1.%d/29", i) Expect(s).To(Equal(res.Address.String())) fmt.Fprintln(GinkgoWriter, "got ip", res.Address.String()) } - x, err := alloc.Get("ID", nil) + x, err := alloc.Get("ID", "eth0", nil) fmt.Fprintln(GinkgoWriter, "got ip", x) Expect(err).To(HaveOccurred()) }) It("should allocate in a round-robin fashion", func() { alloc := mkalloc() - res, err := alloc.Get("ID", nil) + res, err := alloc.Get("ID", "eth0", nil) Expect(err).ToNot(HaveOccurred()) Expect(res.Address.String()).To(Equal("192.168.1.2/29")) - err = alloc.Release("ID") + err = alloc.Release("ID", "eth0") Expect(err).ToNot(HaveOccurred()) - res, err = alloc.Get("ID", nil) + res, err = alloc.Get("ID", "eth0", nil) Expect(err).ToNot(HaveOccurred()) Expect(res.Address.String()).To(Equal("192.168.1.3/29")) @@ -252,7 +252,7 @@ var _ = Describe("host-local ip allocator", func() { It("must allocate the requested IP", func() { alloc := mkalloc() requestedIP := net.IP{192, 168, 1, 5} - res, err := alloc.Get("ID", requestedIP) + res, err := alloc.Get("ID", "eth0", requestedIP) Expect(err).ToNot(HaveOccurred()) Expect(res.Address.IP.String()).To(Equal(requestedIP.String())) }) @@ -260,11 +260,11 @@ var _ = Describe("host-local ip allocator", func() { It("must fail when the requested IP is allocated", func() { alloc := mkalloc() requestedIP := net.IP{192, 168, 1, 5} - res, err := alloc.Get("ID", requestedIP) + res, err := alloc.Get("ID", "eth0", requestedIP) Expect(err).ToNot(HaveOccurred()) Expect(res.Address.IP.String()).To(Equal(requestedIP.String())) - _, err = alloc.Get("ID", requestedIP) + _, err = alloc.Get("ID", "eth0", requestedIP) Expect(err).To(MatchError(`requested IP address 192.168.1.5 is not available in range set 192.168.1.1-192.168.1.6`)) }) @@ -272,7 +272,7 @@ var _ = Describe("host-local ip allocator", func() { alloc := mkalloc() (*alloc.rangeset)[0].RangeEnd = net.IP{192, 168, 1, 4} requestedIP := net.IP{192, 168, 1, 5} - _, err := alloc.Get("ID", requestedIP) + _, err := alloc.Get("ID", "eth0", requestedIP) Expect(err).To(HaveOccurred()) }) @@ -280,7 +280,7 @@ var _ = Describe("host-local ip allocator", func() { alloc := mkalloc() (*alloc.rangeset)[0].RangeStart = net.IP{192, 168, 1, 3} requestedIP := net.IP{192, 168, 1, 2} - _, err := alloc.Get("ID", requestedIP) + _, err := alloc.Get("ID", "eth0", requestedIP) Expect(err).To(HaveOccurred()) }) }) diff --git a/vendor/github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator/config.go b/vendor/github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator/config.go index 004054fe0..c8cb2a746 100644 --- a/vendor/github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator/config.go +++ b/vendor/github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator/config.go @@ -20,7 +20,7 @@ import ( "net" "github.com/containernetworking/cni/pkg/types" - types020 "github.com/containernetworking/cni/pkg/types/020" + "github.com/containernetworking/cni/pkg/types/020" ) // The top-level network config - IPAM plugins are passed the full configuration @@ -97,7 +97,7 @@ func LoadIPAMConfig(bytes []byte, envArgs string) (*IPAMConfig, string, error) { n.IPAM.IPArgs = append(n.IPAM.IPArgs, n.Args.A.IPs...) } - for idx, _ := range n.IPAM.IPArgs { + for idx := range n.IPAM.IPArgs { if err := canonicalizeIP(&n.IPAM.IPArgs[idx]); err != nil { return nil, "", fmt.Errorf("cannot understand ip: %v", err) } @@ -122,7 +122,7 @@ func LoadIPAMConfig(bytes []byte, envArgs string) (*IPAMConfig, string, error) { // Validate all ranges numV4 := 0 numV6 := 0 - for i, _ := range n.IPAM.Ranges { + for i := range n.IPAM.Ranges { if err := n.IPAM.Ranges[i].Canonicalize(); err != nil { return nil, "", fmt.Errorf("invalid range set %d: %s", i, err) } diff --git a/vendor/github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator/config_test.go b/vendor/github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator/config_test.go index cbae3d152..84a0398b3 100644 --- a/vendor/github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator/config_test.go +++ b/vendor/github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator/config_test.go @@ -45,7 +45,7 @@ var _ = Describe("IPAM config", func() { Name: "mynet", Type: "host-local", Ranges: []RangeSet{ - RangeSet{ + { { RangeStart: net.IP{10, 1, 2, 9}, RangeEnd: net.IP{10, 1, 2, 20}, @@ -372,7 +372,7 @@ var _ = Describe("IPAM config", func() { "type": "host-local", "ranges": [ [{"subnet": "10.1.2.0/24"}], - [{"subnet": "2001:db8:1::/24"}] + [{"subnet": "2001:db8:1::/48"}] ] } }` diff --git a/vendor/github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator/range.go b/vendor/github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator/range.go index e696b024b..9bf389e80 100644 --- a/vendor/github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator/range.go +++ b/vendor/github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator/range.go @@ -40,6 +40,12 @@ func (r *Range) Canonicalize() error { return fmt.Errorf("IPNet IP and Mask version mismatch") } + // Ensure Subnet IP is the network address, not some other address + networkIP := r.Subnet.IP.Mask(r.Subnet.Mask) + if !r.Subnet.IP.Equal(networkIP) { + return fmt.Errorf("Network has host bits set. For a subnet mask of length %d the network address is %s", ones, networkIP.String()) + } + // If the gateway is nil, claim .1 if r.Gateway == nil { r.Gateway = ip.NextIP(r.Subnet.IP) @@ -47,10 +53,6 @@ func (r *Range) Canonicalize() error { if err := canonicalizeIP(&r.Gateway); err != nil { return err } - subnet := (net.IPNet)(r.Subnet) - if !subnet.Contains(r.Gateway) { - return fmt.Errorf("gateway %s not in network %s", r.Gateway.String(), subnet.String()) - } } // RangeStart: If specified, make sure it's sane (inside the subnet), diff --git a/vendor/github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator/range_set.go b/vendor/github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator/range_set.go index efe2f9402..da957f535 100644 --- a/vendor/github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator/range_set.go +++ b/vendor/github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator/range_set.go @@ -61,7 +61,7 @@ func (s *RangeSet) Canonicalize() error { } fam := 0 - for i, _ := range *s { + for i := range *s { if err := (*s)[i].Canonicalize(); err != nil { return err } diff --git a/vendor/github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator/range_test.go b/vendor/github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator/range_test.go index cb8ca01be..ffee168ea 100644 --- a/vendor/github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator/range_test.go +++ b/vendor/github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator/range_test.go @@ -25,7 +25,7 @@ import ( ) var _ = Describe("IP ranges", func() { - It("should generate sane defaults for ipv4", func() { + It("should generate sane defaults for ipv4 with a clean prefix", func() { snstr := "192.0.2.0/24" r := Range{Subnet: mustSubnet(snstr)} @@ -33,7 +33,7 @@ var _ = Describe("IP ranges", func() { Expect(err).NotTo(HaveOccurred()) Expect(r).To(Equal(Range{ - Subnet: mustSubnet(snstr), + Subnet: networkSubnet(snstr), RangeStart: net.IP{192, 0, 2, 1}, RangeEnd: net.IP{192, 0, 2, 254}, Gateway: net.IP{192, 0, 2, 1}, @@ -47,13 +47,41 @@ var _ = Describe("IP ranges", func() { Expect(err).NotTo(HaveOccurred()) Expect(r).To(Equal(Range{ - Subnet: mustSubnet(snstr), + Subnet: networkSubnet(snstr), RangeStart: net.IP{192, 0, 2, 1}, RangeEnd: net.IP{192, 0, 2, 126}, Gateway: net.IP{192, 0, 2, 1}, })) }) - It("should generate sane defaults for ipv6", func() { + It("should reject ipv4 subnet using a masked address", func() { + snstr := "192.0.2.12/24" + r := Range{Subnet: mustSubnet(snstr)} + + err := r.Canonicalize() + Expect(err).Should(MatchError("Network has host bits set. For a subnet mask of length 24 the network address is 192.0.2.0")) + }) + It("should reject ipv6 subnet using a masked address", func() { + snstr := "2001:DB8:1::24:19ff:fee1:c44a/64" + r := Range{Subnet: mustSubnet(snstr)} + + err := r.Canonicalize() + Expect(err).Should(MatchError("Network has host bits set. For a subnet mask of length 64 the network address is 2001:db8:1::")) + }) + It("should reject ipv6 prefix with host bit set", func() { + snstr := "2001:DB8:24:19ff::/63" + r := Range{Subnet: mustSubnet(snstr)} + + err := r.Canonicalize() + Expect(err).Should(MatchError("Network has host bits set. For a subnet mask of length 63 the network address is 2001:db8:24:19fe::")) + }) + It("should reject ipv4 network with host bit set", func() { + snstr := "192.168.127.0/23" + r := Range{Subnet: mustSubnet(snstr)} + + err := r.Canonicalize() + Expect(err).Should(MatchError("Network has host bits set. For a subnet mask of length 23 the network address is 192.168.126.0")) + }) + It("should generate sane defaults for ipv6 with a clean prefix", func() { snstr := "2001:DB8:1::/64" r := Range{Subnet: mustSubnet(snstr)} @@ -61,7 +89,7 @@ var _ = Describe("IP ranges", func() { Expect(err).NotTo(HaveOccurred()) Expect(r).To(Equal(Range{ - Subnet: mustSubnet(snstr), + Subnet: networkSubnet(snstr), RangeStart: net.ParseIP("2001:DB8:1::1"), RangeEnd: net.ParseIP("2001:DB8:1::ffff:ffff:ffff:ffff"), Gateway: net.ParseIP("2001:DB8:1::1"), @@ -75,16 +103,17 @@ var _ = Describe("IP ranges", func() { }) It("should reject invalid RangeStart and RangeEnd specifications", func() { - r := Range{Subnet: mustSubnet("192.0.2.0/24"), RangeStart: net.ParseIP("192.0.3.0")} + snstr := "192.0.2.0/24" + r := Range{Subnet: mustSubnet(snstr), RangeStart: net.ParseIP("192.0.3.0")} err := r.Canonicalize() Expect(err).Should(MatchError("RangeStart 192.0.3.0 not in network 192.0.2.0/24")) - r = Range{Subnet: mustSubnet("192.0.2.0/24"), RangeEnd: net.ParseIP("192.0.4.0")} + r = Range{Subnet: mustSubnet(snstr), RangeEnd: net.ParseIP("192.0.4.0")} err = r.Canonicalize() Expect(err).Should(MatchError("RangeEnd 192.0.4.0 not in network 192.0.2.0/24")) r = Range{ - Subnet: mustSubnet("192.0.2.0/24"), + Subnet: networkSubnet(snstr), RangeStart: net.ParseIP("192.0.2.50"), RangeEnd: net.ParseIP("192.0.2.40"), } @@ -92,15 +121,10 @@ var _ = Describe("IP ranges", func() { Expect(err).Should(MatchError("RangeStart 192.0.2.50 not in network 192.0.2.0/24")) }) - It("should reject invalid gateways", func() { - r := Range{Subnet: mustSubnet("192.0.2.0/24"), Gateway: net.ParseIP("192.0.3.0")} - err := r.Canonicalize() - Expect(err).Should(MatchError("gateway 192.0.3.0 not in network 192.0.2.0/24")) - }) - It("should parse all fields correctly", func() { + snstr := "192.0.2.0/24" r := Range{ - Subnet: mustSubnet("192.0.2.0/24"), + Subnet: mustSubnet(snstr), RangeStart: net.ParseIP("192.0.2.40"), RangeEnd: net.ParseIP("192.0.2.50"), Gateway: net.ParseIP("192.0.2.254"), @@ -109,7 +133,7 @@ var _ = Describe("IP ranges", func() { Expect(err).NotTo(HaveOccurred()) Expect(r).To(Equal(Range{ - Subnet: mustSubnet("192.0.2.0/24"), + Subnet: networkSubnet(snstr), RangeStart: net.IP{192, 0, 2, 40}, RangeEnd: net.IP{192, 0, 2, 50}, Gateway: net.IP{192, 0, 2, 254}, @@ -207,3 +231,9 @@ func mustSubnet(s string) types.IPNet { canonicalizeIP(&n.IP) return types.IPNet(*n) } + +func networkSubnet(s string) types.IPNet { + net := mustSubnet(s) + net.IP = net.IP.Mask(net.Mask) + return net +} diff --git a/vendor/github.com/containernetworking/plugins/plugins/ipam/host-local/backend/disk/backend.go b/vendor/github.com/containernetworking/plugins/plugins/ipam/host-local/backend/disk/backend.go index 08bb4eb97..c3e6b496f 100644 --- a/vendor/github.com/containernetworking/plugins/plugins/ipam/host-local/backend/disk/backend.go +++ b/vendor/github.com/containernetworking/plugins/plugins/ipam/host-local/backend/disk/backend.go @@ -26,6 +26,7 @@ import ( ) const lastIPFilePrefix = "last_reserved_ip." +const LineBreak = "\r\n" var defaultDataDir = "/var/lib/cni/networks" @@ -55,7 +56,7 @@ func New(network, dataDir string) (*Store, error) { return &Store{lk, dir}, nil } -func (s *Store) Reserve(id string, ip net.IP, rangeID string) (bool, error) { +func (s *Store) Reserve(id string, ifname string, ip net.IP, rangeID string) (bool, error) { fname := GetEscapedPath(s.dataDir, ip.String()) f, err := os.OpenFile(fname, os.O_RDWR|os.O_EXCL|os.O_CREATE, 0644) @@ -65,7 +66,7 @@ func (s *Store) Reserve(id string, ip net.IP, rangeID string) (bool, error) { if err != nil { return false, err } - if _, err := f.WriteString(strings.TrimSpace(id)); err != nil { + if _, err := f.WriteString(strings.TrimSpace(id) + LineBreak + ifname); err != nil { f.Close() os.Remove(f.Name()) return false, err @@ -97,9 +98,45 @@ func (s *Store) Release(ip net.IP) error { return os.Remove(GetEscapedPath(s.dataDir, ip.String())) } -// N.B. This function eats errors to be tolerant and -// release as much as possible -func (s *Store) ReleaseByID(id string) error { +func (s *Store) FindByKey(id string, ifname string, match string) (bool, error) { + found := false + + err := filepath.Walk(s.dataDir, func(path string, info os.FileInfo, err error) error { + if err != nil || info.IsDir() { + return nil + } + data, err := ioutil.ReadFile(path) + if err != nil { + return nil + } + if strings.TrimSpace(string(data)) == match { + found = true + } + return nil + }) + return found, err + +} + +func (s *Store) FindByID(id string, ifname string) bool { + s.Lock() + defer s.Unlock() + + found := false + match := strings.TrimSpace(id) + LineBreak + ifname + found, err := s.FindByKey(id, ifname, match) + + // Match anything created by this id + if !found && err == nil { + match := strings.TrimSpace(id) + found, err = s.FindByKey(id, ifname, match) + } + + return found +} + +func (s *Store) ReleaseByKey(id string, ifname string, match string) (bool, error) { + found := false err := filepath.Walk(s.dataDir, func(path string, info os.FileInfo, err error) error { if err != nil || info.IsDir() { return nil @@ -108,13 +145,30 @@ func (s *Store) ReleaseByID(id string) error { if err != nil { return nil } - if strings.TrimSpace(string(data)) == strings.TrimSpace(id) { + if strings.TrimSpace(string(data)) == match { if err := os.Remove(path); err != nil { return nil } + found = true } return nil }) + return found, err + +} + +// N.B. This function eats errors to be tolerant and +// release as much as possible +func (s *Store) ReleaseByID(id string, ifname string) error { + found := false + match := strings.TrimSpace(id) + LineBreak + ifname + found, err := s.ReleaseByKey(id, ifname, match) + + // For backwards compatibility, look for files written by a previous version + if !found && err == nil { + match := strings.TrimSpace(id) + found, err = s.ReleaseByKey(id, ifname, match) + } return err } diff --git a/vendor/github.com/containernetworking/plugins/plugins/ipam/host-local/backend/store.go b/vendor/github.com/containernetworking/plugins/plugins/ipam/host-local/backend/store.go index 3d6958477..4ea845da7 100644 --- a/vendor/github.com/containernetworking/plugins/plugins/ipam/host-local/backend/store.go +++ b/vendor/github.com/containernetworking/plugins/plugins/ipam/host-local/backend/store.go @@ -20,8 +20,8 @@ type Store interface { Lock() error Unlock() error Close() error - Reserve(id string, ip net.IP, rangeID string) (bool, error) + Reserve(id string, ifname string, ip net.IP, rangeID string) (bool, error) LastReservedIP(rangeID string) (net.IP, error) Release(ip net.IP) error - ReleaseByID(id string) error + ReleaseByID(id string, ifname string) error } diff --git a/vendor/github.com/containernetworking/plugins/plugins/ipam/host-local/backend/testing/fake_store.go b/vendor/github.com/containernetworking/plugins/plugins/ipam/host-local/backend/testing/fake_store.go index 49a0f5549..631fca2e0 100644 --- a/vendor/github.com/containernetworking/plugins/plugins/ipam/host-local/backend/testing/fake_store.go +++ b/vendor/github.com/containernetworking/plugins/plugins/ipam/host-local/backend/testing/fake_store.go @@ -45,7 +45,7 @@ func (s *FakeStore) Close() error { return nil } -func (s *FakeStore) Reserve(id string, ip net.IP, rangeID string) (bool, error) { +func (s *FakeStore) Reserve(id string, ifname string, ip net.IP, rangeID string) (bool, error) { key := ip.String() if _, ok := s.ipMap[key]; !ok { s.ipMap[key] = id @@ -68,7 +68,7 @@ func (s *FakeStore) Release(ip net.IP) error { return nil } -func (s *FakeStore) ReleaseByID(id string) error { +func (s *FakeStore) ReleaseByID(id string, ifname string) error { toDelete := []string{} for k, v := range s.ipMap { if v == id { diff --git a/vendor/github.com/containernetworking/plugins/plugins/ipam/host-local/host_local_test.go b/vendor/github.com/containernetworking/plugins/plugins/ipam/host-local/host_local_test.go index cf5a39b24..46ab0b56d 100644 --- a/vendor/github.com/containernetworking/plugins/plugins/ipam/host-local/host_local_test.go +++ b/vendor/github.com/containernetworking/plugins/plugins/ipam/host-local/host_local_test.go @@ -33,6 +33,8 @@ import ( . "github.com/onsi/gomega" ) +const LineBreak = "\r\n" + var _ = Describe("host-local Operations", func() { It("allocates and releases addresses with ADD/DEL", func() { const ifname string = "eth0" @@ -111,12 +113,12 @@ var _ = Describe("host-local Operations", func() { ipFilePath1 := filepath.Join(tmpDir, "mynet", "10.1.2.2") contents, err := ioutil.ReadFile(ipFilePath1) Expect(err).NotTo(HaveOccurred()) - Expect(string(contents)).To(Equal(args.ContainerID)) + Expect(string(contents)).To(Equal(args.ContainerID + LineBreak + ifname)) ipFilePath2 := filepath.Join(tmpDir, disk.GetEscapedPath("mynet", "2001:db8:1::2")) contents, err = ioutil.ReadFile(ipFilePath2) Expect(err).NotTo(HaveOccurred()) - Expect(string(contents)).To(Equal(args.ContainerID)) + Expect(string(contents)).To(Equal(args.ContainerID + LineBreak + ifname)) lastFilePath1 := filepath.Join(tmpDir, "mynet", "last_reserved_ip.0") contents, err = ioutil.ReadFile(lastFilePath1) @@ -139,6 +141,173 @@ var _ = Describe("host-local Operations", func() { Expect(err).To(HaveOccurred()) }) + It("allocates and releases addresses on specific interface with ADD/DEL", func() { + const ifname0 string = "eth0" + const ifname1 string = "eth1" + const nspath string = "/some/where" + + tmpDir, err := getTmpDir() + Expect(err).NotTo(HaveOccurred()) + defer os.RemoveAll(tmpDir) + + err = ioutil.WriteFile(filepath.Join(tmpDir, "resolv.conf"), []byte("nameserver 192.0.2.3"), 0644) + Expect(err).NotTo(HaveOccurred()) + + conf0 := fmt.Sprintf(`{ + "cniVersion": "0.3.1", + "name": "mynet0", + "type": "ipvlan", + "master": "foo0", + "ipam": { + "type": "host-local", + "dataDir": "%s", + "resolvConf": "%s/resolv.conf", + "ranges": [ + [{ "subnet": "10.1.2.0/24" }] + ] + } + }`, tmpDir, tmpDir) + + conf1 := fmt.Sprintf(`{ + "cniVersion": "0.3.1", + "name": "mynet1", + "type": "ipvlan", + "master": "foo1", + "ipam": { + "type": "host-local", + "dataDir": "%s", + "resolvConf": "%s/resolv.conf", + "ranges": [ + [{ "subnet": "10.2.2.0/24" }] + ] + } + }`, tmpDir, tmpDir) + + args0 := &skel.CmdArgs{ + ContainerID: "dummy", + Netns: nspath, + IfName: ifname0, + StdinData: []byte(conf0), + } + + // Allocate the IP + r0, raw, err := testutils.CmdAddWithArgs(args0, func() error { + return cmdAdd(args0) + }) + Expect(err).NotTo(HaveOccurred()) + Expect(strings.Index(string(raw), "\"version\":")).Should(BeNumerically(">", 0)) + + _, err = current.GetResult(r0) + Expect(err).NotTo(HaveOccurred()) + + args1 := &skel.CmdArgs{ + ContainerID: "dummy", + Netns: nspath, + IfName: ifname1, + StdinData: []byte(conf1), + } + + // Allocate the IP + r1, raw, err := testutils.CmdAddWithArgs(args1, func() error { + return cmdAdd(args1) + }) + Expect(err).NotTo(HaveOccurred()) + Expect(strings.Index(string(raw), "\"version\":")).Should(BeNumerically(">", 0)) + + _, err = current.GetResult(r1) + Expect(err).NotTo(HaveOccurred()) + + ipFilePath0 := filepath.Join(tmpDir, "mynet0", "10.1.2.2") + contents, err := ioutil.ReadFile(ipFilePath0) + Expect(err).NotTo(HaveOccurred()) + Expect(string(contents)).To(Equal(args0.ContainerID + LineBreak + ifname0)) + + ipFilePath1 := filepath.Join(tmpDir, "mynet1", "10.2.2.2") + contents, err = ioutil.ReadFile(ipFilePath1) + Expect(err).NotTo(HaveOccurred()) + Expect(string(contents)).To(Equal(args1.ContainerID + LineBreak + ifname1)) + + // Release the IP on ifname0 + err = testutils.CmdDelWithArgs(args0, func() error { + return cmdDel(args0) + }) + Expect(err).NotTo(HaveOccurred()) + _, err = os.Stat(ipFilePath0) + Expect(err).To(HaveOccurred()) + + // reread ipFilePath1, ensure that ifname1 didn't get deleted + contents, err = ioutil.ReadFile(ipFilePath1) + Expect(err).NotTo(HaveOccurred()) + Expect(string(contents)).To(Equal(args1.ContainerID + LineBreak + ifname1)) + + // Release the IP on ifname1 + err = testutils.CmdDelWithArgs(args1, func() error { + return cmdDel(args1) + }) + Expect(err).NotTo(HaveOccurred()) + + _, err = os.Stat(ipFilePath1) + Expect(err).To(HaveOccurred()) + }) + + It("Verify DEL works on backwards compatible allocate", func() { + const nspath string = "/some/where" + const ifname string = "eth0" + + tmpDir, err := getTmpDir() + Expect(err).NotTo(HaveOccurred()) + defer os.RemoveAll(tmpDir) + + err = ioutil.WriteFile(filepath.Join(tmpDir, "resolv.conf"), []byte("nameserver 192.0.2.3"), 0644) + Expect(err).NotTo(HaveOccurred()) + + conf := fmt.Sprintf(`{ + "cniVersion": "0.3.1", + "name": "mynet", + "type": "ipvlan", + "master": "foo", + "ipam": { + "type": "host-local", + "dataDir": "%s", + "resolvConf": "%s/resolv.conf", + "ranges": [ + [{ "subnet": "10.1.2.0/24" }] + ] + } + }`, tmpDir, tmpDir) + + args := &skel.CmdArgs{ + ContainerID: "dummy", + Netns: nspath, + IfName: ifname, + StdinData: []byte(conf), + } + + // Allocate the IP + r, raw, err := testutils.CmdAddWithArgs(args, func() error { + return cmdAdd(args) + }) + Expect(err).NotTo(HaveOccurred()) + Expect(strings.Index(string(raw), "\"version\":")).Should(BeNumerically(">", 0)) + + _, err = current.GetResult(r) + Expect(err).NotTo(HaveOccurred()) + + ipFilePath := filepath.Join(tmpDir, "mynet", "10.1.2.2") + contents, err := ioutil.ReadFile(ipFilePath) + Expect(err).NotTo(HaveOccurred()) + Expect(string(contents)).To(Equal(args.ContainerID + LineBreak + ifname)) + err = ioutil.WriteFile(ipFilePath, []byte(strings.TrimSpace(args.ContainerID)), 0644) + Expect(err).NotTo(HaveOccurred()) + + err = testutils.CmdDelWithArgs(args, func() error { + return cmdDel(args) + }) + Expect(err).NotTo(HaveOccurred()) + _, err = os.Stat(ipFilePath) + Expect(err).To(HaveOccurred()) + }) + It("doesn't error when passed an unknown ID on DEL", func() { const ifname string = "eth0" const nspath string = "/some/where" @@ -223,7 +392,7 @@ var _ = Describe("host-local Operations", func() { ipFilePath := filepath.Join(tmpDir, "mynet", "10.1.2.2") contents, err := ioutil.ReadFile(ipFilePath) Expect(err).NotTo(HaveOccurred()) - Expect(string(contents)).To(Equal(args.ContainerID)) + Expect(string(contents)).To(Equal(args.ContainerID + LineBreak + ifname)) lastFilePath := filepath.Join(tmpDir, "mynet", "last_reserved_ip.0") contents, err = ioutil.ReadFile(lastFilePath) @@ -281,7 +450,7 @@ var _ = Describe("host-local Operations", func() { ipFilePath := filepath.Join(tmpDir, "mynet", result.IPs[0].Address.IP.String()) contents, err := ioutil.ReadFile(ipFilePath) Expect(err).NotTo(HaveOccurred()) - Expect(string(contents)).To(Equal("dummy")) + Expect(string(contents)).To(Equal("dummy" + LineBreak + ifname)) // Release the IP err = testutils.CmdDelWithArgs(args, func() error { @@ -444,7 +613,7 @@ var _ = Describe("host-local Operations", func() { "dataDir": "%s", "ranges": [ [{"subnet":"172.16.1.0/24"}, { "subnet": "10.1.2.0/24" }], - [{ "subnet": "2001:db8:1::/24" }] + [{ "subnet": "2001:db8:1::/48" }] ] }, "args": { diff --git a/vendor/github.com/containernetworking/plugins/plugins/ipam/host-local/main.go b/vendor/github.com/containernetworking/plugins/plugins/ipam/host-local/main.go index 9e2bacc23..b5a8f84c4 100644 --- a/vendor/github.com/containernetworking/plugins/plugins/ipam/host-local/main.go +++ b/vendor/github.com/containernetworking/plugins/plugins/ipam/host-local/main.go @@ -15,10 +15,12 @@ package main import ( + "encoding/json" "fmt" "net" "strings" + bv "github.com/containernetworking/plugins/pkg/utils/buildversion" "github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator" "github.com/containernetworking/plugins/plugins/ipam/host-local/backend/disk" @@ -29,7 +31,38 @@ import ( ) func main() { - skel.PluginMain(cmdAdd, cmdDel, version.All) + skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("host-local")) +} + +func loadNetConf(bytes []byte) (*types.NetConf, string, error) { + n := &types.NetConf{} + if err := json.Unmarshal(bytes, n); err != nil { + return nil, "", fmt.Errorf("failed to load netconf: %v", err) + } + return n, n.CNIVersion, nil +} + +func cmdCheck(args *skel.CmdArgs) error { + + ipamConf, _, err := allocator.LoadIPAMConfig(args.StdinData, args.Args) + if err != nil { + return err + } + + // Look to see if there is at least one IP address allocated to the container + // in the data dir, irrespective of what that address actually is + store, err := disk.New(ipamConf.Name, ipamConf.DataDir) + if err != nil { + return err + } + defer store.Close() + + containerIpFound := store.FindByID(args.ContainerID, args.IfName) + if containerIpFound == false { + return fmt.Errorf("host-local: Failed to find address added by container %v", args.ContainerID) + } + + return nil } func cmdAdd(args *skel.CmdArgs) error { @@ -79,11 +112,11 @@ func cmdAdd(args *skel.CmdArgs) error { } } - ipConf, err := allocator.Get(args.ContainerID, requestedIP) + ipConf, err := allocator.Get(args.ContainerID, args.IfName, requestedIP) if err != nil { // Deallocate all already allocated IPs for _, alloc := range allocs { - _ = alloc.Release(args.ContainerID) + _ = alloc.Release(args.ContainerID, args.IfName) } return fmt.Errorf("failed to allocate for range %d: %v", idx, err) } @@ -96,7 +129,7 @@ func cmdAdd(args *skel.CmdArgs) error { // If an IP was requested that wasn't fulfilled, fail if len(requestedIPs) != 0 { for _, alloc := range allocs { - _ = alloc.Release(args.ContainerID) + _ = alloc.Release(args.ContainerID, args.IfName) } errstr := "failed to allocate all requested IPs:" for _, ip := range requestedIPs { @@ -127,7 +160,7 @@ func cmdDel(args *skel.CmdArgs) error { for idx, rangeset := range ipamConf.Ranges { ipAllocator := allocator.NewIPAllocator(&rangeset, store, idx) - err := ipAllocator.Release(args.ContainerID) + err := ipAllocator.Release(args.ContainerID, args.IfName) if err != nil { errors = append(errors, err.Error()) } diff --git a/vendor/github.com/containernetworking/plugins/plugins/ipam/static/README.md b/vendor/github.com/containernetworking/plugins/plugins/ipam/static/README.md index 7b3c01fe9..d9ca28c5b 100644 --- a/vendor/github.com/containernetworking/plugins/plugins/ipam/static/README.md +++ b/vendor/github.com/containernetworking/plugins/plugins/ipam/static/README.md @@ -38,8 +38,17 @@ static IPAM is very simple IPAM plugin that assigns IPv4 and IPv6 addresses stat ## Network configuration reference * `type` (string, required): "static" -* `addresses` (array, required): an array of arrays of ip address objects: +* `addresses` (array, optional): an array of ip address objects: * `address` (string, required): CIDR notation IP address. * `gateway` (string, optional): IP inside of "subnet" to designate as the gateway. * `routes` (string, optional): list of routes add to the container namespace. Each route is a dictionary with "dst" and optional "gw" fields. If "gw" is omitted, value of "gateway" will be used. * `dns` (string, optional): the dictionary with "nameservers", "domain" and "search". + +## Supported arguments + +The following [CNI_ARGS](https://github.com/containernetworking/cni/blob/master/SPEC.md#parameters) are supported: + +* `IP`: request a specific CIDR notation IP addresses, comma separated +* `GATEWAY`: request a specific gateway address + + (example: CNI_ARGS="IP=10.10.0.1/24;GATEWAY=10.10.0.254") diff --git a/vendor/github.com/containernetworking/plugins/plugins/ipam/static/main.go b/vendor/github.com/containernetworking/plugins/plugins/ipam/static/main.go index d236c0b6f..b5c501a1f 100644 --- a/vendor/github.com/containernetworking/plugins/plugins/ipam/static/main.go +++ b/vendor/github.com/containernetworking/plugins/plugins/ipam/static/main.go @@ -18,13 +18,14 @@ import ( "encoding/json" "fmt" "net" + "strings" "github.com/containernetworking/cni/pkg/skel" "github.com/containernetworking/cni/pkg/types" + types020 "github.com/containernetworking/cni/pkg/types/020" "github.com/containernetworking/cni/pkg/types/current" "github.com/containernetworking/cni/pkg/version" - - types020 "github.com/containernetworking/cni/pkg/types/020" + bv "github.com/containernetworking/plugins/pkg/utils/buildversion" ) // The top-level network config - IPAM plugins are passed the full configuration @@ -39,10 +40,16 @@ type IPAMConfig struct { Name string Type string `json:"type"` Routes []*types.Route `json:"routes"` - Addresses []Address `json:"addresses"` + Addresses []Address `json:"addresses,omitempty"` DNS types.DNS `json:"dns"` } +type IPAMEnvArgs struct { + types.CommonArgs + IP types.UnmarshallableString `json:"ip,omitempty"` + GATEWAY types.UnmarshallableString `json:"gateway,omitempty"` +} + type Address struct { AddressStr string `json:"address"` Gateway net.IP `json:"gateway,omitempty"` @@ -51,7 +58,59 @@ type Address struct { } func main() { - skel.PluginMain(cmdAdd, cmdDel, version.All) + skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("static")) +} + +func loadNetConf(bytes []byte) (*types.NetConf, string, error) { + n := &types.NetConf{} + if err := json.Unmarshal(bytes, n); err != nil { + return nil, "", fmt.Errorf("failed to load netconf: %v", err) + } + return n, n.CNIVersion, nil +} + +func cmdCheck(args *skel.CmdArgs) error { + ipamConf, _, err := LoadIPAMConfig(args.StdinData, args.Args) + if err != nil { + return err + } + + // Get PrevResult from stdin... store in RawPrevResult + n, _, err := loadNetConf(args.StdinData) + if err != nil { + return err + } + + // Parse previous result. + if n.RawPrevResult == nil { + return fmt.Errorf("Required prevResult missing") + } + + if err := version.ParsePrevResult(n); err != nil { + return err + } + + result, err := current.NewResultFromResult(n.PrevResult) + if err != nil { + return err + } + + // Each configured IP should be found in result.IPs + for _, rangeset := range ipamConf.Addresses { + for _, ips := range result.IPs { + // Ensure values are what we expect + if rangeset.Address.IP.Equal(ips.Address.IP) { + if rangeset.Gateway == nil { + break + } else if rangeset.Gateway.Equal(ips.Gateway) { + break + } + return fmt.Errorf("static: Failed to match addr %v on interface %v", ips.Address.IP, args.IfName) + } + } + } + + return nil } // canonicalizeIP makes sure a provided ip is in standard form @@ -66,7 +125,9 @@ func canonicalizeIP(ip *net.IP) error { return fmt.Errorf("IP %s not v4 nor v6", *ip) } -// NewIPAMConfig creates a NetworkConfig from the given network name. +// LoadIPAMConfig creates IPAMConfig using json encoded configuration provided +// as `bytes`. At the moment values provided in envArgs are ignored so there +// is no possibility to overload the json configuration using envArgs func LoadIPAMConfig(bytes []byte, envArgs string) (*IPAMConfig, string, error) { n := Net{} if err := json.Unmarshal(bytes, &n); err != nil { @@ -80,6 +141,7 @@ func LoadIPAMConfig(bytes []byte, envArgs string) (*IPAMConfig, string, error) { // Validate all ranges numV4 := 0 numV6 := 0 + for i := range n.IPAM.Addresses { ip, addr, err := net.ParseCIDR(n.IPAM.Addresses[i].AddressStr) if err != nil { @@ -101,6 +163,50 @@ func LoadIPAMConfig(bytes []byte, envArgs string) (*IPAMConfig, string, error) { } } + if envArgs != "" { + e := IPAMEnvArgs{} + err := types.LoadArgs(envArgs, &e) + if err != nil { + return nil, "", err + } + + if e.IP != "" { + for _, item := range strings.Split(string(e.IP), ",") { + ipstr := strings.TrimSpace(item) + + ip, subnet, err := net.ParseCIDR(ipstr) + if err != nil { + return nil, "", fmt.Errorf("invalid CIDR %s: %s", ipstr, err) + } + + addr := Address{Address: net.IPNet{IP: ip, Mask: subnet.Mask}} + if addr.Address.IP.To4() != nil { + addr.Version = "4" + numV4++ + } else { + addr.Version = "6" + numV6++ + } + n.IPAM.Addresses = append(n.IPAM.Addresses, addr) + } + } + + if e.GATEWAY != "" { + for _, item := range strings.Split(string(e.GATEWAY), ",") { + gwip := net.ParseIP(strings.TrimSpace(item)) + if gwip == nil { + return nil, "", fmt.Errorf("invalid gateway address: %s", item) + } + + for i := range n.IPAM.Addresses { + if n.IPAM.Addresses[i].Address.Contains(gwip) { + n.IPAM.Addresses[i].Gateway = gwip + } + } + } + } + } + // CNI spec 0.2.0 and below supported only one v4 and v6 address if numV4 > 1 || numV6 > 1 { for _, v := range types020.SupportedVersions { @@ -132,7 +238,6 @@ func cmdAdd(args *skel.CmdArgs) error { Gateway: v.Gateway}) } - result.Routes = ipamConf.Routes return types.PrintResult(result, confVersion) } diff --git a/vendor/github.com/containernetworking/plugins/plugins/ipam/static/static_suite_test.go b/vendor/github.com/containernetworking/plugins/plugins/ipam/static/static_suite_test.go index aae2fee43..c729ecf19 100644 --- a/vendor/github.com/containernetworking/plugins/plugins/ipam/static/static_suite_test.go +++ b/vendor/github.com/containernetworking/plugins/plugins/ipam/static/static_suite_test.go @@ -23,5 +23,5 @@ import ( func TestStatic(t *testing.T) { RegisterFailHandler(Fail) - RunSpecs(t, "Static Suite") + RunSpecs(t, "plugins/ipam/static") } diff --git a/vendor/github.com/containernetworking/plugins/plugins/ipam/static/static_test.go b/vendor/github.com/containernetworking/plugins/plugins/ipam/static/static_test.go index 31ac3c283..c6d9aea65 100644 --- a/vendor/github.com/containernetworking/plugins/plugins/ipam/static/static_test.go +++ b/vendor/github.com/containernetworking/plugins/plugins/ipam/static/static_test.go @@ -148,6 +148,123 @@ var _ = Describe("static Operations", func() { }) Expect(err).NotTo(HaveOccurred()) }) + + It("allocates and releases addresses with ADD/DEL, with ENV variables", func() { + const ifname string = "eth0" + const nspath string = "/some/where" + + conf := `{ + "cniVersion": "0.3.1", + "name": "mynet", + "type": "ipvlan", + "master": "foo0", + "ipam": { + "type": "static", + "routes": [ + { "dst": "0.0.0.0/0" }, + { "dst": "192.168.0.0/16", "gw": "10.10.5.1" }], + "dns": { + "nameservers" : ["8.8.8.8"], + "domain": "example.com", + "search": [ "example.com" ] + } + } + }` + + args := &skel.CmdArgs{ + ContainerID: "dummy", + Netns: nspath, + IfName: ifname, + StdinData: []byte(conf), + Args: "IP=10.10.0.1/24;GATEWAY=10.10.0.254", + } + + // Allocate the IP + r, raw, err := testutils.CmdAddWithArgs(args, func() error { + return cmdAdd(args) + }) + Expect(err).NotTo(HaveOccurred()) + Expect(strings.Index(string(raw), "\"version\":")).Should(BeNumerically(">", 0)) + + result, err := current.GetResult(r) + Expect(err).NotTo(HaveOccurred()) + + // Gomega is cranky about slices with different caps + Expect(*result.IPs[0]).To(Equal( + current.IPConfig{ + Version: "4", + Address: mustCIDR("10.10.0.1/24"), + Gateway: net.ParseIP("10.10.0.254"), + })) + + Expect(len(result.IPs)).To(Equal(1)) + + Expect(result.Routes).To(Equal([]*types.Route{ + {Dst: mustCIDR("0.0.0.0/0")}, + {Dst: mustCIDR("192.168.0.0/16"), GW: net.ParseIP("10.10.5.1")}, + })) + + // Release the IP + err = testutils.CmdDelWithArgs(args, func() error { + return cmdDel(args) + }) + Expect(err).NotTo(HaveOccurred()) + }) + + It("allocates and releases multiple addresses with ADD/DEL, with ENV variables", func() { + const ifname string = "eth0" + const nspath string = "/some/where" + + conf := `{ + "cniVersion": "0.3.1", + "name": "mynet", + "type": "ipvlan", + "master": "foo0", + "ipam": { + "type": "static" + } + }` + + args := &skel.CmdArgs{ + ContainerID: "dummy", + Netns: nspath, + IfName: ifname, + StdinData: []byte(conf), + Args: "IP=10.10.0.1/24,11.11.0.1/24;GATEWAY=10.10.0.254", + } + + // Allocate the IP + r, raw, err := testutils.CmdAddWithArgs(args, func() error { + return cmdAdd(args) + }) + Expect(err).NotTo(HaveOccurred()) + Expect(strings.Index(string(raw), "\"version\":")).Should(BeNumerically(">", 0)) + + result, err := current.GetResult(r) + Expect(err).NotTo(HaveOccurred()) + + // Gomega is cranky about slices with different caps + Expect(*result.IPs[0]).To(Equal( + current.IPConfig{ + Version: "4", + Address: mustCIDR("10.10.0.1/24"), + Gateway: net.ParseIP("10.10.0.254"), + })) + Expect(*result.IPs[1]).To(Equal( + current.IPConfig{ + Version: "4", + Address: mustCIDR("11.11.0.1/24"), + Gateway: nil, + })) + + Expect(len(result.IPs)).To(Equal(2)) + + // Release the IP + err = testutils.CmdDelWithArgs(args, func() error { + return cmdDel(args) + }) + Expect(err).NotTo(HaveOccurred()) + }) }) func mustCIDR(s string) net.IPNet { diff --git a/vendor/github.com/containernetworking/plugins/plugins/linux_only.txt b/vendor/github.com/containernetworking/plugins/plugins/linux_only.txt index 6b04296f5..b789fb4c3 100644 --- a/vendor/github.com/containernetworking/plugins/plugins/linux_only.txt +++ b/vendor/github.com/containernetworking/plugins/plugins/linux_only.txt @@ -8,4 +8,5 @@ plugins/main/ptp plugins/main/vlan plugins/meta/portmap plugins/meta/tuning -plugins/meta/bandwidth \ No newline at end of file +plugins/meta/bandwidth +plugins/meta/firewall diff --git a/vendor/github.com/containernetworking/plugins/plugins/main/bridge/README.md b/vendor/github.com/containernetworking/plugins/plugins/main/bridge/README.md index 07eac591a..793c7e43c 100644 --- a/vendor/github.com/containernetworking/plugins/plugins/main/bridge/README.md +++ b/vendor/github.com/containernetworking/plugins/plugins/main/bridge/README.md @@ -28,6 +28,17 @@ If the bridge is missing, the plugin will create one on first use and, if gatewa } ``` +## Example L2-only configuration +``` +{ + "cniVersion": "0.3.1", + "name": "mynet", + "type": "bridge", + "bridge": "mynet0", + "ipam": {} +} +``` + ## Network configuration reference * `name` (string, required): the name of the network. @@ -39,5 +50,10 @@ If the bridge is missing, the plugin will create one on first use and, if gatewa * `ipMasq` (boolean, optional): set up IP Masquerade on the host for traffic originating from this network and destined outside of it. Defaults to false. * `mtu` (integer, optional): explicitly set MTU to the specified value. Defaults to the value chosen by the kernel. * `hairpinMode` (boolean, optional): set hairpin mode for interfaces on the bridge. Defaults to false. -* `ipam` (dictionary, required): IPAM configuration to be used for this network. +* `ipam` (dictionary, required): IPAM configuration to be used for this network. For L2-only network, create empty dictionary. * `promiscMode` (boolean, optional): set promiscuous mode on the bridge. Defaults to false. +* `vlan` (int, optional): assign VLAN tag. Defaults to none. + +*Note:* The VLAN parameter configures the VLAN tag on the host end of the veth and also enables the vlan_filtering feature on the bridge interface. + +*Note:* To configure uplink for L2 network you need to allow the vlan on the uplink interface by using the following command ``` bridge vlan add vid VLAN_ID dev DEV```. \ No newline at end of file diff --git a/vendor/github.com/containernetworking/plugins/plugins/main/bridge/bridge.go b/vendor/github.com/containernetworking/plugins/plugins/main/bridge/bridge.go index 63e0d89a9..675ffdd20 100644 --- a/vendor/github.com/containernetworking/plugins/plugins/main/bridge/bridge.go +++ b/vendor/github.com/containernetworking/plugins/plugins/main/bridge/bridge.go @@ -18,11 +18,13 @@ import ( "encoding/json" "errors" "fmt" + "io/ioutil" "net" "runtime" "syscall" - "io/ioutil" + "github.com/j-keck/arping" + "github.com/vishvananda/netlink" "github.com/containernetworking/cni/pkg/skel" "github.com/containernetworking/cni/pkg/types" @@ -32,10 +34,12 @@ import ( "github.com/containernetworking/plugins/pkg/ipam" "github.com/containernetworking/plugins/pkg/ns" "github.com/containernetworking/plugins/pkg/utils" - "github.com/j-keck/arping" - "github.com/vishvananda/netlink" + bv "github.com/containernetworking/plugins/pkg/utils/buildversion" ) +// For testcases to force an error after IPAM has been performed +var debugPostIPAMError error + const defaultBrName = "cni0" type NetConf struct { @@ -48,6 +52,7 @@ type NetConf struct { MTU int `json:"mtu"` HairpinMode bool `json:"hairpinMode"` PromiscMode bool `json:"promiscMode"` + Vlan int `json:"vlan"` } type gwInfo struct { @@ -140,7 +145,7 @@ func calcGateways(result *current.Result, n *NetConf) (*gwInfo, *gwInfo, error) return gwsV4, gwsV6, nil } -func ensureBridgeAddr(br *netlink.Bridge, family int, ipn *net.IPNet, forceAddress bool) error { +func ensureAddr(br netlink.Link, family int, ipn *net.IPNet, forceAddress bool) error { addrs, err := netlink.AddrList(br, family) if err != nil && err != syscall.ENOENT { return fmt.Errorf("could not get list of IP addresses: %v", err) @@ -160,34 +165,34 @@ func ensureBridgeAddr(br *netlink.Bridge, family int, ipn *net.IPNet, forceAddre // forceAddress is true, otherwise throw an error. if family == netlink.FAMILY_V4 || a.IPNet.Contains(ipn.IP) || ipn.Contains(a.IPNet.IP) { if forceAddress { - if err = deleteBridgeAddr(br, a.IPNet); err != nil { + if err = deleteAddr(br, a.IPNet); err != nil { return err } } else { - return fmt.Errorf("%q already has an IP address different from %v", br.Name, ipnStr) + return fmt.Errorf("%q already has an IP address different from %v", br.Attrs().Name, ipnStr) } } } addr := &netlink.Addr{IPNet: ipn, Label: ""} if err := netlink.AddrAdd(br, addr); err != nil { - return fmt.Errorf("could not add IP address to %q: %v", br.Name, err) + return fmt.Errorf("could not add IP address to %q: %v", br.Attrs().Name, err) } // Set the bridge's MAC to itself. Otherwise, the bridge will take the // lowest-numbered mac on the bridge, and will change as ifs churn - if err := netlink.LinkSetHardwareAddr(br, br.HardwareAddr); err != nil { + if err := netlink.LinkSetHardwareAddr(br, br.Attrs().HardwareAddr); err != nil { return fmt.Errorf("could not set bridge's mac: %v", err) } return nil } -func deleteBridgeAddr(br *netlink.Bridge, ipn *net.IPNet) error { +func deleteAddr(br netlink.Link, ipn *net.IPNet) error { addr := &netlink.Addr{IPNet: ipn, Label: ""} if err := netlink.AddrDel(br, addr); err != nil { - return fmt.Errorf("could not remove IP address from %q: %v", br.Name, err) + return fmt.Errorf("could not remove IP address from %q: %v", br.Attrs().Name, err) } return nil @@ -205,7 +210,7 @@ func bridgeByName(name string) (*netlink.Bridge, error) { return br, nil } -func ensureBridge(brName string, mtu int, promiscMode bool) (*netlink.Bridge, error) { +func ensureBridge(brName string, mtu int, promiscMode, vlanFiltering bool) (*netlink.Bridge, error) { br := &netlink.Bridge{ LinkAttrs: netlink.LinkAttrs{ Name: brName, @@ -216,6 +221,7 @@ func ensureBridge(brName string, mtu int, promiscMode bool) (*netlink.Bridge, er // default packet limit TxQLen: -1, }, + VlanFiltering: &vlanFiltering, } err := netlink.LinkAdd(br) @@ -243,7 +249,35 @@ func ensureBridge(brName string, mtu int, promiscMode bool) (*netlink.Bridge, er return br, nil } -func setupVeth(netns ns.NetNS, br *netlink.Bridge, ifName string, mtu int, hairpinMode bool) (*current.Interface, *current.Interface, error) { +func ensureVlanInterface(br *netlink.Bridge, vlanId int) (netlink.Link, error) { + name := fmt.Sprintf("%s.%d", br.Name, vlanId) + + brGatewayVeth, err := netlink.LinkByName(name) + if err != nil { + if err.Error() != "Link not found" { + return nil, fmt.Errorf("failed to find interface %q: %v", name, err) + } + + hostNS, err := ns.GetCurrentNS() + if err != nil { + return nil, fmt.Errorf("faild to find host namespace: %v", err) + } + + _, brGatewayIface, err := setupVeth(hostNS, br, name, br.MTU, false, vlanId) + if err != nil { + return nil, fmt.Errorf("faild to create vlan gateway %q: %v", name, err) + } + + brGatewayVeth, err = netlink.LinkByName(brGatewayIface.Name) + if err != nil { + return nil, fmt.Errorf("failed to lookup %q: %v", brGatewayIface.Name, err) + } + } + + return brGatewayVeth, nil +} + +func setupVeth(netns ns.NetNS, br *netlink.Bridge, ifName string, mtu int, hairpinMode bool, vlanID int) (*current.Interface, *current.Interface, error) { contIface := ¤t.Interface{} hostIface := ¤t.Interface{} @@ -280,6 +314,13 @@ func setupVeth(netns ns.NetNS, br *netlink.Bridge, ifName string, mtu int, hairp return nil, nil, fmt.Errorf("failed to setup hairpin mode for %v: %v", hostVeth.Attrs().Name, err) } + if vlanID != 0 { + err = netlink.BridgeVlanAdd(hostVeth, uint16(vlanID), true, true, false, true) + if err != nil { + return nil, nil, fmt.Errorf("failed to setup vlan tag on interface %q: %v", hostIface.Name, err) + } + } + return hostIface, contIface, nil } @@ -289,8 +330,12 @@ func calcGatewayIP(ipn *net.IPNet) net.IP { } func setupBridge(n *NetConf) (*netlink.Bridge, *current.Interface, error) { + vlanFiltering := false + if n.Vlan != 0 { + vlanFiltering = true + } // create bridge if necessary - br, err := ensureBridge(n.BrName, n.MTU, n.PromiscMode) + br, err := ensureBridge(n.BrName, n.MTU, n.PromiscMode, vlanFiltering) if err != nil { return nil, nil, fmt.Errorf("failed to create bridge %q: %v", n.BrName, err) } @@ -323,11 +368,15 @@ func enableIPForward(family int) error { } func cmdAdd(args *skel.CmdArgs) error { + var success bool = false + n, cniVersion, err := loadNetConf(args.StdinData) if err != nil { return err } + isLayer3 := n.IPAM.Type != "" + if n.IsDefaultGW { n.IsGW = true } @@ -347,99 +396,130 @@ func cmdAdd(args *skel.CmdArgs) error { } defer netns.Close() - hostInterface, containerInterface, err := setupVeth(netns, br, args.IfName, n.MTU, n.HairpinMode) - if err != nil { - return err - } - - // run the IPAM plugin and get back the config to apply - r, err := ipam.ExecAdd(n.IPAM.Type, args.StdinData) - if err != nil { - return err - } - - // Convert whatever the IPAM result was into the current Result type - result, err := current.NewResultFromResult(r) + hostInterface, containerInterface, err := setupVeth(netns, br, args.IfName, n.MTU, n.HairpinMode, n.Vlan) if err != nil { return err } - if len(result.IPs) == 0 { - return errors.New("IPAM plugin returned missing IP config") - } + // Assume L2 interface only + result := ¤t.Result{CNIVersion: cniVersion, Interfaces: []*current.Interface{brInterface, hostInterface, containerInterface}} - result.Interfaces = []*current.Interface{brInterface, hostInterface, containerInterface} + if isLayer3 { + // run the IPAM plugin and get back the config to apply + r, err := ipam.ExecAdd(n.IPAM.Type, args.StdinData) + if err != nil { + return err + } - // Gather gateway information for each IP family - gwsV4, gwsV6, err := calcGateways(result, n) - if err != nil { - return err - } + // release IP in case of failure + defer func() { + if !success { + ipam.ExecDel(n.IPAM.Type, args.StdinData) + } + }() - // Configure the container hardware address and IP address(es) - if err := netns.Do(func(_ ns.NetNS) error { - contVeth, err := net.InterfaceByName(args.IfName) + // Convert whatever the IPAM result was into the current Result type + ipamResult, err := current.NewResultFromResult(r) if err != nil { return err } - // Disable IPv6 DAD just in case hairpin mode is enabled on the - // bridge. Hairpin mode causes echos of neighbor solicitation - // packets, which causes DAD failures. - for _, ipc := range result.IPs { - if ipc.Version == "6" && (n.HairpinMode || n.PromiscMode) { - if err := disableIPV6DAD(args.IfName); err != nil { - return err - } - break - } + result.IPs = ipamResult.IPs + result.Routes = ipamResult.Routes + + if len(result.IPs) == 0 { + return errors.New("IPAM plugin returned missing IP config") } - // Add the IP to the interface - if err := ipam.ConfigureIface(args.IfName, result); err != nil { + // Gather gateway information for each IP family + gwsV4, gwsV6, err := calcGateways(result, n) + if err != nil { return err } - // Send a gratuitous arp - for _, ipc := range result.IPs { - if ipc.Version == "4" { - _ = arping.GratuitousArpOverIface(ipc.Address.IP, *contVeth) + // Configure the container hardware address and IP address(es) + if err := netns.Do(func(_ ns.NetNS) error { + contVeth, err := net.InterfaceByName(args.IfName) + if err != nil { + return err } - } - return nil - }); err != nil { - return err - } - if n.IsGW { - var firstV4Addr net.IP - // Set the IP address(es) on the bridge and enable forwarding - for _, gws := range []*gwInfo{gwsV4, gwsV6} { - for _, gw := range gws.gws { - if gw.IP.To4() != nil && firstV4Addr == nil { - firstV4Addr = gw.IP + // Disable IPv6 DAD just in case hairpin mode is enabled on the + // bridge. Hairpin mode causes echos of neighbor solicitation + // packets, which causes DAD failures. + for _, ipc := range result.IPs { + if ipc.Version == "6" && (n.HairpinMode || n.PromiscMode) { + if err := disableIPV6DAD(args.IfName); err != nil { + return err + } + break } + } + + // Add the IP to the interface + if err := ipam.ConfigureIface(args.IfName, result); err != nil { + return err + } - err = ensureBridgeAddr(br, gws.family, &gw, n.ForceAddress) - if err != nil { - return fmt.Errorf("failed to set bridge addr: %v", err) + // Send a gratuitous arp + for _, ipc := range result.IPs { + if ipc.Version == "4" { + _ = arping.GratuitousArpOverIface(ipc.Address.IP, *contVeth) } } + return nil + }); err != nil { + return err + } - if gws.gws != nil { - if err = enableIPForward(gws.family); err != nil { - return fmt.Errorf("failed to enable forwarding: %v", err) + if n.IsGW { + var firstV4Addr net.IP + var vlanInterface *current.Interface + // Set the IP address(es) on the bridge and enable forwarding + for _, gws := range []*gwInfo{gwsV4, gwsV6} { + for _, gw := range gws.gws { + if gw.IP.To4() != nil && firstV4Addr == nil { + firstV4Addr = gw.IP + } + if n.Vlan != 0 { + vlanIface, err := ensureVlanInterface(br, n.Vlan) + if err != nil { + return fmt.Errorf("failed to create vlan interface: %v", err) + } + + if vlanInterface == nil { + vlanInterface = ¤t.Interface{Name: vlanIface.Attrs().Name, + Mac: vlanIface.Attrs().HardwareAddr.String()} + result.Interfaces = append(result.Interfaces, vlanInterface) + } + + err = ensureAddr(vlanIface, gws.family, &gw, n.ForceAddress) + if err != nil { + return fmt.Errorf("failed to set vlan interface for bridge with addr: %v", err) + } + } else { + err = ensureAddr(br, gws.family, &gw, n.ForceAddress) + if err != nil { + return fmt.Errorf("failed to set bridge addr: %v", err) + } + } + } + + if gws.gws != nil { + if err = enableIPForward(gws.family); err != nil { + return fmt.Errorf("failed to enable forwarding: %v", err) + } } } } - } - if n.IPMasq { - chain := utils.FormatChainName(n.Name, args.ContainerID) - comment := utils.FormatComment(n.Name, args.ContainerID) - for _, ipc := range result.IPs { - if err = ip.SetupIPMasq(ip.Network(&ipc.Address), chain, comment); err != nil { - return err + if n.IPMasq { + chain := utils.FormatChainName(n.Name, args.ContainerID) + comment := utils.FormatComment(n.Name, args.ContainerID) + for _, ipc := range result.IPs { + if err = ip.SetupIPMasq(ip.Network(&ipc.Address), chain, comment); err != nil { + return err + } } } } @@ -454,6 +534,13 @@ func cmdAdd(args *skel.CmdArgs) error { result.DNS = n.DNS + // Return an error requested by testcases, if any + if debugPostIPAMError != nil { + return debugPostIPAMError + } + + success = true + return types.PrintResult(result, cniVersion) } @@ -463,8 +550,12 @@ func cmdDel(args *skel.CmdArgs) error { return err } - if err := ipam.ExecDel(n.IPAM.Type, args.StdinData); err != nil { - return err + isLayer3 := n.IPAM.Type != "" + + if isLayer3 { + if err := ipam.ExecDel(n.IPAM.Type, args.StdinData); err != nil { + return err + } } if args.Netns == "" { @@ -488,7 +579,7 @@ func cmdDel(args *skel.CmdArgs) error { return err } - if n.IPMasq { + if isLayer3 && n.IPMasq { chain := utils.FormatChainName(n.Name, args.ContainerID) comment := utils.FormatComment(n.Name, args.ContainerID) for _, ipn := range ipnets { @@ -502,5 +593,269 @@ func cmdDel(args *skel.CmdArgs) error { } func main() { - skel.PluginMain(cmdAdd, cmdDel, version.All) + skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("bridge")) +} + +type cniBridgeIf struct { + Name string + ifIndex int + peerIndex int + masterIndex int + found bool +} + +func validateInterface(intf current.Interface, expectInSb bool) (cniBridgeIf, netlink.Link, error) { + + ifFound := cniBridgeIf{found: false} + if intf.Name == "" { + return ifFound, nil, fmt.Errorf("Interface name missing ") + } + + link, err := netlink.LinkByName(intf.Name) + if err != nil { + return ifFound, nil, fmt.Errorf("Interface name %s not found", intf.Name) + } + + if expectInSb { + if intf.Sandbox == "" { + return ifFound, nil, fmt.Errorf("Interface %s is expected to be in a sandbox", intf.Name) + } + } else { + if intf.Sandbox != "" { + return ifFound, nil, fmt.Errorf("Interface %s should not be in sandbox", intf.Name) + } + } + + return ifFound, link, err +} + +func validateCniBrInterface(intf current.Interface, n *NetConf) (cniBridgeIf, error) { + + brFound, link, err := validateInterface(intf, false) + if err != nil { + return brFound, err + } + + _, isBridge := link.(*netlink.Bridge) + if !isBridge { + return brFound, fmt.Errorf("Interface %s does not have link type of bridge", intf.Name) + } + + if intf.Mac != "" { + if intf.Mac != link.Attrs().HardwareAddr.String() { + return brFound, fmt.Errorf("Bridge interface %s Mac doesn't match: %s", intf.Name, intf.Mac) + } + } + + linkPromisc := link.Attrs().Promisc != 0 + if linkPromisc != n.PromiscMode { + return brFound, fmt.Errorf("Bridge interface %s configured Promisc Mode %v doesn't match current state: %v ", + intf.Name, n.PromiscMode, linkPromisc) + } + + brFound.found = true + brFound.Name = link.Attrs().Name + brFound.ifIndex = link.Attrs().Index + brFound.masterIndex = link.Attrs().MasterIndex + + return brFound, nil +} + +func validateCniVethInterface(intf *current.Interface, brIf cniBridgeIf, contIf cniBridgeIf) (cniBridgeIf, error) { + + vethFound, link, err := validateInterface(*intf, false) + if err != nil { + return vethFound, err + } + + _, isVeth := link.(*netlink.Veth) + if !isVeth { + // just skip it, it's not what CNI created + return vethFound, nil + } + + _, vethFound.peerIndex, err = ip.GetVethPeerIfindex(link.Attrs().Name) + if err != nil { + return vethFound, fmt.Errorf("Unable to obtain veth peer index for veth %s", link.Attrs().Name) + } + vethFound.ifIndex = link.Attrs().Index + vethFound.masterIndex = link.Attrs().MasterIndex + + if vethFound.ifIndex != contIf.peerIndex { + return vethFound, nil + } + + if contIf.ifIndex != vethFound.peerIndex { + return vethFound, nil + } + + if vethFound.masterIndex != brIf.ifIndex { + return vethFound, nil + } + + if intf.Mac != "" { + if intf.Mac != link.Attrs().HardwareAddr.String() { + return vethFound, fmt.Errorf("Interface %s Mac doesn't match: %s not found", intf.Name, intf.Mac) + } + } + + vethFound.found = true + vethFound.Name = link.Attrs().Name + + return vethFound, nil +} + +func validateCniContainerInterface(intf current.Interface) (cniBridgeIf, error) { + + vethFound, link, err := validateInterface(intf, true) + if err != nil { + return vethFound, err + } + + _, isVeth := link.(*netlink.Veth) + if !isVeth { + return vethFound, fmt.Errorf("Error: Container interface %s not of type veth", link.Attrs().Name) + } + _, vethFound.peerIndex, err = ip.GetVethPeerIfindex(link.Attrs().Name) + if err != nil { + return vethFound, fmt.Errorf("Unable to obtain veth peer index for veth %s", link.Attrs().Name) + } + vethFound.ifIndex = link.Attrs().Index + + if intf.Mac != "" { + if intf.Mac != link.Attrs().HardwareAddr.String() { + return vethFound, fmt.Errorf("Interface %s Mac %s doesn't match container Mac: %s", intf.Name, intf.Mac, link.Attrs().HardwareAddr) + } + } + + vethFound.found = true + vethFound.Name = link.Attrs().Name + + return vethFound, nil +} + +func cmdCheck(args *skel.CmdArgs) error { + + n, _, err := loadNetConf(args.StdinData) + if err != nil { + return err + } + netns, err := ns.GetNS(args.Netns) + if err != nil { + return fmt.Errorf("failed to open netns %q: %v", args.Netns, err) + } + defer netns.Close() + + // run the IPAM plugin and get back the config to apply + err = ipam.ExecCheck(n.IPAM.Type, args.StdinData) + if err != nil { + return err + } + + // Parse previous result. + if n.NetConf.RawPrevResult == nil { + return fmt.Errorf("Required prevResult missing") + } + + if err := version.ParsePrevResult(&n.NetConf); err != nil { + return err + } + + result, err := current.NewResultFromResult(n.PrevResult) + if err != nil { + return err + } + + var errLink error + var contCNI, vethCNI cniBridgeIf + var brMap, contMap current.Interface + + // Find interfaces for names whe know, CNI Bridge and container + for _, intf := range result.Interfaces { + if n.BrName == intf.Name { + brMap = *intf + continue + } else if args.IfName == intf.Name { + if args.Netns == intf.Sandbox { + contMap = *intf + continue + } + } + } + + brCNI, err := validateCniBrInterface(brMap, n) + if err != nil { + return err + } + + // The namespace must be the same as what was configured + if args.Netns != contMap.Sandbox { + return fmt.Errorf("Sandbox in prevResult %s doesn't match configured netns: %s", + contMap.Sandbox, args.Netns) + } + + // Check interface against values found in the container + if err := netns.Do(func(_ ns.NetNS) error { + contCNI, errLink = validateCniContainerInterface(contMap) + if errLink != nil { + return errLink + } + return nil + }); err != nil { + return err + } + + // Now look for veth that is peer with container interface. + // Anything else wasn't created by CNI, skip it + for _, intf := range result.Interfaces { + // Skip this result if name is the same as cni bridge + // It's either the cni bridge we dealt with above, or something with the + // same name in a different namespace. We just skip since it's not ours + if brMap.Name == intf.Name { + continue + } + + // same here for container name + if contMap.Name == intf.Name { + continue + } + + vethCNI, errLink = validateCniVethInterface(intf, brCNI, contCNI) + if errLink != nil { + return errLink + } + + if vethCNI.found { + // veth with container interface as peer and bridge as master found + break + } + } + + if !brCNI.found { + return fmt.Errorf("CNI created bridge %s in host namespace was not found", n.BrName) + } + if !contCNI.found { + return fmt.Errorf("CNI created interface in container %s not found", args.IfName) + } + if !vethCNI.found { + return fmt.Errorf("CNI veth created for bridge %s was not found", n.BrName) + } + + // Check prevResults for ips, routes and dns against values found in the container + if err := netns.Do(func(_ ns.NetNS) error { + err = ip.ValidateExpectedInterfaceIPs(args.IfName, result.IPs) + if err != nil { + return err + } + + err = ip.ValidateExpectedRoute(result.Routes) + if err != nil { + return err + } + return nil + }); err != nil { + return err + } + + return nil } diff --git a/vendor/github.com/containernetworking/plugins/plugins/main/bridge/bridge_test.go b/vendor/github.com/containernetworking/plugins/plugins/main/bridge/bridge_test.go index bbfa3a684..97a93645c 100644 --- a/vendor/github.com/containernetworking/plugins/plugins/main/bridge/bridge_test.go +++ b/vendor/github.com/containernetworking/plugins/plugins/main/bridge/bridge_test.go @@ -1,4 +1,4 @@ -// Copyright 2015 CNI authors +// Copyright 2015-2018 CNI authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -15,8 +15,12 @@ package main import ( + "encoding/json" "fmt" + "github.com/vishvananda/netlink/nl" + "io/ioutil" "net" + "os" "strings" "github.com/containernetworking/cni/pkg/skel" @@ -28,15 +32,33 @@ import ( "github.com/vishvananda/netlink" + "github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) const ( - BRNAME = "bridge0" - IFNAME = "eth0" + BRNAME = "bridge0" + BRNAMEVLAN = "bridge0.100" + IFNAME = "eth0" ) +type Net struct { + Name string `json:"name"` + CNIVersion string `json:"cniVersion"` + Type string `json:"type,omitempty"` + BrName string `json:"bridge"` + IPAM *allocator.IPAMConfig `json:"ipam"` + //RuntimeConfig struct { // The capability arg + // IPRanges []RangeSet `json:"ipRanges,omitempty"` + //} `json:"runtimeConfig,omitempty"` + //Args *struct { + // A *IPAMArgs `json:"cni"` + DNS types.DNS `json:"dns"` + RawPrevResult map[string]interface{} `json:"prevResult,omitempty"` + PrevResult current.Result `json:"-"` +} + // testCase defines the CNI network configuration and the expected // bridge addresses for a test case. type testCase struct { @@ -45,7 +67,9 @@ type testCase struct { gateway string // Single subnet config: Gateway ranges []rangeInfo // Ranges list (multiple subnets config) isGW bool + isLayer2 bool expGWCIDRs []string // Expected gateway addresses in CIDR form + vlan int } // Range definition for each entry in the ranges list @@ -75,7 +99,12 @@ const ( "cniVersion": "%s", "name": "testConfig", "type": "bridge", - "bridge": "%s", + "bridge": "%s"` + + vlan = `, + "vlan": %d` + + netDefault = `, "isDefaultGateway": true, "ipMasq": false` @@ -83,6 +112,9 @@ const ( "ipam": { "type": "host-local"` + ipamDataDirStr = `, + "dataDir": "%s"` + // Single subnet configuration (legacy) subnetConfStr = `, "subnet": "%s"` @@ -110,17 +142,30 @@ const ( // netConfJSON() generates a JSON network configuration string // for a test case. -func (tc testCase) netConfJSON() string { +func (tc testCase) netConfJSON(dataDir string) string { conf := fmt.Sprintf(netConfStr, tc.cniVersion, BRNAME) - if tc.subnet != "" || tc.ranges != nil { - conf += ipamStartStr - if tc.subnet != "" { - conf += tc.subnetConfig() - } - if tc.ranges != nil { - conf += tc.rangesConfig() + if tc.vlan != 0 { + conf += fmt.Sprintf(vlan, tc.vlan) + } + + if !tc.isLayer2 { + conf += netDefault + if tc.subnet != "" || tc.ranges != nil { + conf += ipamStartStr + if dataDir != "" { + conf += fmt.Sprintf(ipamDataDirStr, dataDir) + } + if tc.subnet != "" { + conf += tc.subnetConfig() + } + if tc.ranges != nil { + conf += tc.rangesConfig() + } + conf += ipamEndStr } - conf += ipamEndStr + } else { + conf += `, + "ipam": {}` } return "{" + conf + "\n}" } @@ -152,9 +197,26 @@ var counter uint // createCmdArgs generates network configuration and creates command // arguments for a test case. -func (tc testCase) createCmdArgs(targetNS ns.NetNS) *skel.CmdArgs { - conf := tc.netConfJSON() - defer func() { counter += 1 }() +func (tc testCase) createCmdArgs(targetNS ns.NetNS, dataDir string) *skel.CmdArgs { + conf := tc.netConfJSON(dataDir) + //defer func() { counter += 1 }() + return &skel.CmdArgs{ + ContainerID: fmt.Sprintf("dummy-%d", counter), + Netns: targetNS.Path(), + IfName: IFNAME, + StdinData: []byte(conf), + } +} + +// createCheckCmdArgs generates network configuration and creates command +// arguments for a Check test case. +func (tc testCase) createCheckCmdArgs(targetNS ns.NetNS, config *Net, dataDir string) *skel.CmdArgs { + + conf, err := json.Marshal(config) + Expect(err).NotTo(HaveOccurred()) + + // TODO Don't we need to use the same counter as before? + //defer func() { counter += 1 }() return &skel.CmdArgs{ ContainerID: fmt.Sprintf("dummy-%d", counter), Netns: targetNS.Path(), @@ -201,6 +263,38 @@ func delBridgeAddrs(testNS ns.NetNS) { } } + br, err = netlink.LinkByName(BRNAMEVLAN) + if err == nil { + addrs, err = netlink.AddrList(br, netlink.FAMILY_ALL) + Expect(err).NotTo(HaveOccurred()) + for _, addr := range addrs { + if !addr.IP.IsLinkLocalUnicast() { + err = netlink.AddrDel(br, &addr) + Expect(err).NotTo(HaveOccurred()) + } + } + } + + return nil + }) + Expect(err).NotTo(HaveOccurred()) +} + +func delVlanAddrs(testNS ns.NetNS, vlan int) { + err := testNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + vlanLink, err := netlink.LinkByName(fmt.Sprintf("%s.%d", BRNAME, vlan)) + Expect(err).NotTo(HaveOccurred()) + addrs, err := netlink.AddrList(vlanLink, netlink.FAMILY_ALL) + Expect(err).NotTo(HaveOccurred()) + for _, addr := range addrs { + if !addr.IP.IsLinkLocalUnicast() { + err = netlink.AddrDel(vlanLink, &addr) + Expect(err).NotTo(HaveOccurred()) + } + } + return nil }) Expect(err).NotTo(HaveOccurred()) @@ -213,14 +307,45 @@ func ipVersion(ip net.IP) string { return "6" } +func countIPAMIPs(path string) (int, error) { + count := 0 + files, err := ioutil.ReadDir(path) + if err != nil { + return -1, err + } + for _, file := range files { + if file.IsDir() { + continue + } + + if net.ParseIP(file.Name()) != nil { + count++ + } + } + return count, nil +} + +func checkVlan(vlanId int, bridgeVlanInfo []*nl.BridgeVlanInfo) bool { + for _, vlan := range bridgeVlanInfo { + if vlan.Vid == uint16(vlanId) { + return true + } + } + + return false +} + type cmdAddDelTester interface { setNS(testNS ns.NetNS, targetNS ns.NetNS) - cmdAddTest(tc testCase) - cmdDelTest(tc testCase) + cmdAddTest(tc testCase, dataDir string) (*current.Result, error) + cmdCheckTest(tc testCase, conf *Net, dataDir string) + cmdDelTest(tc testCase, dataDir string) } func testerByVersion(version string) cmdAddDelTester { switch { + case strings.HasPrefix(version, "0.4."): + return &testerV04x{} case strings.HasPrefix(version, "0.3."): return &testerV03x{} default: @@ -228,21 +353,21 @@ func testerByVersion(version string) cmdAddDelTester { } } -type testerV03x struct { +type testerV04x struct { testNS ns.NetNS targetNS ns.NetNS args *skel.CmdArgs vethName string } -func (tester *testerV03x) setNS(testNS ns.NetNS, targetNS ns.NetNS) { +func (tester *testerV04x) setNS(testNS ns.NetNS, targetNS ns.NetNS) { tester.testNS = testNS tester.targetNS = targetNS } -func (tester *testerV03x) cmdAddTest(tc testCase) { +func (tester *testerV04x) cmdAddTest(tc testCase, dataDir string) (*current.Result, error) { // Generate network config and command arguments - tester.args = tc.createCmdArgs(tester.targetNS) + tester.args = tc.createCmdArgs(tester.targetNS, dataDir) // Execute cmdADD on the plugin var result *current.Result @@ -371,9 +496,86 @@ func (tester *testerV03x) cmdAddTest(tc testCase) { return nil }) Expect(err).NotTo(HaveOccurred()) + + return result, nil } -func (tester *testerV03x) cmdDelTest(tc testCase) { +func (tester *testerV04x) cmdCheckTest(tc testCase, conf *Net, dataDir string) { + // Generate network config and command arguments + tester.args = tc.createCheckCmdArgs(tester.targetNS, conf, dataDir) + + // Execute cmdCHECK on the plugin + err := tester.testNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + err := testutils.CmdCheckWithArgs(tester.args, func() error { + return cmdCheck(tester.args) + }) + Expect(err).NotTo(HaveOccurred()) + + return nil + }) + Expect(err).NotTo(HaveOccurred()) + + // Find the veth peer in the container namespace and the default route + err = tester.targetNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + link, err := netlink.LinkByName(IFNAME) + Expect(err).NotTo(HaveOccurred()) + Expect(link.Attrs().Name).To(Equal(IFNAME)) + Expect(link).To(BeAssignableToTypeOf(&netlink.Veth{})) + + expCIDRsV4, expCIDRsV6 := tc.expectedCIDRs() + addrs, err := netlink.AddrList(link, netlink.FAMILY_V4) + Expect(err).NotTo(HaveOccurred()) + Expect(len(addrs)).To(Equal(len(expCIDRsV4))) + addrs, err = netlink.AddrList(link, netlink.FAMILY_V6) + Expect(len(addrs)).To(Equal(len(expCIDRsV6) + 1)) //add one for the link-local + Expect(err).NotTo(HaveOccurred()) + // Ignore link local address which may or may not be + // ready when we read addresses. + var foundAddrs int + for _, addr := range addrs { + if !addr.IP.IsLinkLocalUnicast() { + foundAddrs++ + } + } + Expect(foundAddrs).To(Equal(len(expCIDRsV6))) + + // Ensure the default route(s) + routes, err := netlink.RouteList(link, 0) + Expect(err).NotTo(HaveOccurred()) + + var defaultRouteFound4, defaultRouteFound6 bool + for _, cidr := range tc.expGWCIDRs { + gwIP, _, err := net.ParseCIDR(cidr) + Expect(err).NotTo(HaveOccurred()) + var found *bool + if ipVersion(gwIP) == "4" { + found = &defaultRouteFound4 + } else { + found = &defaultRouteFound6 + } + if *found == true { + continue + } + for _, route := range routes { + *found = (route.Dst == nil && route.Src == nil && route.Gw.Equal(gwIP)) + if *found { + break + } + } + Expect(*found).To(Equal(true)) + } + + return nil + }) + Expect(err).NotTo(HaveOccurred()) +} + +func (tester *testerV04x) cmdDelTest(tc testCase, dataDir string) { + tester.args = tc.createCmdArgs(tester.targetNS, dataDir) err := tester.testNS.Do(func(ns.NetNS) error { defer GinkgoRecover() @@ -405,6 +607,243 @@ func (tester *testerV03x) cmdDelTest(tc testCase) { Expect(link).To(BeNil()) return nil }) + Expect(err).NotTo(HaveOccurred()) +} + +type testerV03x struct { + testNS ns.NetNS + targetNS ns.NetNS + args *skel.CmdArgs + vethName string +} + +func (tester *testerV03x) setNS(testNS ns.NetNS, targetNS ns.NetNS) { + tester.testNS = testNS + tester.targetNS = targetNS +} + +func (tester *testerV03x) cmdAddTest(tc testCase, dataDir string) (*current.Result, error) { + // Generate network config and command arguments + tester.args = tc.createCmdArgs(tester.targetNS, dataDir) + + // Execute cmdADD on the plugin + var result *current.Result + err := tester.testNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + r, raw, err := testutils.CmdAddWithArgs(tester.args, func() error { + return cmdAdd(tester.args) + }) + Expect(err).NotTo(HaveOccurred()) + Expect(strings.Index(string(raw), "\"interfaces\":")).Should(BeNumerically(">", 0)) + + result, err = current.GetResult(r) + Expect(err).NotTo(HaveOccurred()) + + if !tc.isLayer2 && tc.vlan != 0 { + Expect(len(result.Interfaces)).To(Equal(4)) + } else { + Expect(len(result.Interfaces)).To(Equal(3)) + } + + Expect(result.Interfaces[0].Name).To(Equal(BRNAME)) + Expect(result.Interfaces[0].Mac).To(HaveLen(17)) + + Expect(result.Interfaces[1].Name).To(HavePrefix("veth")) + Expect(result.Interfaces[1].Mac).To(HaveLen(17)) + + Expect(result.Interfaces[2].Name).To(Equal(IFNAME)) + Expect(result.Interfaces[2].Mac).To(HaveLen(17)) //mac is random + Expect(result.Interfaces[2].Sandbox).To(Equal(tester.targetNS.Path())) + + // Make sure bridge link exists + link, err := netlink.LinkByName(result.Interfaces[0].Name) + Expect(err).NotTo(HaveOccurred()) + Expect(link.Attrs().Name).To(Equal(BRNAME)) + Expect(link).To(BeAssignableToTypeOf(&netlink.Bridge{})) + Expect(link.Attrs().HardwareAddr.String()).To(Equal(result.Interfaces[0].Mac)) + bridgeMAC := link.Attrs().HardwareAddr.String() + + var vlanLink netlink.Link + if !tc.isLayer2 && tc.vlan != 0 { + // Make sure vlan link exists + vlanLink, err = netlink.LinkByName(fmt.Sprintf("%s.%d", BRNAME, tc.vlan)) + Expect(err).NotTo(HaveOccurred()) + Expect(vlanLink.Attrs().Name).To(Equal(fmt.Sprintf("%s.%d", BRNAME, tc.vlan))) + Expect(vlanLink).To(BeAssignableToTypeOf(&netlink.Veth{})) + + // Check the bridge dot vlan interface have the vlan tag + peerLink, err := netlink.LinkByIndex(vlanLink.Attrs().Index - 1) + Expect(err).NotTo(HaveOccurred()) + interfaceMap, err := netlink.BridgeVlanList() + Expect(err).NotTo(HaveOccurred()) + vlans, isExist := interfaceMap[int32(peerLink.Attrs().Index)] + Expect(isExist).To(BeTrue()) + Expect(checkVlan(tc.vlan, vlans)).To(BeTrue()) + } + + // Check the bridge vlan filtering equals true + if tc.vlan != 0 { + Expect(*link.(*netlink.Bridge).VlanFiltering).To(Equal(true)) + } else { + Expect(*link.(*netlink.Bridge).VlanFiltering).To(Equal(false)) + } + + // Ensure bridge has expected gateway address(es) + var addrs []netlink.Addr + if tc.vlan == 0 { + addrs, err = netlink.AddrList(link, netlink.FAMILY_ALL) + } else { + addrs, err = netlink.AddrList(vlanLink, netlink.FAMILY_ALL) + } + Expect(err).NotTo(HaveOccurred()) + Expect(len(addrs)).To(BeNumerically(">", 0)) + for _, cidr := range tc.expGWCIDRs { + ip, subnet, err := net.ParseCIDR(cidr) + Expect(err).NotTo(HaveOccurred()) + + found := false + subnetPrefix, subnetBits := subnet.Mask.Size() + for _, a := range addrs { + aPrefix, aBits := a.IPNet.Mask.Size() + if a.IPNet.IP.Equal(ip) && aPrefix == subnetPrefix && aBits == subnetBits { + found = true + break + } + } + Expect(found).To(Equal(true)) + } + + // Check for the veth link in the main namespace + links, err := netlink.LinkList() + Expect(err).NotTo(HaveOccurred()) + if !tc.isLayer2 && tc.vlan != 0 { + Expect(len(links)).To(Equal(5)) // Bridge, Bridge vlan veth, veth, and loopback + } else { + Expect(len(links)).To(Equal(3)) // Bridge, veth, and loopback + } + + link, err = netlink.LinkByName(result.Interfaces[1].Name) + Expect(err).NotTo(HaveOccurred()) + Expect(link).To(BeAssignableToTypeOf(&netlink.Veth{})) + tester.vethName = result.Interfaces[1].Name + + // check vlan exist on the veth interface + if tc.vlan != 0 { + interfaceMap, err := netlink.BridgeVlanList() + Expect(err).NotTo(HaveOccurred()) + vlans, isExist := interfaceMap[int32(link.Attrs().Index)] + Expect(isExist).To(BeTrue()) + Expect(checkVlan(tc.vlan, vlans)).To(BeTrue()) + } + + // Check that the bridge has a different mac from the veth + // If not, it means the bridge has an unstable mac and will change + // as ifs are added and removed + // this check is not relevant for a layer 2 bridge + if !tc.isLayer2 && tc.vlan == 0 { + Expect(link.Attrs().HardwareAddr.String()).NotTo(Equal(bridgeMAC)) + } + + return nil + }) + Expect(err).NotTo(HaveOccurred()) + + // Find the veth peer in the container namespace and the default route + err = tester.targetNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + link, err := netlink.LinkByName(IFNAME) + Expect(err).NotTo(HaveOccurred()) + Expect(link.Attrs().Name).To(Equal(IFNAME)) + Expect(link).To(BeAssignableToTypeOf(&netlink.Veth{})) + + expCIDRsV4, expCIDRsV6 := tc.expectedCIDRs() + addrs, err := netlink.AddrList(link, netlink.FAMILY_V4) + Expect(err).NotTo(HaveOccurred()) + Expect(len(addrs)).To(Equal(len(expCIDRsV4))) + addrs, err = netlink.AddrList(link, netlink.FAMILY_V6) + Expect(len(addrs)).To(Equal(len(expCIDRsV6) + 1)) //add one for the link-local + Expect(err).NotTo(HaveOccurred()) + // Ignore link local address which may or may not be + // ready when we read addresses. + var foundAddrs int + for _, addr := range addrs { + if !addr.IP.IsLinkLocalUnicast() { + foundAddrs++ + } + } + Expect(foundAddrs).To(Equal(len(expCIDRsV6))) + + // Ensure the default route(s) + routes, err := netlink.RouteList(link, 0) + Expect(err).NotTo(HaveOccurred()) + + var defaultRouteFound4, defaultRouteFound6 bool + for _, cidr := range tc.expGWCIDRs { + gwIP, _, err := net.ParseCIDR(cidr) + Expect(err).NotTo(HaveOccurred()) + var found *bool + if ipVersion(gwIP) == "4" { + found = &defaultRouteFound4 + } else { + found = &defaultRouteFound6 + } + if *found == true { + continue + } + for _, route := range routes { + *found = (route.Dst == nil && route.Src == nil && route.Gw.Equal(gwIP)) + if *found { + break + } + } + Expect(*found).To(Equal(true)) + } + + return nil + }) + Expect(err).NotTo(HaveOccurred()) + return result, nil +} + +func (tester *testerV03x) cmdCheckTest(tc testCase, conf *Net, dataDir string) { + return +} + +func (tester *testerV03x) cmdDelTest(tc testCase, dataDir string) { + err := tester.testNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + err := testutils.CmdDelWithArgs(tester.args, func() error { + return cmdDel(tester.args) + }) + Expect(err).NotTo(HaveOccurred()) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + + // Make sure the host veth has been deleted + err = tester.targetNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + link, err := netlink.LinkByName(IFNAME) + Expect(err).To(HaveOccurred()) + Expect(link).To(BeNil()) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + + // Make sure the container veth has been deleted + err = tester.testNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + link, err := netlink.LinkByName(tester.vethName) + Expect(err).To(HaveOccurred()) + Expect(link).To(BeNil()) + return nil + }) + Expect(err).NotTo(HaveOccurred()) } type testerV01xOr02x struct { @@ -419,9 +858,9 @@ func (tester *testerV01xOr02x) setNS(testNS ns.NetNS, targetNS ns.NetNS) { tester.targetNS = targetNS } -func (tester *testerV01xOr02x) cmdAddTest(tc testCase) { +func (tester *testerV01xOr02x) cmdAddTest(tc testCase, dataDir string) (*current.Result, error) { // Generate network config and calculate gateway addresses - tester.args = tc.createCmdArgs(tester.targetNS) + tester.args = tc.createCmdArgs(tester.targetNS, dataDir) // Execute cmdADD on the plugin err := tester.testNS.Do(func(ns.NetNS) error { @@ -510,9 +949,14 @@ func (tester *testerV01xOr02x) cmdAddTest(tc testCase) { return nil }) Expect(err).NotTo(HaveOccurred()) + return nil, nil +} + +func (tester *testerV01xOr02x) cmdCheckTest(tc testCase, conf *Net, dataDir string) { + return } -func (tester *testerV01xOr02x) cmdDelTest(tc testCase) { +func (tester *testerV01xOr02x) cmdDelTest(tc testCase, dataDir string) { err := tester.testNS.Do(func(ns.NetNS) error { defer GinkgoRecover() @@ -537,7 +981,77 @@ func (tester *testerV01xOr02x) cmdDelTest(tc testCase) { Expect(err).NotTo(HaveOccurred()) } -func cmdAddDelTest(testNS ns.NetNS, tc testCase) { +func cmdAddDelTest(testNS ns.NetNS, tc testCase, dataDir string) { + // Get a Add/Del tester based on test case version + tester := testerByVersion(tc.cniVersion) + + targetNS, err := testutils.NewNS() + Expect(err).NotTo(HaveOccurred()) + defer targetNS.Close() + tester.setNS(testNS, targetNS) + + // Test IP allocation + result, err := tester.cmdAddTest(tc, dataDir) + Expect(err).NotTo(HaveOccurred()) + + if strings.HasPrefix(tc.cniVersion, "0.3.") { + Expect(result).NotTo(BeNil()) + } else { + Expect(result).To(BeNil()) + } + + // Test IP Release + tester.cmdDelTest(tc, dataDir) + + // Clean up bridge addresses for next test case + delBridgeAddrs(testNS) +} + +func buildOneConfig(name, cniVersion string, orig *Net, prevResult types.Result) (*Net, error) { + var err error + + inject := map[string]interface{}{ + "name": name, + "cniVersion": cniVersion, + } + // Add previous plugin result + if prevResult != nil { + inject["prevResult"] = prevResult + } + + // Ensure every config uses the same name and version + config := make(map[string]interface{}) + confBytes, err := json.Marshal(orig) + if err != nil { + return nil, err + } + + err = json.Unmarshal(confBytes, &config) + if err != nil { + return nil, fmt.Errorf("unmarshal existing network bytes: %s", err) + } + + for key, value := range inject { + config[key] = value + } + + newBytes, err := json.Marshal(config) + if err != nil { + return nil, err + } + + conf := &Net{} + if err := json.Unmarshal(newBytes, &conf); err != nil { + return nil, fmt.Errorf("error parsing configuration: %s", err) + } + + return conf, nil + +} + +func cmdAddDelCheckTest(testNS ns.NetNS, tc testCase, dataDir string) { + Expect(tc.cniVersion).To(Equal("0.4.0")) + // Get a Add/Del tester based on test case version tester := testerByVersion(tc.cniVersion) @@ -547,26 +1061,56 @@ func cmdAddDelTest(testNS ns.NetNS, tc testCase) { tester.setNS(testNS, targetNS) // Test IP allocation - tester.cmdAddTest(tc) + prevResult, err := tester.cmdAddTest(tc, dataDir) + Expect(err).NotTo(HaveOccurred()) + + Expect(prevResult).NotTo(BeNil()) + + confString := tc.netConfJSON(dataDir) + + conf := &Net{} + err = json.Unmarshal([]byte(confString), &conf) + Expect(err).NotTo(HaveOccurred()) + + conf.IPAM, _, err = allocator.LoadIPAMConfig([]byte(confString), "") + Expect(err).NotTo(HaveOccurred()) + + newConf, err := buildOneConfig("testConfig", tc.cniVersion, conf, prevResult) + Expect(err).NotTo(HaveOccurred()) + + // Test CHECK + tester.cmdCheckTest(tc, newConf, dataDir) // Test IP Release - tester.cmdDelTest(tc) + tester.cmdDelTest(tc, dataDir) // Clean up bridge addresses for next test case delBridgeAddrs(testNS) + + if tc.vlan != 0 && !tc.isLayer2 { + delVlanAddrs(testNS, tc.vlan) + } } var _ = Describe("bridge Operations", func() { var originalNS ns.NetNS + var dataDir string BeforeEach(func() { // Create a new NetNS so we don't modify the host var err error originalNS, err = testutils.NewNS() Expect(err).NotTo(HaveOccurred()) + + dataDir, err = ioutil.TempDir("", "bridge_test") + Expect(err).NotTo(HaveOccurred()) + + // Do not emulate an error, each test will set this if needed + debugPostIPAMError = nil }) AfterEach(func() { + Expect(os.RemoveAll(dataDir)).To(Succeed()) Expect(originalNS.Close()).To(Succeed()) }) @@ -661,7 +1205,74 @@ var _ = Describe("bridge Operations", func() { } for _, tc := range testCases { tc.cniVersion = "0.3.0" - cmdAddDelTest(originalNS, tc) + cmdAddDelTest(originalNS, tc, dataDir) + } + }) + + It("configures and deconfigures a l2 bridge and veth with ADD/DEL for 0.3.1 config", func() { + tc := testCase{cniVersion: "0.3.0", isLayer2: true} + cmdAddDelTest(originalNS, tc, dataDir) + }) + + It("configures and deconfigures a l2 bridge and veth with ADD/DEL for 0.3.1 config", func() { + tc := testCase{cniVersion: "0.3.1", isLayer2: true} + cmdAddDelTest(originalNS, tc, dataDir) + }) + + It("configures and deconfigures a l2 bridge with vlan id 100 using ADD/DEL for 0.3.1 config", func() { + tc := testCase{cniVersion: "0.3.0", isLayer2: true, vlan: 100} + cmdAddDelTest(originalNS, tc, dataDir) + }) + + It("configures and deconfigures a l2 bridge with vlan id 100 using ADD/DEL for 0.3.1 config", func() { + tc := testCase{cniVersion: "0.3.1", isLayer2: true, vlan: 100} + cmdAddDelTest(originalNS, tc, dataDir) + }) + + It("configures and deconfigures a bridge, veth with default route and vlanID 100 with ADD/DEL for 0.3.0 config", func() { + testCases := []testCase{ + { + // IPv4 only + subnet: "10.1.2.0/24", + expGWCIDRs: []string{"10.1.2.1/24"}, + vlan: 100, + }, + { + // IPv6 only + subnet: "2001:db8::0/64", + expGWCIDRs: []string{"2001:db8::1/64"}, + vlan: 100, + }, + { + // Dual-Stack + ranges: []rangeInfo{ + {subnet: "192.168.0.0/24"}, + {subnet: "fd00::0/64"}, + }, + expGWCIDRs: []string{ + "192.168.0.1/24", + "fd00::1/64", + }, + vlan: 100, + }, + { + // 3 Subnets (1 IPv4 and 2 IPv6 subnets) + ranges: []rangeInfo{ + {subnet: "192.168.0.0/24"}, + {subnet: "fd00::0/64"}, + {subnet: "2001:db8::0/64"}, + }, + expGWCIDRs: []string{ + "192.168.0.1/24", + "fd00::1/64", + "2001:db8::1/64", + }, + vlan: 100, + }, + } + for _, tc := range testCases { + tc.cniVersion = "0.3.0" + cmdAddDelTest(originalNS, tc, dataDir) } }) @@ -691,7 +1302,7 @@ var _ = Describe("bridge Operations", func() { } for _, tc := range testCases { tc.cniVersion = "0.3.1" - cmdAddDelTest(originalNS, tc) + cmdAddDelTest(originalNS, tc, dataDir) } }) @@ -707,10 +1318,10 @@ var _ = Describe("bridge Operations", func() { Expect(err).NotTo(HaveOccurred()) defer targetNS.Close() tester.setNS(originalNS, targetNS) - tester.args = tc.createCmdArgs(targetNS) + tester.args = tc.createCmdArgs(targetNS, dataDir) // Execute cmdDEL on the plugin, expect no errors - tester.cmdDelTest(tc) + tester.cmdDelTest(tc, dataDir) }) It("configures and deconfigures a bridge and veth with default route with ADD/DEL for 0.1.0 config", func() { @@ -739,7 +1350,7 @@ var _ = Describe("bridge Operations", func() { } for _, tc := range testCases { tc.cniVersion = "0.1.0" - cmdAddDelTest(originalNS, tc) + cmdAddDelTest(originalNS, tc, dataDir) } }) @@ -813,13 +1424,13 @@ var _ = Describe("bridge Operations", func() { Expect(conf.ForceAddress).To(Equal(false)) // Set first address on bridge - err = ensureBridgeAddr(bridge, family, &gwnFirst, conf.ForceAddress) + err = ensureAddr(bridge, family, &gwnFirst, conf.ForceAddress) Expect(err).NotTo(HaveOccurred()) checkBridgeIPs(tc.gwCIDRFirst, "") // Attempt to set the second address on the bridge // with ForceAddress set to false. - err = ensureBridgeAddr(bridge, family, &gwnSecond, false) + err = ensureAddr(bridge, family, &gwnSecond, false) if family == netlink.FAMILY_V4 || subnetsOverlap { // IPv4 or overlapping IPv6 subnets: // Expect an error, and address should remain the same @@ -835,7 +1446,7 @@ var _ = Describe("bridge Operations", func() { // Set the second address on the bridge // with ForceAddress set to true. - err = ensureBridgeAddr(bridge, family, &gwnSecond, true) + err = ensureAddr(bridge, family, &gwnSecond, true) Expect(err).NotTo(HaveOccurred()) if family == netlink.FAMILY_V4 || subnetsOverlap { // IPv4 or overlapping IPv6 subnets: @@ -905,15 +1516,83 @@ var _ = Describe("bridge Operations", func() { for _, tc := range testCases { tc.cniVersion = "0.3.1" _, _, err := setupBridge(tc.netConf()) + Expect(err).NotTo(HaveOccurred()) link, err := netlink.LinkByName(BRNAME) Expect(err).NotTo(HaveOccurred()) origMac := link.Attrs().HardwareAddr - cmdAddDelTest(originalNS, tc) + cmdAddDelTest(originalNS, tc, dataDir) link, err = netlink.LinkByName(BRNAME) Expect(err).NotTo(HaveOccurred()) Expect(link.Attrs().HardwareAddr).To(Equal(origMac)) } }) + + It("checks ip release in case of error", func() { + err := originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + tc := testCase{ + cniVersion: "0.3.1", + subnet: "10.1.2.0/24", + } + + _, _, err := setupBridge(tc.netConf()) + Expect(err).NotTo(HaveOccurred()) + + args := tc.createCmdArgs(originalNS, dataDir) + + // get number of allocated IPs before asking for a new one + before, err := countIPAMIPs(dataDir) + Expect(err).NotTo(HaveOccurred()) + + debugPostIPAMError = fmt.Errorf("debugPostIPAMError") + _, _, err = testutils.CmdAddWithArgs(args, func() error { + return cmdAdd(args) + }) + Expect(err).To(MatchError("debugPostIPAMError")) + + // get number of allocated IPs after failure + after, err := countIPAMIPs(dataDir) + Expect(err).NotTo(HaveOccurred()) + + Expect(before).To(Equal(after)) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + }) + + It("configures and deconfigures a bridge and veth with default route with ADD/DEL/CHECK for 0.4.0 config", func() { + testCases := []testCase{ + { + // IPv4 only + ranges: []rangeInfo{{ + subnet: "10.1.2.0/24", + }}, + expGWCIDRs: []string{"10.1.2.1/24"}, + }, + { + // IPv6 only + ranges: []rangeInfo{{ + subnet: "2001:db8::0/64", + }}, + expGWCIDRs: []string{"2001:db8::1/64"}, + }, + { + // Dual-Stack + ranges: []rangeInfo{ + {subnet: "192.168.0.0/24"}, + {subnet: "fd00::0/64"}, + }, + expGWCIDRs: []string{ + "192.168.0.1/24", + "fd00::1/64", + }, + }, + } + for _, tc := range testCases { + tc.cniVersion = "0.4.0" + cmdAddDelCheckTest(originalNS, tc, dataDir) + } + }) }) diff --git a/vendor/github.com/containernetworking/plugins/plugins/main/host-device/README.md b/vendor/github.com/containernetworking/plugins/plugins/main/host-device/README.md index 1e0af84ae..b06e2169b 100644 --- a/vendor/github.com/containernetworking/plugins/plugins/main/host-device/README.md +++ b/vendor/github.com/containernetworking/plugins/plugins/main/host-device/README.md @@ -1,5 +1,5 @@ # host-device -Move an already-existing device in to a container. +Move an already-existing device into a container. This simple plugin will move the requested device from the host's network namespace to the container's. Nothing else will be done - no IPAM, no addresses. @@ -16,6 +16,7 @@ A sample configuration might look like: ```json { "cniVersion": "0.3.1", + "type": "host-device", "device": "enp0s1" } ``` diff --git a/vendor/github.com/containernetworking/plugins/plugins/main/host-device/host-device.go b/vendor/github.com/containernetworking/plugins/plugins/main/host-device/host-device.go index fea5b9804..f82e2967b 100644 --- a/vendor/github.com/containernetworking/plugins/plugins/main/host-device/host-device.go +++ b/vendor/github.com/containernetworking/plugins/plugins/main/host-device/host-device.go @@ -17,6 +17,7 @@ package main import ( "bytes" "encoding/json" + "errors" "fmt" "io/ioutil" "net" @@ -24,14 +25,20 @@ import ( "runtime" "strings" + "github.com/vishvananda/netlink" + "github.com/containernetworking/cni/pkg/skel" "github.com/containernetworking/cni/pkg/types" "github.com/containernetworking/cni/pkg/types/current" "github.com/containernetworking/cni/pkg/version" + + "github.com/containernetworking/plugins/pkg/ip" + "github.com/containernetworking/plugins/pkg/ipam" "github.com/containernetworking/plugins/pkg/ns" - "github.com/vishvananda/netlink" + bv "github.com/containernetworking/plugins/pkg/utils/buildversion" ) +//NetConf for host-device config, look the README to learn how to use those parameters type NetConf struct { types.NetConf Device string `json:"device"` // Device-Name, something like eth0 or can0 etc. @@ -77,14 +84,68 @@ func cmdAdd(args *skel.CmdArgs) error { if err != nil { return fmt.Errorf("failed to move link %v", err) } + + var result *current.Result + // run the IPAM plugin and get back the config to apply + if cfg.IPAM.Type != "" { + r, err := ipam.ExecAdd(cfg.IPAM.Type, args.StdinData) + if err != nil { + return err + } + + // Invoke ipam del if err to avoid ip leak + defer func() { + if err != nil { + ipam.ExecDel(cfg.IPAM.Type, args.StdinData) + } + }() + + // Convert whatever the IPAM result was into the current Result type + result, err = current.NewResultFromResult(r) + if err != nil { + return err + } + + if len(result.IPs) == 0 { + return errors.New("IPAM plugin returned missing IP config") + } + + result.Interfaces = []*current.Interface{{ + Name: contDev.Attrs().Name, + Mac: contDev.Attrs().HardwareAddr.String(), + Sandbox: containerNs.Path(), + }} + for _, ipc := range result.IPs { + // All addresses apply to the container interface (move from host) + ipc.Interface = current.Int(0) + } + + err = containerNs.Do(func(_ ns.NetNS) error { + if err := ipam.ConfigureIface(args.IfName, result); err != nil { + return err + } + return nil + }) + if err != nil { + return err + } + + result.DNS = cfg.DNS + + return types.PrintResult(result, cfg.CNIVersion) + } + return printLink(contDev, cfg.CNIVersion, containerNs) } func cmdDel(args *skel.CmdArgs) error { - _, err := loadConf(args.StdinData) + cfg, err := loadConf(args.StdinData) if err != nil { return err } + if args.Netns == "" { + return nil + } containerNs, err := ns.GetNS(args.Netns) if err != nil { return fmt.Errorf("failed to open netns %q: %v", args.Netns, err) @@ -95,6 +156,12 @@ func cmdDel(args *skel.CmdArgs) error { return err } + if cfg.IPAM.Type != "" { + if err := ipam.ExecDel(cfg.IPAM.Type, args.StdinData); err != nil { + return err + } + } + return nil } @@ -217,5 +284,109 @@ func getLink(devname, hwaddr, kernelpath string) (netlink.Link, error) { } func main() { - skel.PluginMain(cmdAdd, cmdDel, version.All) + skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("host-device")) +} + +func cmdCheck(args *skel.CmdArgs) error { + + cfg, err := loadConf(args.StdinData) + if err != nil { + return err + } + netns, err := ns.GetNS(args.Netns) + if err != nil { + return fmt.Errorf("failed to open netns %q: %v", args.Netns, err) + } + defer netns.Close() + + // run the IPAM plugin and get back the config to apply + if cfg.IPAM.Type != "" { + err = ipam.ExecCheck(cfg.IPAM.Type, args.StdinData) + if err != nil { + return err + } + } + + // Parse previous result. + if cfg.NetConf.RawPrevResult == nil { + return fmt.Errorf("Required prevResult missing") + } + + if err := version.ParsePrevResult(&cfg.NetConf); err != nil { + return err + } + + result, err := current.NewResultFromResult(cfg.PrevResult) + if err != nil { + return err + } + + var contMap current.Interface + // Find interfaces for name we know, that of host-device inside container + for _, intf := range result.Interfaces { + if args.IfName == intf.Name { + if args.Netns == intf.Sandbox { + contMap = *intf + continue + } + } + } + + // The namespace must be the same as what was configured + if args.Netns != contMap.Sandbox { + return fmt.Errorf("Sandbox in prevResult %s doesn't match configured netns: %s", + contMap.Sandbox, args.Netns) + } + + // + // Check prevResults for ips, routes and dns against values found in the container + if err := netns.Do(func(_ ns.NetNS) error { + + // Check interface against values found in the container + err := validateCniContainerInterface(contMap) + if err != nil { + return err + } + + err = ip.ValidateExpectedInterfaceIPs(args.IfName, result.IPs) + if err != nil { + return err + } + + err = ip.ValidateExpectedRoute(result.Routes) + if err != nil { + return err + } + return nil + }); err != nil { + return err + } + + // + return nil +} + +func validateCniContainerInterface(intf current.Interface) error { + + var link netlink.Link + var err error + + if intf.Name == "" { + return fmt.Errorf("Container interface name missing in prevResult: %v", intf.Name) + } + link, err = netlink.LinkByName(intf.Name) + if err != nil { + return fmt.Errorf("Container Interface name in prevResult: %s not found", intf.Name) + } + if intf.Sandbox == "" { + return fmt.Errorf("Error: Container interface %s should not be in host namespace", link.Attrs().Name) + } + + if intf.Mac != "" { + if intf.Mac != link.Attrs().HardwareAddr.String() { + return fmt.Errorf("Interface %s Mac %s doesn't match container Mac: %s", intf.Name, intf.Mac, link.Attrs().HardwareAddr) + } + } + + return nil } diff --git a/vendor/github.com/containernetworking/plugins/plugins/main/host-device/host-device_test.go b/vendor/github.com/containernetworking/plugins/plugins/main/host-device/host-device_test.go index bbecdec74..6a230044b 100644 --- a/vendor/github.com/containernetworking/plugins/plugins/main/host-device/host-device_test.go +++ b/vendor/github.com/containernetworking/plugins/plugins/main/host-device/host-device_test.go @@ -15,19 +15,210 @@ package main import ( + "encoding/json" "fmt" "math/rand" + "net" + "strings" "github.com/containernetworking/cni/pkg/skel" "github.com/containernetworking/cni/pkg/types" + types020 "github.com/containernetworking/cni/pkg/types/020" "github.com/containernetworking/cni/pkg/types/current" "github.com/containernetworking/plugins/pkg/ns" "github.com/containernetworking/plugins/pkg/testutils" + . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" "github.com/vishvananda/netlink" ) +type Net struct { + Name string `json:"name"` + CNIVersion string `json:"cniVersion"` + Type string `json:"type,omitempty"` + Device string `json:"device"` // Device-Name, something like eth0 or can0 etc. + HWAddr string `json:"hwaddr"` // MAC Address of target network interface + KernelPath string `json:"kernelpath"` // Kernelpath of the device + IPAM *IPAMConfig `json:"ipam,omitempty"` + DNS types.DNS `json:"dns"` + RawPrevResult map[string]interface{} `json:"prevResult,omitempty"` + PrevResult current.Result `json:"-"` +} + +type IPAMConfig struct { + Name string + Type string `json:"type"` + Routes []*types.Route `json:"routes"` + Addresses []Address `json:"addresses,omitempty"` + DNS types.DNS `json:"dns"` +} + +type IPAMEnvArgs struct { + types.CommonArgs + IP types.UnmarshallableString `json:"ip,omitempty"` + GATEWAY types.UnmarshallableString `json:"gateway,omitempty"` +} + +type Address struct { + AddressStr string `json:"address"` + Gateway net.IP `json:"gateway,omitempty"` + Address net.IPNet + Version string +} + +// canonicalizeIP makes sure a provided ip is in standard form +func canonicalizeIP(ip *net.IP) error { + if ip.To4() != nil { + *ip = ip.To4() + return nil + } else if ip.To16() != nil { + *ip = ip.To16() + return nil + } + return fmt.Errorf("IP %s not v4 nor v6", *ip) +} + +// LoadIPAMConfig creates IPAMConfig using json encoded configuration provided +// as `bytes`. At the moment values provided in envArgs are ignored so there +// is no possibility to overload the json configuration using envArgs +func LoadIPAMConfig(bytes []byte, envArgs string) (*IPAMConfig, string, error) { + n := Net{} + if err := json.Unmarshal(bytes, &n); err != nil { + return nil, "", err + } + + if n.IPAM == nil { + return nil, "", fmt.Errorf("IPAM config missing 'ipam' key") + } + + // Validate all ranges + numV4 := 0 + numV6 := 0 + + for i := range n.IPAM.Addresses { + ip, addr, err := net.ParseCIDR(n.IPAM.Addresses[i].AddressStr) + if err != nil { + return nil, "", fmt.Errorf("invalid CIDR %s: %s", n.IPAM.Addresses[i].AddressStr, err) + } + n.IPAM.Addresses[i].Address = *addr + n.IPAM.Addresses[i].Address.IP = ip + + if err := canonicalizeIP(&n.IPAM.Addresses[i].Address.IP); err != nil { + return nil, "", fmt.Errorf("invalid address %d: %s", i, err) + } + + if n.IPAM.Addresses[i].Address.IP.To4() != nil { + n.IPAM.Addresses[i].Version = "4" + numV4++ + } else { + n.IPAM.Addresses[i].Version = "6" + numV6++ + } + } + + if envArgs != "" { + e := IPAMEnvArgs{} + err := types.LoadArgs(envArgs, &e) + if err != nil { + return nil, "", err + } + + if e.IP != "" { + for _, item := range strings.Split(string(e.IP), ",") { + ipstr := strings.TrimSpace(item) + + ip, subnet, err := net.ParseCIDR(ipstr) + if err != nil { + return nil, "", fmt.Errorf("invalid CIDR %s: %s", ipstr, err) + } + + addr := Address{Address: net.IPNet{IP: ip, Mask: subnet.Mask}} + if addr.Address.IP.To4() != nil { + addr.Version = "4" + numV4++ + } else { + addr.Version = "6" + numV6++ + } + n.IPAM.Addresses = append(n.IPAM.Addresses, addr) + } + } + + if e.GATEWAY != "" { + for _, item := range strings.Split(string(e.GATEWAY), ",") { + gwip := net.ParseIP(strings.TrimSpace(item)) + if gwip == nil { + return nil, "", fmt.Errorf("invalid gateway address: %s", item) + } + + for i := range n.IPAM.Addresses { + if n.IPAM.Addresses[i].Address.Contains(gwip) { + n.IPAM.Addresses[i].Gateway = gwip + } + } + } + } + } + + // CNI spec 0.2.0 and below supported only one v4 and v6 address + if numV4 > 1 || numV6 > 1 { + for _, v := range types020.SupportedVersions { + if n.CNIVersion == v { + return nil, "", fmt.Errorf("CNI version %v does not support more than 1 address per family", n.CNIVersion) + } + } + } + + // Copy net name into IPAM so not to drag Net struct around + n.IPAM.Name = n.Name + + return n.IPAM, n.CNIVersion, nil +} + +func buildOneConfig(name, cniVersion string, orig *Net, prevResult types.Result) (*Net, error) { + var err error + + inject := map[string]interface{}{ + "name": name, + "cniVersion": cniVersion, + } + // Add previous plugin result + if prevResult != nil { + inject["prevResult"] = prevResult + } + + // Ensure every config uses the same name and version + config := make(map[string]interface{}) + + confBytes, err := json.Marshal(orig) + if err != nil { + return nil, err + } + + err = json.Unmarshal(confBytes, &config) + if err != nil { + return nil, fmt.Errorf("unmarshal existing network bytes: %s", err) + } + + for key, value := range inject { + config[key] = value + } + + newBytes, err := json.Marshal(config) + if err != nil { + return nil, err + } + + conf := &Net{} + if err := json.Unmarshal(newBytes, &conf); err != nil { + return nil, fmt.Errorf("error parsing configuration: %s", err) + } + + return conf, nil + +} + var _ = Describe("base functionality", func() { var originalNS ns.NetNS var ifname string @@ -44,7 +235,7 @@ var _ = Describe("base functionality", func() { originalNS.Close() }) - It("Works with a valid config", func() { + It("Works with a valid config without IPAM", func() { var origLink netlink.Link // prepare ifname in original namespace @@ -68,17 +259,117 @@ var _ = Describe("base functionality", func() { targetNS, err := testutils.NewNS() Expect(err).NotTo(HaveOccurred()) - CNI_IFNAME := "eth0" + cniName := "eth0" + conf := fmt.Sprintf(`{ + "cniVersion": "0.3.0", + "name": "cni-plugin-host-device-test", + "type": "host-device", + "device": %q + }`, ifname) + args := &skel.CmdArgs{ + ContainerID: "dummy", + Netns: targetNS.Path(), + IfName: cniName, + StdinData: []byte(conf), + } + var resI types.Result + err = originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + var err error + resI, _, err = testutils.CmdAddWithArgs(args, func() error { return cmdAdd(args) }) + return err + }) + Expect(err).NotTo(HaveOccurred()) + + // check that the result was sane + res, err := current.NewResultFromResult(resI) + Expect(err).NotTo(HaveOccurred()) + Expect(res.Interfaces).To(Equal([]*current.Interface{ + { + Name: cniName, + Mac: origLink.Attrs().HardwareAddr.String(), + Sandbox: targetNS.Path(), + }, + })) + + // assert that dummy0 is now in the target namespace + err = targetNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + link, err := netlink.LinkByName(cniName) + Expect(err).NotTo(HaveOccurred()) + Expect(link.Attrs().HardwareAddr).To(Equal(origLink.Attrs().HardwareAddr)) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + + // assert that dummy0 is now NOT in the original namespace anymore + err = originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + _, err := netlink.LinkByName(ifname) + Expect(err).To(HaveOccurred()) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + + // Check that deleting the device moves it back and restores the name + err = originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + err = testutils.CmdDelWithArgs(args, func() error { + return cmdDel(args) + }) + Expect(err).NotTo(HaveOccurred()) + + _, err := netlink.LinkByName(ifname) + Expect(err).NotTo(HaveOccurred()) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + + }) + + It("Works with a valid config with IPAM", func() { + var origLink netlink.Link + + // prepare ifname in original namespace + err := originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + err := netlink.LinkAdd(&netlink.Dummy{ + LinkAttrs: netlink.LinkAttrs{ + Name: ifname, + }, + }) + Expect(err).NotTo(HaveOccurred()) + origLink, err = netlink.LinkByName(ifname) + Expect(err).NotTo(HaveOccurred()) + err = netlink.LinkSetUp(origLink) + Expect(err).NotTo(HaveOccurred()) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + + // call CmdAdd + targetNS, err := testutils.NewNS() + Expect(err).NotTo(HaveOccurred()) + targetIP := "10.10.0.1/24" + cniName := "eth0" conf := fmt.Sprintf(`{ "cniVersion": "0.3.0", "name": "cni-plugin-host-device-test", "type": "host-device", + "ipam": { + "type": "static", + "addresses": [ + { + "address":"`+targetIP+`", + "gateway": "10.10.0.254" + }] + }, "device": %q }`, ifname) args := &skel.CmdArgs{ ContainerID: "dummy", Netns: targetNS.Path(), - IfName: CNI_IFNAME, + IfName: cniName, StdinData: []byte(conf), } var resI types.Result @@ -95,7 +386,7 @@ var _ = Describe("base functionality", func() { Expect(err).NotTo(HaveOccurred()) Expect(res.Interfaces).To(Equal([]*current.Interface{ { - Name: CNI_IFNAME, + Name: cniName, Mac: origLink.Attrs().HardwareAddr.String(), Sandbox: targetNS.Path(), }, @@ -104,9 +395,17 @@ var _ = Describe("base functionality", func() { // assert that dummy0 is now in the target namespace err = targetNS.Do(func(ns.NetNS) error { defer GinkgoRecover() - link, err := netlink.LinkByName(CNI_IFNAME) + link, err := netlink.LinkByName(cniName) Expect(err).NotTo(HaveOccurred()) Expect(link.Attrs().HardwareAddr).To(Equal(origLink.Attrs().HardwareAddr)) + + //get the IP address of the interface in the target namespace + addrs, err := netlink.AddrList(link, netlink.FAMILY_V4) + Expect(err).NotTo(HaveOccurred()) + addr := addrs[0].IPNet.String() + //assert that IP address is what we set + Expect(addr).To(Equal(targetIP)) + return nil }) Expect(err).NotTo(HaveOccurred()) @@ -154,4 +453,255 @@ var _ = Describe("base functionality", func() { }) + It("Works with a valid 0.4.0 config without IPAM", func() { + var origLink netlink.Link + + // prepare ifname in original namespace + err := originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + err := netlink.LinkAdd(&netlink.Dummy{ + LinkAttrs: netlink.LinkAttrs{ + Name: ifname, + }, + }) + Expect(err).NotTo(HaveOccurred()) + origLink, err = netlink.LinkByName(ifname) + Expect(err).NotTo(HaveOccurred()) + err = netlink.LinkSetUp(origLink) + Expect(err).NotTo(HaveOccurred()) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + + // call CmdAdd + targetNS, err := testutils.NewNS() + Expect(err).NotTo(HaveOccurred()) + + cniName := "eth0" + conf := fmt.Sprintf(`{ + "cniVersion": "0.4.0", + "name": "cni-plugin-host-device-test", + "type": "host-device", + "device": %q + }`, ifname) + args := &skel.CmdArgs{ + ContainerID: "dummy", + Netns: targetNS.Path(), + IfName: cniName, + StdinData: []byte(conf), + } + var resI types.Result + err = originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + var err error + resI, _, err = testutils.CmdAddWithArgs(args, func() error { return cmdAdd(args) }) + return err + }) + Expect(err).NotTo(HaveOccurred()) + + // check that the result was sane + res, err := current.NewResultFromResult(resI) + Expect(err).NotTo(HaveOccurred()) + Expect(res.Interfaces).To(Equal([]*current.Interface{ + { + Name: cniName, + Mac: origLink.Attrs().HardwareAddr.String(), + Sandbox: targetNS.Path(), + }, + })) + + // assert that dummy0 is now in the target namespace + err = targetNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + link, err := netlink.LinkByName(cniName) + Expect(err).NotTo(HaveOccurred()) + Expect(link.Attrs().HardwareAddr).To(Equal(origLink.Attrs().HardwareAddr)) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + + // assert that dummy0 is now NOT in the original namespace anymore + err = originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + _, err := netlink.LinkByName(ifname) + Expect(err).To(HaveOccurred()) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + + // call CmdCheck + n := &Net{} + err = json.Unmarshal([]byte(conf), &n) + Expect(err).NotTo(HaveOccurred()) + + cniVersion := "0.4.0" + newConf, err := buildOneConfig("testConfig", cniVersion, n, res) + Expect(err).NotTo(HaveOccurred()) + + confString, err := json.Marshal(newConf) + Expect(err).NotTo(HaveOccurred()) + + args.StdinData = confString + + // CNI Check host-device in the target namespace + + err = originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + var err error + err = testutils.CmdCheckWithArgs(args, func() error { return cmdCheck(args) }) + return err + }) + Expect(err).NotTo(HaveOccurred()) + + // Check that deleting the device moves it back and restores the name + err = originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + err = testutils.CmdDelWithArgs(args, func() error { + return cmdDel(args) + }) + Expect(err).NotTo(HaveOccurred()) + + _, err := netlink.LinkByName(ifname) + Expect(err).NotTo(HaveOccurred()) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + + }) + + It("Works with a valid 0.4.0 config with IPAM", func() { + var origLink netlink.Link + + // prepare ifname in original namespace + err := originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + err := netlink.LinkAdd(&netlink.Dummy{ + LinkAttrs: netlink.LinkAttrs{ + Name: ifname, + }, + }) + Expect(err).NotTo(HaveOccurred()) + origLink, err = netlink.LinkByName(ifname) + Expect(err).NotTo(HaveOccurred()) + err = netlink.LinkSetUp(origLink) + Expect(err).NotTo(HaveOccurred()) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + + // call CmdAdd + targetNS, err := testutils.NewNS() + Expect(err).NotTo(HaveOccurred()) + targetIP := "10.10.0.1/24" + cniName := "eth0" + conf := fmt.Sprintf(`{ + "cniVersion": "0.4.0", + "name": "cni-plugin-host-device-test", + "type": "host-device", + "ipam": { + "type": "static", + "addresses": [ + { + "address":"`+targetIP+`", + "gateway": "10.10.0.254" + }] + }, + "device": %q + }`, ifname) + args := &skel.CmdArgs{ + ContainerID: "dummy", + Netns: targetNS.Path(), + IfName: cniName, + StdinData: []byte(conf), + } + var resI types.Result + err = originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + var err error + resI, _, err = testutils.CmdAddWithArgs(args, func() error { return cmdAdd(args) }) + return err + }) + Expect(err).NotTo(HaveOccurred()) + + // check that the result was sane + res, err := current.NewResultFromResult(resI) + Expect(err).NotTo(HaveOccurred()) + Expect(res.Interfaces).To(Equal([]*current.Interface{ + { + Name: cniName, + Mac: origLink.Attrs().HardwareAddr.String(), + Sandbox: targetNS.Path(), + }, + })) + + // assert that dummy0 is now in the target namespace + err = targetNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + link, err := netlink.LinkByName(cniName) + Expect(err).NotTo(HaveOccurred()) + Expect(link.Attrs().HardwareAddr).To(Equal(origLink.Attrs().HardwareAddr)) + + //get the IP address of the interface in the target namespace + addrs, err := netlink.AddrList(link, netlink.FAMILY_V4) + Expect(err).NotTo(HaveOccurred()) + addr := addrs[0].IPNet.String() + //assert that IP address is what we set + Expect(addr).To(Equal(targetIP)) + + return nil + }) + Expect(err).NotTo(HaveOccurred()) + + // assert that dummy0 is now NOT in the original namespace anymore + err = originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + _, err := netlink.LinkByName(ifname) + Expect(err).To(HaveOccurred()) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + + // call CmdCheck + n := &Net{} + err = json.Unmarshal([]byte(conf), &n) + Expect(err).NotTo(HaveOccurred()) + + n.IPAM, _, err = LoadIPAMConfig([]byte(conf), "") + Expect(err).NotTo(HaveOccurred()) + + cniVersion := "0.4.0" + newConf, err := buildOneConfig("testConfig", cniVersion, n, res) + Expect(err).NotTo(HaveOccurred()) + + confString, err := json.Marshal(newConf) + Expect(err).NotTo(HaveOccurred()) + + args.StdinData = confString + + // CNI Check host-device in the target namespace + + err = originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + var err error + err = testutils.CmdCheckWithArgs(args, func() error { return cmdCheck(args) }) + return err + }) + Expect(err).NotTo(HaveOccurred()) + + // Check that deleting the device moves it back and restores the name + err = originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + err = testutils.CmdDelWithArgs(args, func() error { + return cmdDel(args) + }) + Expect(err).NotTo(HaveOccurred()) + + _, err := netlink.LinkByName(ifname) + Expect(err).NotTo(HaveOccurred()) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + + }) + }) diff --git a/vendor/github.com/containernetworking/plugins/plugins/main/ipvlan/ipvlan.go b/vendor/github.com/containernetworking/plugins/plugins/main/ipvlan/ipvlan.go index 269fa8e8e..ba24b54e3 100644 --- a/vendor/github.com/containernetworking/plugins/plugins/main/ipvlan/ipvlan.go +++ b/vendor/github.com/containernetworking/plugins/plugins/main/ipvlan/ipvlan.go @@ -20,24 +20,21 @@ import ( "fmt" "runtime" + "github.com/vishvananda/netlink" + "github.com/containernetworking/cni/pkg/skel" "github.com/containernetworking/cni/pkg/types" "github.com/containernetworking/cni/pkg/types/current" "github.com/containernetworking/cni/pkg/version" + "github.com/containernetworking/plugins/pkg/ip" "github.com/containernetworking/plugins/pkg/ipam" "github.com/containernetworking/plugins/pkg/ns" - "github.com/vishvananda/netlink" + bv "github.com/containernetworking/plugins/pkg/utils/buildversion" ) type NetConf struct { types.NetConf - - // support chaining for master interface and IP decisions - // occurring prior to running ipvlan plugin - RawPrevResult *map[string]interface{} `json:"prevResult"` - PrevResult *current.Result `json:"-"` - Master string `json:"master"` Mode string `json:"mode"` MTU int `json:"mtu"` @@ -50,33 +47,35 @@ func init() { runtime.LockOSThread() } -func loadConf(bytes []byte) (*NetConf, string, error) { +func loadConf(bytes []byte, cmdCheck bool) (*NetConf, string, error) { n := &NetConf{} if err := json.Unmarshal(bytes, n); err != nil { return nil, "", fmt.Errorf("failed to load netconf: %v", err) } + + if cmdCheck { + return n, n.CNIVersion, nil + } + + var result *current.Result + var err error // Parse previous result - if n.RawPrevResult != nil { - resultBytes, err := json.Marshal(n.RawPrevResult) - if err != nil { - return nil, "", fmt.Errorf("could not serialize prevResult: %v", err) - } - res, err := version.NewResult(n.CNIVersion, resultBytes) - if err != nil { + if n.NetConf.RawPrevResult != nil { + if err = version.ParsePrevResult(&n.NetConf); err != nil { return nil, "", fmt.Errorf("could not parse prevResult: %v", err) } - n.RawPrevResult = nil - n.PrevResult, err = current.NewResultFromResult(res) + + result, err = current.NewResultFromResult(n.PrevResult) if err != nil { return nil, "", fmt.Errorf("could not convert result to current version: %v", err) } } if n.Master == "" { - if n.PrevResult == nil { + if result == nil { return nil, "", fmt.Errorf(`"master" field is required. It specifies the host interface name to virtualize`) } - if len(n.PrevResult.Interfaces) == 1 && n.PrevResult.Interfaces[0].Name != "" { - n.Master = n.PrevResult.Interfaces[0].Name + if len(result.Interfaces) == 1 && result.Interfaces[0].Name != "" { + n.Master = result.Interfaces[0].Name } else { return nil, "", fmt.Errorf("chained master failure. PrevResult lacks a single named interface") } @@ -97,6 +96,19 @@ func modeFromString(s string) (netlink.IPVlanMode, error) { } } +func modeToString(mode netlink.IPVlanMode) (string, error) { + switch mode { + case netlink.IPVLAN_MODE_L2: + return "l2", nil + case netlink.IPVLAN_MODE_L3: + return "l3", nil + case netlink.IPVLAN_MODE_L3S: + return "l3s", nil + default: + return "", fmt.Errorf("unknown ipvlan mode: %q", mode) + } +} + func createIpvlan(conf *NetConf, ifName string, netns ns.NetNS) (*current.Interface, error) { ipvlan := ¤t.Interface{} @@ -156,7 +168,7 @@ func createIpvlan(conf *NetConf, ifName string, netns ns.NetNS) (*current.Interf } func cmdAdd(args *skel.CmdArgs) error { - n, cniVersion, err := loadConf(args.StdinData) + n, cniVersion, err := loadConf(args.StdinData, false) if err != nil { return err } @@ -175,14 +187,30 @@ func cmdAdd(args *skel.CmdArgs) error { var result *current.Result // Configure iface from PrevResult if we have IPs and an IPAM // block has not been configured - if n.IPAM.Type == "" && n.PrevResult != nil && len(n.PrevResult.IPs) > 0 { - result = n.PrevResult - } else { + haveResult := false + if n.IPAM.Type == "" && n.PrevResult != nil { + result, err = current.NewResultFromResult(n.PrevResult) + if err != nil { + return err + } + if len(result.IPs) > 0 { + haveResult = true + } + } + if !haveResult { // run the IPAM plugin and get back the config to apply r, err := ipam.ExecAdd(n.IPAM.Type, args.StdinData) if err != nil { return err } + + // Invoke ipam del if err to avoid ip leak + defer func() { + if err != nil { + ipam.ExecDel(n.IPAM.Type, args.StdinData) + } + }() + // Convert whatever the IPAM result was into the current Result type result, err = current.NewResultFromResult(r) if err != nil { @@ -213,7 +241,7 @@ func cmdAdd(args *skel.CmdArgs) error { } func cmdDel(args *skel.CmdArgs) error { - n, _, err := loadConf(args.StdinData) + n, _, err := loadConf(args.StdinData, false) if err != nil { return err } @@ -245,5 +273,130 @@ func cmdDel(args *skel.CmdArgs) error { } func main() { - skel.PluginMain(cmdAdd, cmdDel, version.All) + skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("ipvlan")) +} + +func cmdCheck(args *skel.CmdArgs) error { + + n, _, err := loadConf(args.StdinData, true) + if err != nil { + return err + } + netns, err := ns.GetNS(args.Netns) + if err != nil { + return fmt.Errorf("failed to open netns %q: %v", args.Netns, err) + } + defer netns.Close() + + if n.IPAM.Type != "" { + // run the IPAM plugin and get back the config to apply + err = ipam.ExecCheck(n.IPAM.Type, args.StdinData) + if err != nil { + return err + } + } + + // Parse previous result. + if n.NetConf.RawPrevResult == nil { + return fmt.Errorf("Required prevResult missing") + } + + if err := version.ParsePrevResult(&n.NetConf); err != nil { + return err + } + + result, err := current.NewResultFromResult(n.PrevResult) + if err != nil { + return err + } + + var contMap current.Interface + // Find interfaces for names whe know, ipvlan inside container + for _, intf := range result.Interfaces { + if args.IfName == intf.Name { + if args.Netns == intf.Sandbox { + contMap = *intf + continue + } + } + } + + // The namespace must be the same as what was configured + if args.Netns != contMap.Sandbox { + return fmt.Errorf("Sandbox in prevResult %s doesn't match configured netns: %s", + contMap.Sandbox, args.Netns) + } + + m, err := netlink.LinkByName(n.Master) + if err != nil { + return fmt.Errorf("failed to lookup master %q: %v", n.Master, err) + } + + // Check prevResults for ips, routes and dns against values found in the container + if err := netns.Do(func(_ ns.NetNS) error { + + // Check interface against values found in the container + err := validateCniContainerInterface(contMap, m.Attrs().Index, n.Mode) + if err != nil { + return err + } + + err = ip.ValidateExpectedInterfaceIPs(args.IfName, result.IPs) + if err != nil { + return err + } + + err = ip.ValidateExpectedRoute(result.Routes) + if err != nil { + return err + } + return nil + }); err != nil { + return err + } + + return nil +} + +func validateCniContainerInterface(intf current.Interface, masterIndex int, modeExpected string) error { + + var link netlink.Link + var err error + + if intf.Name == "" { + return fmt.Errorf("Container interface name missing in prevResult: %v", intf.Name) + } + link, err = netlink.LinkByName(intf.Name) + if err != nil { + return fmt.Errorf("Container Interface name in prevResult: %s not found", intf.Name) + } + if intf.Sandbox == "" { + return fmt.Errorf("Error: Container interface %s should not be in host namespace", link.Attrs().Name) + } + + ipv, isIPVlan := link.(*netlink.IPVlan) + if !isIPVlan { + return fmt.Errorf("Error: Container interface %s not of type ipvlan", link.Attrs().Name) + } + + mode, err := modeFromString(modeExpected) + if ipv.Mode != mode { + currString, err := modeToString(ipv.Mode) + if err != nil { + return err + } + confString, err := modeToString(mode) + if err != nil { + return err + } + return fmt.Errorf("Container IPVlan mode %s does not match expected value: %s", currString, confString) + } + + if intf.Mac != "" { + if intf.Mac != link.Attrs().HardwareAddr.String() { + return fmt.Errorf("Interface %s Mac %s doesn't match container Mac: %s", intf.Name, intf.Mac, link.Attrs().HardwareAddr) + } + } + + return nil } diff --git a/vendor/github.com/containernetworking/plugins/plugins/main/ipvlan/ipvlan_test.go b/vendor/github.com/containernetworking/plugins/plugins/main/ipvlan/ipvlan_test.go index 9fb110ade..64503d2ef 100644 --- a/vendor/github.com/containernetworking/plugins/plugins/main/ipvlan/ipvlan_test.go +++ b/vendor/github.com/containernetworking/plugins/plugins/main/ipvlan/ipvlan_test.go @@ -15,6 +15,7 @@ package main import ( + "encoding/json" "fmt" "net" "syscall" @@ -27,12 +28,71 @@ import ( "github.com/vishvananda/netlink" + "github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) const MASTER_NAME = "eth0" +type Net struct { + Name string `json:"name"` + CNIVersion string `json:"cniVersion"` + Type string `json:"type,omitempty"` + Master string `json:"master"` + Mode string `json:"mode"` + IPAM *allocator.IPAMConfig `json:"ipam"` + DNS types.DNS `json:"dns"` + RawPrevResult map[string]interface{} `json:"prevResult,omitempty"` + PrevResult current.Result `json:"-"` +} + +func buildOneConfig(netName string, cniVersion string, master string, orig *Net, prevResult types.Result) (*Net, error) { + var err error + + inject := map[string]interface{}{ + "name": netName, + "cniVersion": cniVersion, + } + // Add previous plugin result + if prevResult != nil { + inject["prevResult"] = prevResult + } + if orig.IPAM == nil { + inject["master"] = master + } + + // Ensure every config uses the same name and version + config := make(map[string]interface{}) + + confBytes, err := json.Marshal(orig) + if err != nil { + return nil, err + } + + err = json.Unmarshal(confBytes, &config) + if err != nil { + return nil, fmt.Errorf("unmarshal existing network bytes: %s", err) + } + + for key, value := range inject { + config[key] = value + } + + newBytes, err := json.Marshal(config) + if err != nil { + return nil, err + } + + conf := &Net{} + if err := json.Unmarshal(newBytes, &conf); err != nil { + return nil, fmt.Errorf("error parsing configuration: %s", err) + } + + return conf, nil + +} + func ipvlanAddDelTest(conf, IFNAME string, originalNS ns.NetNS) { targetNs, err := testutils.NewNS() Expect(err).NotTo(HaveOccurred()) @@ -106,6 +166,109 @@ func ipvlanAddDelTest(conf, IFNAME string, originalNS ns.NetNS) { Expect(err).NotTo(HaveOccurred()) } +func ipvlanAddCheckDelTest(conf string, netName string, IFNAME string, originalNS ns.NetNS) { + targetNs, err := testutils.NewNS() + Expect(err).NotTo(HaveOccurred()) + defer targetNs.Close() + + args := &skel.CmdArgs{ + ContainerID: "dummy", + Netns: targetNs.Path(), + IfName: IFNAME, + StdinData: []byte(conf), + } + + var result *current.Result + err = originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + r, _, err := testutils.CmdAddWithArgs(args, func() error { + return cmdAdd(args) + }) + Expect(err).NotTo(HaveOccurred()) + + result, err = current.GetResult(r) + Expect(err).NotTo(HaveOccurred()) + + Expect(len(result.Interfaces)).To(Equal(1)) + Expect(result.Interfaces[0].Name).To(Equal(IFNAME)) + Expect(len(result.IPs)).To(Equal(1)) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + + // Make sure ipvlan link exists in the target namespace + err = targetNs.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + link, err := netlink.LinkByName(IFNAME) + Expect(err).NotTo(HaveOccurred()) + Expect(link.Attrs().Name).To(Equal(IFNAME)) + + hwaddr, err := net.ParseMAC(result.Interfaces[0].Mac) + Expect(err).NotTo(HaveOccurred()) + Expect(link.Attrs().HardwareAddr).To(Equal(hwaddr)) + + addrs, err := netlink.AddrList(link, syscall.AF_INET) + Expect(err).NotTo(HaveOccurred()) + Expect(len(addrs)).To(Equal(1)) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + + n := &Net{} + err = json.Unmarshal([]byte(conf), &n) + Expect(err).NotTo(HaveOccurred()) + + if n.IPAM != nil { + n.IPAM, _, err = allocator.LoadIPAMConfig([]byte(conf), "") + Expect(err).NotTo(HaveOccurred()) + } + + cniVersion := "0.4.0" + newConf, err := buildOneConfig(netName, cniVersion, MASTER_NAME, n, result) + Expect(err).NotTo(HaveOccurred()) + + confString, err := json.Marshal(newConf) + Expect(err).NotTo(HaveOccurred()) + + args.StdinData = confString + + // CNI Check on macvlan in the target namespace + err = originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + err := testutils.CmdCheckWithArgs(args, func() error { + return cmdCheck(args) + }) + Expect(err).NotTo(HaveOccurred()) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + + err = originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + err = testutils.CmdDelWithArgs(args, func() error { + return cmdDel(args) + }) + Expect(err).NotTo(HaveOccurred()) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + + // Make sure ipvlan link has been deleted + err = targetNs.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + link, err := netlink.LinkByName(IFNAME) + Expect(err).To(HaveOccurred()) + Expect(link).To(BeNil()) + return nil + }) + Expect(err).NotTo(HaveOccurred()) +} + var _ = Describe("ipvlan Operations", func() { var originalNS ns.NetNS @@ -256,4 +419,49 @@ var _ = Describe("ipvlan Operations", func() { }) Expect(err).NotTo(HaveOccurred()) }) + + It("configures and deconfigures a cniVersion 0.4.0 iplvan link with ADD/CHECK/DEL", func() { + const IFNAME = "ipvl0" + + conf := fmt.Sprintf(`{ + "cniVersion": "0.4.0", + "name": "ipvlanTest1", + "type": "ipvlan", + "master": "%s", + "ipam": { + "type": "host-local", + "subnet": "10.1.2.0/24" + } +}`, MASTER_NAME) + + ipvlanAddCheckDelTest(conf, "ipvlanTest1", IFNAME, originalNS) + }) + + It("configures and deconfigures a cniVersion 0.4.0 iplvan link with ADD/CHECK/DEL when chained", func() { + const IFNAME = "ipvl0" + + conf := fmt.Sprintf(`{ + "cniVersion": "0.4.0", + "name": "ipvlanTest2", + "type": "ipvlan", + "prevResult": { + "interfaces": [ + { + "name": "%s" + } + ], + "ips": [ + { + "version": "4", + "address": "10.1.2.2/24", + "gateway": "10.1.2.1", + "interface": 0 + } + ], + "routes": [] + } + }`, MASTER_NAME) + + ipvlanAddCheckDelTest(conf, "ipvlanTest2", IFNAME, originalNS) + }) }) diff --git a/vendor/github.com/containernetworking/plugins/plugins/main/loopback/loopback.go b/vendor/github.com/containernetworking/plugins/plugins/main/loopback/loopback.go index 08c84a5d7..db5e9964c 100644 --- a/vendor/github.com/containernetworking/plugins/plugins/main/loopback/loopback.go +++ b/vendor/github.com/containernetworking/plugins/plugins/main/loopback/loopback.go @@ -15,11 +15,14 @@ package main import ( + "github.com/vishvananda/netlink" + "github.com/containernetworking/cni/pkg/skel" "github.com/containernetworking/cni/pkg/types/current" "github.com/containernetworking/cni/pkg/version" + "github.com/containernetworking/plugins/pkg/ns" - "github.com/vishvananda/netlink" + bv "github.com/containernetworking/plugins/pkg/utils/buildversion" ) func cmdAdd(args *skel.CmdArgs) error { @@ -46,6 +49,9 @@ func cmdAdd(args *skel.CmdArgs) error { } func cmdDel(args *skel.CmdArgs) error { + if args.Netns == "" { + return nil + } args.IfName = "lo" // ignore config, this only works for loopback err := ns.WithNetNSPath(args.Netns, func(ns.NetNS) error { link, err := netlink.LinkByName(args.IfName) @@ -68,5 +74,10 @@ func cmdDel(args *skel.CmdArgs) error { } func main() { - skel.PluginMain(cmdAdd, cmdDel, version.All) + skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("loopback")) +} + +func cmdCheck(args *skel.CmdArgs) error { + // TODO: implement + return nil } diff --git a/vendor/github.com/containernetworking/plugins/plugins/main/loopback/loopback_test.go b/vendor/github.com/containernetworking/plugins/plugins/main/loopback/loopback_test.go index dd5d5c026..136938332 100644 --- a/vendor/github.com/containernetworking/plugins/plugins/main/loopback/loopback_test.go +++ b/vendor/github.com/containernetworking/plugins/plugins/main/loopback/loopback_test.go @@ -58,6 +58,8 @@ var _ = Describe("Loopback", func() { Context("when given a network namespace", func() { It("sets the lo device to UP", func() { + + Skip("TODO: add network name") command.Env = append(environ, fmt.Sprintf("CNI_COMMAND=%s", "ADD")) session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter) @@ -78,6 +80,8 @@ var _ = Describe("Loopback", func() { }) It("sets the lo device to DOWN", func() { + + Skip("TODO: add network name") command.Env = append(environ, fmt.Sprintf("CNI_COMMAND=%s", "DEL")) session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter) diff --git a/vendor/github.com/containernetworking/plugins/plugins/main/macvlan/README.md b/vendor/github.com/containernetworking/plugins/plugins/main/macvlan/README.md index 32448c61a..7e8239b13 100644 --- a/vendor/github.com/containernetworking/plugins/plugins/main/macvlan/README.md +++ b/vendor/github.com/containernetworking/plugins/plugins/main/macvlan/README.md @@ -23,10 +23,10 @@ Since each macvlan interface has its own MAC address, it makes it easy to use wi * `name` (string, required): the name of the network * `type` (string, required): "macvlan" -* `master` (string, required): name of the host interface to enslave -* `mode` (string, optional): one of "bridge", "private", "vepa", "passthrough". Defaults to "bridge". +* `master` (string, optional): name of the host interface to enslave. Defaults to default route interace. +* `mode` (string, optional): one of "bridge", "private", "vepa", "passthru". Defaults to "bridge". * `mtu` (integer, optional): explicitly set MTU to the specified value. Defaults to the value chosen by the kernel. -* `ipam` (dictionary, required): IPAM configuration to be used for this network. +* `ipam` (dictionary, required): IPAM configuration to be used for this network. For interface only without ip address, create empty dictionary. ## Notes diff --git a/vendor/github.com/containernetworking/plugins/plugins/main/macvlan/macvlan.go b/vendor/github.com/containernetworking/plugins/plugins/main/macvlan/macvlan.go index 618fe96da..7ffba21dc 100644 --- a/vendor/github.com/containernetworking/plugins/plugins/main/macvlan/macvlan.go +++ b/vendor/github.com/containernetworking/plugins/plugins/main/macvlan/macvlan.go @@ -21,16 +21,19 @@ import ( "net" "runtime" + "github.com/j-keck/arping" + "github.com/vishvananda/netlink" + "github.com/containernetworking/cni/pkg/skel" "github.com/containernetworking/cni/pkg/types" "github.com/containernetworking/cni/pkg/types/current" "github.com/containernetworking/cni/pkg/version" + "github.com/containernetworking/plugins/pkg/ip" "github.com/containernetworking/plugins/pkg/ipam" "github.com/containernetworking/plugins/pkg/ns" + bv "github.com/containernetworking/plugins/pkg/utils/buildversion" "github.com/containernetworking/plugins/pkg/utils/sysctl" - "github.com/j-keck/arping" - "github.com/vishvananda/netlink" ) const ( @@ -51,13 +54,36 @@ func init() { runtime.LockOSThread() } +func getDefaultRouteInterfaceName() (string, error) { + routeToDstIP, err := netlink.RouteList(nil, netlink.FAMILY_ALL) + if err != nil { + return "", err + } + + for _, v := range routeToDstIP { + if v.Dst == nil { + l, err := netlink.LinkByIndex(v.LinkIndex) + if err != nil { + return "", err + } + return l.Attrs().Name, nil + } + } + + return "", fmt.Errorf("no default route interface found") +} + func loadConf(bytes []byte) (*NetConf, string, error) { n := &NetConf{} if err := json.Unmarshal(bytes, n); err != nil { return nil, "", fmt.Errorf("failed to load netconf: %v", err) } if n.Master == "" { - return nil, "", fmt.Errorf(`"master" field is required. It specifies the host interface name to virtualize`) + defaultRouteInterface, err := getDefaultRouteInterfaceName() + if err != nil { + return nil, "", err + } + n.Master = defaultRouteInterface } return n, n.CNIVersion, nil } @@ -77,6 +103,21 @@ func modeFromString(s string) (netlink.MacvlanMode, error) { } } +func modeToString(mode netlink.MacvlanMode) (string, error) { + switch mode { + case netlink.MACVLAN_MODE_BRIDGE: + return "bridge", nil + case netlink.MACVLAN_MODE_PRIVATE: + return "private", nil + case netlink.MACVLAN_MODE_VEPA: + return "vepa", nil + case netlink.MACVLAN_MODE_PASSTHRU: + return "passthru", nil + default: + return "", fmt.Errorf("unknown macvlan mode: %q", mode) + } +} + func createMacvlan(conf *NetConf, ifName string, netns ns.NetNS) (*current.Interface, error) { macvlan := ¤t.Interface{} @@ -150,6 +191,8 @@ func cmdAdd(args *skel.CmdArgs) error { return err } + isLayer3 := n.IPAM.Type != "" + netns, err := ns.GetNS(args.Netns) if err != nil { return fmt.Errorf("failed to open netns %q: %v", netns, err) @@ -170,54 +213,78 @@ func cmdAdd(args *skel.CmdArgs) error { } }() - // run the IPAM plugin and get back the config to apply - r, err := ipam.ExecAdd(n.IPAM.Type, args.StdinData) - if err != nil { - return err - } + // Assume L2 interface only + result := ¤t.Result{CNIVersion: cniVersion, Interfaces: []*current.Interface{macvlanInterface}} - // Invoke ipam del if err to avoid ip leak - defer func() { + if isLayer3 { + // run the IPAM plugin and get back the config to apply + r, err := ipam.ExecAdd(n.IPAM.Type, args.StdinData) if err != nil { - ipam.ExecDel(n.IPAM.Type, args.StdinData) + return err } - }() - // Convert whatever the IPAM result was into the current Result type - result, err := current.NewResultFromResult(r) - if err != nil { - return err - } + // Invoke ipam del if err to avoid ip leak + defer func() { + if err != nil { + ipam.ExecDel(n.IPAM.Type, args.StdinData) + } + }() - if len(result.IPs) == 0 { - return errors.New("IPAM plugin returned missing IP config") - } - result.Interfaces = []*current.Interface{macvlanInterface} + // Convert whatever the IPAM result was into the current Result type + ipamResult, err := current.NewResultFromResult(r) + if err != nil { + return err + } - for _, ipc := range result.IPs { - // All addresses apply to the container macvlan interface - ipc.Interface = current.Int(0) - } + if len(ipamResult.IPs) == 0 { + return errors.New("IPAM plugin returned missing IP config") + } - err = netns.Do(func(_ ns.NetNS) error { - if err := ipam.ConfigureIface(args.IfName, result); err != nil { - return err + result.IPs = ipamResult.IPs + result.Routes = ipamResult.Routes + + for _, ipc := range result.IPs { + // All addresses apply to the container macvlan interface + ipc.Interface = current.Int(0) } - contVeth, err := net.InterfaceByName(args.IfName) + err = netns.Do(func(_ ns.NetNS) error { + if err := ipam.ConfigureIface(args.IfName, result); err != nil { + return err + } + + contVeth, err := net.InterfaceByName(args.IfName) + if err != nil { + return fmt.Errorf("failed to look up %q: %v", args.IfName, err) + } + + for _, ipc := range result.IPs { + if ipc.Version == "4" { + _ = arping.GratuitousArpOverIface(ipc.Address.IP, *contVeth) + } + } + return nil + }) if err != nil { - return fmt.Errorf("failed to look up %q: %v", args.IfName, err) + return err } + } else { + // For L2 just change interface status to up + err = netns.Do(func(_ ns.NetNS) error { + macvlanInterfaceLink, err := netlink.LinkByName(args.IfName) + if err != nil { + return fmt.Errorf("failed to find interface name %q: %v", macvlanInterface.Name, err) + } - for _, ipc := range result.IPs { - if ipc.Version == "4" { - _ = arping.GratuitousArpOverIface(ipc.Address.IP, *contVeth) + if err := netlink.LinkSetUp(macvlanInterfaceLink); err != nil { + return fmt.Errorf("failed to set %q UP: %v", args.IfName, err) } + + return nil + }) + if err != nil { + return err } - return nil - }) - if err != nil { - return err } result.DNS = n.DNS @@ -231,9 +298,13 @@ func cmdDel(args *skel.CmdArgs) error { return err } - err = ipam.ExecDel(n.IPAM.Type, args.StdinData) - if err != nil { - return err + isLayer3 := n.IPAM.Type != "" + + if isLayer3 { + err = ipam.ExecDel(n.IPAM.Type, args.StdinData) + if err != nil { + return err + } } if args.Netns == "" { @@ -255,5 +326,132 @@ func cmdDel(args *skel.CmdArgs) error { } func main() { - skel.PluginMain(cmdAdd, cmdDel, version.All) + skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("macvlan")) +} + +func cmdCheck(args *skel.CmdArgs) error { + + n, _, err := loadConf(args.StdinData) + if err != nil { + return err + } + isLayer3 := n.IPAM.Type != "" + + netns, err := ns.GetNS(args.Netns) + if err != nil { + return fmt.Errorf("failed to open netns %q: %v", args.Netns, err) + } + defer netns.Close() + + if isLayer3 { + // run the IPAM plugin and get back the config to apply + err = ipam.ExecCheck(n.IPAM.Type, args.StdinData) + if err != nil { + return err + } + } + + // Parse previous result. + if n.NetConf.RawPrevResult == nil { + return fmt.Errorf("Required prevResult missing") + } + + if err := version.ParsePrevResult(&n.NetConf); err != nil { + return err + } + + result, err := current.NewResultFromResult(n.PrevResult) + if err != nil { + return err + } + + var contMap current.Interface + // Find interfaces for names whe know, macvlan device name inside container + for _, intf := range result.Interfaces { + if args.IfName == intf.Name { + if args.Netns == intf.Sandbox { + contMap = *intf + continue + } + } + } + + // The namespace must be the same as what was configured + if args.Netns != contMap.Sandbox { + return fmt.Errorf("Sandbox in prevResult %s doesn't match configured netns: %s", + contMap.Sandbox, args.Netns) + } + + m, err := netlink.LinkByName(n.Master) + if err != nil { + return fmt.Errorf("failed to lookup master %q: %v", n.Master, err) + } + + // Check prevResults for ips, routes and dns against values found in the container + if err := netns.Do(func(_ ns.NetNS) error { + + // Check interface against values found in the container + err := validateCniContainerInterface(contMap, m.Attrs().Index, n.Mode) + if err != nil { + return err + } + + err = ip.ValidateExpectedInterfaceIPs(args.IfName, result.IPs) + if err != nil { + return err + } + + err = ip.ValidateExpectedRoute(result.Routes) + if err != nil { + return err + } + return nil + }); err != nil { + return err + } + + return nil +} + +func validateCniContainerInterface(intf current.Interface, parentIndex int, modeExpected string) error { + + var link netlink.Link + var err error + + if intf.Name == "" { + return fmt.Errorf("Container interface name missing in prevResult: %v", intf.Name) + } + link, err = netlink.LinkByName(intf.Name) + if err != nil { + return fmt.Errorf("Container Interface name in prevResult: %s not found", intf.Name) + } + if intf.Sandbox == "" { + return fmt.Errorf("Error: Container interface %s should not be in host namespace", link.Attrs().Name) + } + + macv, isMacvlan := link.(*netlink.Macvlan) + if !isMacvlan { + return fmt.Errorf("Error: Container interface %s not of type macvlan", link.Attrs().Name) + } + + mode, err := modeFromString(modeExpected) + if macv.Mode != mode { + currString, err := modeToString(macv.Mode) + if err != nil { + return err + } + confString, err := modeToString(mode) + if err != nil { + return err + } + return fmt.Errorf("Container macvlan mode %s does not match expected value: %s", currString, confString) + } + + if intf.Mac != "" { + if intf.Mac != link.Attrs().HardwareAddr.String() { + return fmt.Errorf("Interface %s Mac %s doesn't match container Mac: %s", intf.Name, intf.Mac, link.Attrs().HardwareAddr) + } + } + + return nil } diff --git a/vendor/github.com/containernetworking/plugins/plugins/main/macvlan/macvlan_test.go b/vendor/github.com/containernetworking/plugins/plugins/main/macvlan/macvlan_test.go index 0f7fef9f2..b7740517e 100644 --- a/vendor/github.com/containernetworking/plugins/plugins/main/macvlan/macvlan_test.go +++ b/vendor/github.com/containernetworking/plugins/plugins/main/macvlan/macvlan_test.go @@ -15,6 +15,7 @@ package main import ( + "encoding/json" "fmt" "net" "syscall" @@ -27,12 +28,73 @@ import ( "github.com/vishvananda/netlink" + "github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) const MASTER_NAME = "eth0" +type Net struct { + Name string `json:"name"` + CNIVersion string `json:"cniVersion"` + Type string `json:"type,omitempty"` + Master string `json:"master"` + Mode string `json:"mode"` + IPAM *allocator.IPAMConfig `json:"ipam"` + //RuntimeConfig struct { // The capability arg + // IPRanges []RangeSet `json:"ipRanges,omitempty"` + //} `json:"runtimeConfig,omitempty"` + //Args *struct { + // A *IPAMArgs `json:"cni"` + DNS types.DNS `json:"dns"` + RawPrevResult map[string]interface{} `json:"prevResult,omitempty"` + PrevResult current.Result `json:"-"` +} + +func buildOneConfig(netName string, cniVersion string, orig *Net, prevResult types.Result) (*Net, error) { + var err error + + inject := map[string]interface{}{ + "name": netName, + "cniVersion": cniVersion, + } + // Add previous plugin result + if prevResult != nil { + inject["prevResult"] = prevResult + } + + // Ensure every config uses the same name and version + config := make(map[string]interface{}) + + confBytes, err := json.Marshal(orig) + if err != nil { + return nil, err + } + + err = json.Unmarshal(confBytes, &config) + if err != nil { + return nil, fmt.Errorf("unmarshal existing network bytes: %s", err) + } + + for key, value := range inject { + config[key] = value + } + + newBytes, err := json.Marshal(config) + if err != nil { + return nil, err + } + + conf := &Net{} + if err := json.Unmarshal(newBytes, &conf); err != nil { + return nil, fmt.Errorf("error parsing configuration: %s", err) + } + + return conf, nil + +} + var _ = Describe("macvlan Operations", func() { var originalNS ns.NetNS @@ -223,4 +285,312 @@ var _ = Describe("macvlan Operations", func() { Expect(err).NotTo(HaveOccurred()) }) + + It("configures and deconfigures a l2 macvlan link with ADD/DEL", func() { + const IFNAME = "macvl0" + + conf := fmt.Sprintf(`{ + "cniVersion": "0.3.1", + "name": "mynet", + "type": "macvlan", + "master": "%s", + "ipam": {} +}`, MASTER_NAME) + + targetNs, err := testutils.NewNS() + Expect(err).NotTo(HaveOccurred()) + defer targetNs.Close() + + args := &skel.CmdArgs{ + ContainerID: "dummy", + Netns: targetNs.Path(), + IfName: IFNAME, + StdinData: []byte(conf), + } + + var result *current.Result + err = originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + r, _, err := testutils.CmdAddWithArgs(args, func() error { + return cmdAdd(args) + }) + Expect(err).NotTo(HaveOccurred()) + + result, err = current.GetResult(r) + Expect(err).NotTo(HaveOccurred()) + + Expect(len(result.Interfaces)).To(Equal(1)) + Expect(result.Interfaces[0].Name).To(Equal(IFNAME)) + Expect(len(result.IPs)).To(Equal(0)) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + + // Make sure macvlan link exists in the target namespace + err = targetNs.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + link, err := netlink.LinkByName(IFNAME) + Expect(err).NotTo(HaveOccurred()) + Expect(link.Attrs().Name).To(Equal(IFNAME)) + + hwaddr, err := net.ParseMAC(result.Interfaces[0].Mac) + Expect(err).NotTo(HaveOccurred()) + Expect(link.Attrs().HardwareAddr).To(Equal(hwaddr)) + + addrs, err := netlink.AddrList(link, syscall.AF_INET) + Expect(err).NotTo(HaveOccurred()) + Expect(len(addrs)).To(Equal(0)) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + + err = originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + err := testutils.CmdDelWithArgs(args, func() error { + return cmdDel(args) + }) + Expect(err).NotTo(HaveOccurred()) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + + // Make sure macvlan link has been deleted + err = targetNs.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + link, err := netlink.LinkByName(IFNAME) + Expect(err).To(HaveOccurred()) + Expect(link).To(BeNil()) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + }) + + It("configures and deconfigures a cniVersion 0.4.0 macvlan link with ADD/DEL", func() { + const IFNAME = "macvl0" + + conf := fmt.Sprintf(`{ + "cniVersion": "0.4.0", + "name": "macvlanTestv4", + "type": "macvlan", + "master": "%s", + "ipam": { + "type": "host-local", + "ranges": [[ {"subnet": "10.1.2.0/24", "gateway": "10.1.2.1"} ]] + } +}`, MASTER_NAME) + + targetNs, err := testutils.NewNS() + Expect(err).NotTo(HaveOccurred()) + defer targetNs.Close() + + args := &skel.CmdArgs{ + ContainerID: "dummy", + Netns: targetNs.Path(), + IfName: IFNAME, + StdinData: []byte(conf), + } + + var result *current.Result + err = originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + r, _, err := testutils.CmdAddWithArgs(args, func() error { + return cmdAdd(args) + }) + Expect(err).NotTo(HaveOccurred()) + + result, err = current.GetResult(r) + Expect(err).NotTo(HaveOccurred()) + + Expect(len(result.Interfaces)).To(Equal(1)) + Expect(result.Interfaces[0].Name).To(Equal(IFNAME)) + Expect(len(result.IPs)).To(Equal(1)) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + + // Make sure macvlan link exists in the target namespace + err = targetNs.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + link, err := netlink.LinkByName(IFNAME) + Expect(err).NotTo(HaveOccurred()) + Expect(link.Attrs().Name).To(Equal(IFNAME)) + + hwaddr, err := net.ParseMAC(result.Interfaces[0].Mac) + Expect(err).NotTo(HaveOccurred()) + Expect(link.Attrs().HardwareAddr).To(Equal(hwaddr)) + + addrs, err := netlink.AddrList(link, syscall.AF_INET) + Expect(err).NotTo(HaveOccurred()) + Expect(len(addrs)).To(Equal(1)) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + + n := &Net{} + err = json.Unmarshal([]byte(conf), &n) + Expect(err).NotTo(HaveOccurred()) + + n.IPAM, _, err = allocator.LoadIPAMConfig([]byte(conf), "") + Expect(err).NotTo(HaveOccurred()) + + cniVersion := "0.4.0" + newConf, err := buildOneConfig("macvlanTestv4", cniVersion, n, result) + Expect(err).NotTo(HaveOccurred()) + + confString, err := json.Marshal(newConf) + Expect(err).NotTo(HaveOccurred()) + + args.StdinData = confString + + // CNI Check on macvlan in the target namespace + err = originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + err := testutils.CmdCheckWithArgs(args, func() error { + return cmdCheck(args) + }) + Expect(err).NotTo(HaveOccurred()) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + + err = originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + err := testutils.CmdDelWithArgs(args, func() error { + return cmdDel(args) + }) + Expect(err).NotTo(HaveOccurred()) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + + // Make sure macvlan link has been deleted + err = targetNs.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + link, err := netlink.LinkByName(IFNAME) + Expect(err).To(HaveOccurred()) + Expect(link).To(BeNil()) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + }) + + It("configures and deconfigures a macvlan link with ADD/DEL, without master config", func() { + const IFNAME = "macvl0" + + conf := `{ + "cniVersion": "0.3.1", + "name": "mynet", + "type": "macvlan", + "ipam": { + "type": "host-local", + "subnet": "10.1.2.0/24" + } +}` + + targetNs, err := testutils.NewNS() + Expect(err).NotTo(HaveOccurred()) + defer targetNs.Close() + + args := &skel.CmdArgs{ + ContainerID: "dummy", + Netns: targetNs.Path(), + IfName: IFNAME, + StdinData: []byte(conf), + } + + // Make MASTER_NAME as default route interface + err = originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + link, err := netlink.LinkByName(MASTER_NAME) + Expect(err).NotTo(HaveOccurred()) + err = netlink.LinkSetUp(link) + Expect(err).NotTo(HaveOccurred()) + + var address = &net.IPNet{IP: net.IPv4(192, 0, 0, 1), Mask: net.CIDRMask(24, 32)} + var addr = &netlink.Addr{IPNet: address} + err = netlink.AddrAdd(link, addr) + Expect(err).NotTo(HaveOccurred()) + + // add default gateway into MASTER + dst := &net.IPNet{ + IP: net.IPv4(0, 0, 0, 0), + Mask: net.CIDRMask(0, 0), + } + ip := net.IPv4(192, 0, 0, 254) + route := netlink.Route{LinkIndex: link.Attrs().Index, Dst: dst, Gw: ip} + err = netlink.RouteAdd(&route) + Expect(err).NotTo(HaveOccurred()) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + + var result *current.Result + err = originalNS.Do(func(ns.NetNS) error { + r, _, err := testutils.CmdAddWithArgs(args, func() error { + return cmdAdd(args) + }) + Expect(err).NotTo(HaveOccurred()) + + result, err = current.GetResult(r) + Expect(err).NotTo(HaveOccurred()) + + Expect(len(result.Interfaces)).To(Equal(1)) + Expect(result.Interfaces[0].Name).To(Equal(IFNAME)) + Expect(len(result.IPs)).To(Equal(1)) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + + // Make sure macvlan link exists in the target namespace + err = targetNs.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + link, err := netlink.LinkByName(IFNAME) + Expect(err).NotTo(HaveOccurred()) + Expect(link.Attrs().Name).To(Equal(IFNAME)) + + hwaddr, err := net.ParseMAC(result.Interfaces[0].Mac) + Expect(err).NotTo(HaveOccurred()) + Expect(link.Attrs().HardwareAddr).To(Equal(hwaddr)) + + addrs, err := netlink.AddrList(link, syscall.AF_INET) + Expect(err).NotTo(HaveOccurred()) + Expect(len(addrs)).To(Equal(1)) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + + err = originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + err := testutils.CmdDelWithArgs(args, func() error { + return cmdDel(args) + }) + Expect(err).NotTo(HaveOccurred()) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + + // Make sure macvlan link has been deleted + err = targetNs.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + link, err := netlink.LinkByName(IFNAME) + Expect(err).To(HaveOccurred()) + Expect(link).To(BeNil()) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + }) + }) diff --git a/vendor/github.com/containernetworking/plugins/plugins/main/ptp/README.md b/vendor/github.com/containernetworking/plugins/plugins/main/ptp/README.md index 1467fb844..190476e3c 100644 --- a/vendor/github.com/containernetworking/plugins/plugins/main/ptp/README.md +++ b/vendor/github.com/containernetworking/plugins/plugins/main/ptp/README.md @@ -26,7 +26,7 @@ The traffic of the container interface will be routed through the interface of t * `name` (string, required): the name of the network * `type` (string, required): "ptp" -* `ipMasq` (boolean, optional): set up IP Masquerade on the host for traffic originating from this network and destined outside of it. Defaults to false. +* `ipMasq` (boolean, optional): set up IP Masquerade on the host for traffic originating from ip of this network and destined outside of this network. Defaults to false. * `mtu` (integer, optional): explicitly set MTU to the specified value. Defaults to value chosen by the kernel. * `ipam` (dictionary, required): IPAM configuration to be used for this network. * `dns` (dictionary, optional): DNS information to return as described in the [Result](https://github.com/containernetworking/cni/blob/master/SPEC.md#result). diff --git a/vendor/github.com/containernetworking/plugins/plugins/main/ptp/ptp.go b/vendor/github.com/containernetworking/plugins/plugins/main/ptp/ptp.go index da07b1b53..a748d7f6f 100644 --- a/vendor/github.com/containernetworking/plugins/plugins/main/ptp/ptp.go +++ b/vendor/github.com/containernetworking/plugins/plugins/main/ptp/ptp.go @@ -22,16 +22,19 @@ import ( "os" "runtime" + "github.com/j-keck/arping" + "github.com/vishvananda/netlink" + "github.com/containernetworking/cni/pkg/skel" "github.com/containernetworking/cni/pkg/types" "github.com/containernetworking/cni/pkg/types/current" "github.com/containernetworking/cni/pkg/version" + "github.com/containernetworking/plugins/pkg/ip" "github.com/containernetworking/plugins/pkg/ipam" "github.com/containernetworking/plugins/pkg/ns" "github.com/containernetworking/plugins/pkg/utils" - "github.com/j-keck/arping" - "github.com/vishvananda/netlink" + bv "github.com/containernetworking/plugins/pkg/utils/buildversion" ) func init() { @@ -110,7 +113,7 @@ func setupContainerVeth(netns ns.NetNS, ifName string, mtu int, pr *current.Resu } for _, r := range []netlink.Route{ - netlink.Route{ + { LinkIndex: contVeth.Index, Dst: &net.IPNet{ IP: ipc.Gateway, @@ -119,7 +122,7 @@ func setupContainerVeth(netns ns.NetNS, ifName string, mtu int, pr *current.Resu Scope: netlink.SCOPE_LINK, Src: ipc.Address.IP, }, - netlink.Route{ + { LinkIndex: contVeth.Index, Dst: &net.IPNet{ IP: ipc.Address.IP.Mask(ipc.Address.Mask), @@ -197,6 +200,14 @@ func cmdAdd(args *skel.CmdArgs) error { if err != nil { return err } + + // Invoke ipam del if err to avoid ip leak + defer func() { + if err != nil { + ipam.ExecDel(conf.IPAM.Type, args.StdinData) + } + }() + // Convert whatever the IPAM result was into the current Result type result, err := current.NewResultFromResult(r) if err != nil { @@ -285,5 +296,108 @@ func cmdDel(args *skel.CmdArgs) error { } func main() { - skel.PluginMain(cmdAdd, cmdDel, version.All) + skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("ptp")) +} + +func cmdCheck(args *skel.CmdArgs) error { + conf := NetConf{} + if err := json.Unmarshal(args.StdinData, &conf); err != nil { + return fmt.Errorf("failed to load netconf: %v", err) + } + + netns, err := ns.GetNS(args.Netns) + if err != nil { + return fmt.Errorf("failed to open netns %q: %v", args.Netns, err) + } + defer netns.Close() + + // run the IPAM plugin and get back the config to apply + err = ipam.ExecCheck(conf.IPAM.Type, args.StdinData) + if err != nil { + return err + } + if conf.NetConf.RawPrevResult == nil { + return fmt.Errorf("ptp: Required prevResult missing") + } + if err := version.ParsePrevResult(&conf.NetConf); err != nil { + return err + } + // Convert whatever the IPAM result was into the current Result type + result, err := current.NewResultFromResult(conf.PrevResult) + if err != nil { + return err + } + + var contMap current.Interface + // Find interfaces for name whe know, that of host-device inside container + for _, intf := range result.Interfaces { + if args.IfName == intf.Name { + if args.Netns == intf.Sandbox { + contMap = *intf + continue + } + } + } + + // The namespace must be the same as what was configured + if args.Netns != contMap.Sandbox { + return fmt.Errorf("Sandbox in prevResult %s doesn't match configured netns: %s", + contMap.Sandbox, args.Netns) + } + + // + // Check prevResults for ips, routes and dns against values found in the container + if err := netns.Do(func(_ ns.NetNS) error { + + // Check interface against values found in the container + err := validateCniContainerInterface(contMap) + if err != nil { + return err + } + + err = ip.ValidateExpectedInterfaceIPs(args.IfName, result.IPs) + if err != nil { + return err + } + + err = ip.ValidateExpectedRoute(result.Routes) + if err != nil { + return err + } + return nil + }); err != nil { + return err + } + + return nil +} + +func validateCniContainerInterface(intf current.Interface) error { + + var link netlink.Link + var err error + + if intf.Name == "" { + return fmt.Errorf("Container interface name missing in prevResult: %v", intf.Name) + } + link, err = netlink.LinkByName(intf.Name) + if err != nil { + return fmt.Errorf("ptp: Container Interface name in prevResult: %s not found", intf.Name) + } + if intf.Sandbox == "" { + return fmt.Errorf("ptp: Error: Container interface %s should not be in host namespace", link.Attrs().Name) + } + + _, isVeth := link.(*netlink.Veth) + if !isVeth { + return fmt.Errorf("Error: Container interface %s not of type veth/p2p", link.Attrs().Name) + } + + if intf.Mac != "" { + if intf.Mac != link.Attrs().HardwareAddr.String() { + return fmt.Errorf("ptp: Interface %s Mac %s doesn't match container Mac: %s", intf.Name, intf.Mac, link.Attrs().HardwareAddr) + } + } + + return nil } diff --git a/vendor/github.com/containernetworking/plugins/plugins/main/ptp/ptp_test.go b/vendor/github.com/containernetworking/plugins/plugins/main/ptp/ptp_test.go index b5d16f896..6efdc0981 100644 --- a/vendor/github.com/containernetworking/plugins/plugins/main/ptp/ptp_test.go +++ b/vendor/github.com/containernetworking/plugins/plugins/main/ptp/ptp_test.go @@ -15,6 +15,7 @@ package main import ( + "encoding/json" "fmt" "github.com/containernetworking/cni/pkg/skel" @@ -25,10 +26,66 @@ import ( "github.com/vishvananda/netlink" + "github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) +type Net struct { + Name string `json:"name"` + CNIVersion string `json:"cniVersion"` + Type string `json:"type,omitempty"` + IPMasq bool `json:"ipMasq"` + MTU int `json:"mtu"` + IPAM *allocator.IPAMConfig `json:"ipam"` + DNS types.DNS `json:"dns"` + RawPrevResult map[string]interface{} `json:"prevResult,omitempty"` + PrevResult current.Result `json:"-"` +} + +func buildOneConfig(netName string, cniVersion string, orig *Net, prevResult types.Result) (*Net, error) { + var err error + + inject := map[string]interface{}{ + "name": netName, + "cniVersion": cniVersion, + } + // Add previous plugin result + if prevResult != nil { + inject["prevResult"] = prevResult + } + + // Ensure every config uses the same name and version + config := make(map[string]interface{}) + + confBytes, err := json.Marshal(orig) + if err != nil { + return nil, err + } + + err = json.Unmarshal(confBytes, &config) + if err != nil { + return nil, fmt.Errorf("unmarshal existing network bytes: %s", err) + } + + for key, value := range inject { + config[key] = value + } + + newBytes, err := json.Marshal(config) + if err != nil { + return nil, err + } + + conf := &Net{} + if err := json.Unmarshal(newBytes, &conf); err != nil { + return nil, fmt.Errorf("error parsing configuration: %s", err) + } + + return conf, nil + +} + var _ = Describe("ptp Operations", func() { var originalNS ns.NetNS @@ -142,6 +199,133 @@ var _ = Describe("ptp Operations", func() { Expect(err).NotTo(HaveOccurred()) } + doTestv4 := func(conf string, netName string, numIPs int) { + const IFNAME = "ptp0" + + targetNs, err := testutils.NewNS() + Expect(err).NotTo(HaveOccurred()) + defer targetNs.Close() + + args := &skel.CmdArgs{ + ContainerID: "dummy", + Netns: targetNs.Path(), + IfName: IFNAME, + StdinData: []byte(conf), + } + + var resI types.Result + var res *current.Result + + // Execute the plugin with the ADD command, creating the veth endpoints + err = originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + resI, _, err = testutils.CmdAddWithArgs(args, func() error { + return cmdAdd(args) + }) + Expect(err).NotTo(HaveOccurred()) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + + res, err = current.NewResultFromResult(resI) + Expect(err).NotTo(HaveOccurred()) + + // Make sure ptp link exists in the target namespace + // Then, ping the gateway + seenIPs := 0 + + wantMac := "" + err = targetNs.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + link, err := netlink.LinkByName(IFNAME) + Expect(err).NotTo(HaveOccurred()) + wantMac = link.Attrs().HardwareAddr.String() + + for _, ipc := range res.IPs { + if *ipc.Interface != 1 { + continue + } + seenIPs += 1 + saddr := ipc.Address.IP.String() + daddr := ipc.Gateway.String() + fmt.Fprintln(GinkgoWriter, "ping", saddr, "->", daddr) + + if err := testutils.Ping(saddr, daddr, (ipc.Version == "6"), 30); err != nil { + return fmt.Errorf("ping %s -> %s failed: %s", saddr, daddr, err) + } + } + + return nil + }) + Expect(err).NotTo(HaveOccurred()) + + Expect(seenIPs).To(Equal(numIPs)) + + // make sure the interfaces are correct + Expect(res.Interfaces).To(HaveLen(2)) + + Expect(res.Interfaces[0].Name).To(HavePrefix("veth")) + Expect(res.Interfaces[0].Mac).To(HaveLen(17)) + Expect(res.Interfaces[0].Sandbox).To(BeEmpty()) + + Expect(res.Interfaces[1].Name).To(Equal(IFNAME)) + Expect(res.Interfaces[1].Mac).To(Equal(wantMac)) + Expect(res.Interfaces[1].Sandbox).To(Equal(targetNs.Path())) + + // call CmdCheck + n := &Net{} + err = json.Unmarshal([]byte(conf), &n) + Expect(err).NotTo(HaveOccurred()) + + n.IPAM, _, err = allocator.LoadIPAMConfig([]byte(conf), "") + Expect(err).NotTo(HaveOccurred()) + + cniVersion := "0.4.0" + newConf, err := buildOneConfig(netName, cniVersion, n, res) + Expect(err).NotTo(HaveOccurred()) + + confString, err := json.Marshal(newConf) + Expect(err).NotTo(HaveOccurred()) + + args.StdinData = confString + + // CNI Check host-device in the target namespace + err = originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + var err error + err = testutils.CmdCheckWithArgs(args, func() error { return cmdCheck(args) }) + return err + }) + Expect(err).NotTo(HaveOccurred()) + + args.StdinData = []byte(conf) + + // Call the plugins with the DEL command, deleting the veth endpoints + err = originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + err := testutils.CmdDelWithArgs(args, func() error { + return cmdDel(args) + }) + Expect(err).NotTo(HaveOccurred()) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + + // Make sure ptp link has been deleted + err = targetNs.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + link, err := netlink.LinkByName(IFNAME) + Expect(err).To(HaveOccurred()) + Expect(link).To(BeNil()) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + } + It("configures and deconfigures a ptp link with ADD/DEL", func() { conf := `{ "cniVersion": "0.3.1", @@ -215,4 +399,39 @@ var _ = Describe("ptp Operations", func() { }) Expect(err).NotTo(HaveOccurred()) }) + + It("configures and deconfigures a CNI V4 ptp link with ADD/DEL", func() { + conf := `{ + "cniVersion": "0.4.0", + "name": "ptpNetv4", + "type": "ptp", + "ipMasq": true, + "mtu": 5000, + "ipam": { + "type": "host-local", + "subnet": "10.1.2.0/24" + } +}` + + doTestv4(conf, "ptpNetv4", 1) + }) + + It("configures and deconfigures a CNI V4 dual-stack ptp link with ADD/DEL", func() { + conf := `{ + "cniVersion": "0.4.0", + "name": "ptpNetv4ds", + "type": "ptp", + "ipMasq": true, + "mtu": 5000, + "ipam": { + "type": "host-local", + "ranges": [ + [{ "subnet": "10.1.2.0/24"}], + [{ "subnet": "2001:db8:1::0/66"}] + ] + } +}` + + doTestv4(conf, "ptpNetv4ds", 2) + }) }) diff --git a/vendor/github.com/containernetworking/plugins/plugins/main/vlan/vlan.go b/vendor/github.com/containernetworking/plugins/plugins/main/vlan/vlan.go index 694d85ae2..a7cd7c75f 100644 --- a/vendor/github.com/containernetworking/plugins/plugins/main/vlan/vlan.go +++ b/vendor/github.com/containernetworking/plugins/plugins/main/vlan/vlan.go @@ -20,14 +20,17 @@ import ( "fmt" "runtime" + "github.com/vishvananda/netlink" + "github.com/containernetworking/cni/pkg/skel" "github.com/containernetworking/cni/pkg/types" "github.com/containernetworking/cni/pkg/types/current" "github.com/containernetworking/cni/pkg/version" + "github.com/containernetworking/plugins/pkg/ip" "github.com/containernetworking/plugins/pkg/ipam" "github.com/containernetworking/plugins/pkg/ns" - "github.com/vishvananda/netlink" + bv "github.com/containernetworking/plugins/pkg/utils/buildversion" ) type NetConf struct { @@ -137,6 +140,14 @@ func cmdAdd(args *skel.CmdArgs) error { if err != nil { return err } + + // Invoke ipam del if err to avoid ip leak + defer func() { + if err != nil { + ipam.ExecDel(n.IPAM.Type, args.StdinData) + } + }() + // Convert whatever the IPAM result was into the current Result type result, err := current.NewResultFromResult(r) if err != nil { @@ -192,5 +203,130 @@ func cmdDel(args *skel.CmdArgs) error { } func main() { - skel.PluginMain(cmdAdd, cmdDel, version.All) + skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("vlan")) +} + +func cmdCheck(args *skel.CmdArgs) error { + conf := NetConf{} + if err := json.Unmarshal(args.StdinData, &conf); err != nil { + return fmt.Errorf("failed to load netconf: %v", err) + } + + netns, err := ns.GetNS(args.Netns) + if err != nil { + return fmt.Errorf("failed to open netns %q: %v", args.Netns, err) + } + defer netns.Close() + + // run the IPAM plugin and get back the config to apply + err = ipam.ExecCheck(conf.IPAM.Type, args.StdinData) + if err != nil { + return err + } + if conf.NetConf.RawPrevResult == nil { + return fmt.Errorf("ptp: Required prevResult missing") + } + if err := version.ParsePrevResult(&conf.NetConf); err != nil { + return err + } + // Convert whatever the IPAM result was into the current Result type + result, err := current.NewResultFromResult(conf.PrevResult) + if err != nil { + return err + } + + var contMap current.Interface + // Find interfaces for name whe know, that of host-device inside container + for _, intf := range result.Interfaces { + if args.IfName == intf.Name { + if args.Netns == intf.Sandbox { + contMap = *intf + continue + } + } + } + + // The namespace must be the same as what was configured + if args.Netns != contMap.Sandbox { + return fmt.Errorf("Sandbox in prevResult %s doesn't match configured netns: %s", + contMap.Sandbox, args.Netns) + } + + m, err := netlink.LinkByName(conf.Master) + if err != nil { + return fmt.Errorf("failed to lookup master %q: %v", conf.Master, err) + } + + // + // Check prevResults for ips, routes and dns against values found in the container + if err := netns.Do(func(_ ns.NetNS) error { + + // Check interface against values found in the container + err := validateCniContainerInterface(contMap, m.Attrs().Index, conf.VlanId, conf.MTU) + if err != nil { + return err + } + + err = ip.ValidateExpectedInterfaceIPs(args.IfName, result.IPs) + if err != nil { + return err + } + + err = ip.ValidateExpectedRoute(result.Routes) + if err != nil { + return err + } + return nil + }); err != nil { + return err + } + + return nil +} + +func validateCniContainerInterface(intf current.Interface, masterIndex int, vlanId int, mtu int) error { + + var link netlink.Link + var err error + + if intf.Name == "" { + return fmt.Errorf("Container interface name missing in prevResult: %v", intf.Name) + } + link, err = netlink.LinkByName(intf.Name) + if err != nil { + return fmt.Errorf("ptp: Container Interface name in prevResult: %s not found", intf.Name) + } + if intf.Sandbox == "" { + return fmt.Errorf("ptp: Error: Container interface %s should not be in host namespace", link.Attrs().Name) + } + + vlan, isVlan := link.(*netlink.Vlan) + if !isVlan { + return fmt.Errorf("Error: Container interface %s not of type vlan", link.Attrs().Name) + } + + // TODO This works when unit testing via cnitool; fails with ./test.sh + //if masterIndex != vlan.Attrs().ParentIndex { + // return fmt.Errorf("Container vlan Master %d does not match expected value: %d", vlan.Attrs().ParentIndex, masterIndex) + //} + + if intf.Mac != "" { + if intf.Mac != link.Attrs().HardwareAddr.String() { + return fmt.Errorf("vlan: Interface %s Mac %s doesn't match container Mac: %s", intf.Name, intf.Mac, link.Attrs().HardwareAddr) + } + } + + if vlanId != vlan.VlanId { + return fmt.Errorf("Error: Tuning link %s configured promisc is %v, current value is %d", + intf.Name, vlanId, vlan.VlanId) + } + + if mtu != 0 { + if mtu != link.Attrs().MTU { + return fmt.Errorf("Error: Tuning configured MTU of %s is %d, current value is %d", + intf.Name, mtu, link.Attrs().MTU) + } + } + + return nil } diff --git a/vendor/github.com/containernetworking/plugins/plugins/main/vlan/vlan_test.go b/vendor/github.com/containernetworking/plugins/plugins/main/vlan/vlan_test.go index d2cdf8218..e845a84cc 100644 --- a/vendor/github.com/containernetworking/plugins/plugins/main/vlan/vlan_test.go +++ b/vendor/github.com/containernetworking/plugins/plugins/main/vlan/vlan_test.go @@ -15,6 +15,7 @@ package main import ( + "encoding/json" "fmt" "net" "syscall" @@ -27,12 +28,69 @@ import ( "github.com/vishvananda/netlink" + "github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) const MASTER_NAME = "eth0" +type Net struct { + Name string `json:"name"` + CNIVersion string `json:"cniVersion"` + Type string `json:"type,omitempty"` + Master string `json:"master"` + VlanId int `json:"vlanId"` + MTU int `json:"mtu"` + IPAM *allocator.IPAMConfig `json:"ipam"` + DNS types.DNS `json:"dns"` + RawPrevResult map[string]interface{} `json:"prevResult,omitempty"` + PrevResult current.Result `json:"-"` +} + +func buildOneConfig(netName string, cniVersion string, orig *Net, prevResult types.Result) (*Net, error) { + var err error + + inject := map[string]interface{}{ + "name": netName, + "cniVersion": cniVersion, + } + // Add previous plugin result + if prevResult != nil { + inject["prevResult"] = prevResult + } + + // Ensure every config uses the same name and version + config := make(map[string]interface{}) + + confBytes, err := json.Marshal(orig) + if err != nil { + return nil, err + } + + err = json.Unmarshal(confBytes, &config) + if err != nil { + return nil, fmt.Errorf("unmarshal existing network bytes: %s", err) + } + + for key, value := range inject { + config[key] = value + } + + newBytes, err := json.Marshal(config) + if err != nil { + return nil, err + } + + conf := &Net{} + if err := json.Unmarshal(newBytes, &conf); err != nil { + return nil, fmt.Errorf("error parsing configuration: %s", err) + } + + return conf, nil + +} + var _ = Describe("vlan Operations", func() { var originalNS ns.NetNS @@ -234,4 +292,119 @@ var _ = Describe("vlan Operations", func() { }) Expect(err).NotTo(HaveOccurred()) }) + + It("configures and deconfigures an CNI V4 vlan link with ADD/CHECK/DEL", func() { + const IFNAME = "eth0" + + conf := fmt.Sprintf(`{ + "cniVersion": "0.4.0", + "name": "vlanTestv4", + "type": "vlan", + "master": "%s", + "vlanId": 1234, + "ipam": { + "type": "host-local", + "subnet": "10.1.2.0/24" + } +}`, MASTER_NAME) + + targetNs, err := testutils.NewNS() + Expect(err).NotTo(HaveOccurred()) + defer targetNs.Close() + + args := &skel.CmdArgs{ + ContainerID: "dummy", + Netns: targetNs.Path(), + IfName: IFNAME, + StdinData: []byte(conf), + } + + var result *current.Result + err = originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + r, _, err := testutils.CmdAddWithArgs(args, func() error { + return cmdAdd(args) + }) + Expect(err).NotTo(HaveOccurred()) + + result, err = current.GetResult(r) + Expect(err).NotTo(HaveOccurred()) + + Expect(len(result.Interfaces)).To(Equal(1)) + Expect(result.Interfaces[0].Name).To(Equal(IFNAME)) + Expect(len(result.IPs)).To(Equal(1)) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + + // Make sure vlan link exists in the target namespace + err = targetNs.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + link, err := netlink.LinkByName(IFNAME) + Expect(err).NotTo(HaveOccurred()) + Expect(link.Attrs().Name).To(Equal(IFNAME)) + + hwaddr, err := net.ParseMAC(result.Interfaces[0].Mac) + Expect(err).NotTo(HaveOccurred()) + Expect(link.Attrs().HardwareAddr).To(Equal(hwaddr)) + + addrs, err := netlink.AddrList(link, syscall.AF_INET) + Expect(err).NotTo(HaveOccurred()) + Expect(len(addrs)).To(Equal(1)) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + + // call CmdCheck + n := &Net{} + err = json.Unmarshal([]byte(conf), &n) + Expect(err).NotTo(HaveOccurred()) + + n.IPAM, _, err = allocator.LoadIPAMConfig([]byte(conf), "") + Expect(err).NotTo(HaveOccurred()) + + cniVersion := "0.4.0" + newConf, err := buildOneConfig("vlanTestv4", cniVersion, n, result) + Expect(err).NotTo(HaveOccurred()) + + confString, err := json.Marshal(newConf) + Expect(err).NotTo(HaveOccurred()) + + args.StdinData = confString + + // CNI Check host-device in the target namespace + err = originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + var err error + err = testutils.CmdCheckWithArgs(args, func() error { return cmdCheck(args) }) + return err + }) + Expect(err).NotTo(HaveOccurred()) + + args.StdinData = []byte(conf) + + err = originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + err = testutils.CmdDelWithArgs(args, func() error { + return cmdDel(args) + }) + Expect(err).NotTo(HaveOccurred()) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + + // Make sure vlan link has been deleted + err = targetNs.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + link, err := netlink.LinkByName(IFNAME) + Expect(err).To(HaveOccurred()) + Expect(link).To(BeNil()) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + }) }) diff --git a/vendor/github.com/containernetworking/plugins/plugins/main/windows/CONTRIBUTORS.md b/vendor/github.com/containernetworking/plugins/plugins/main/windows/CONTRIBUTORS.md new file mode 100644 index 000000000..ea050a8ed --- /dev/null +++ b/vendor/github.com/containernetworking/plugins/plugins/main/windows/CONTRIBUTORS.md @@ -0,0 +1,6 @@ +# Contributors +This is the official list of the Windows CNI network plugins contributors: + - @rakelkar + - @madhanrm + - @thxCode + - @nagiesek \ No newline at end of file diff --git a/vendor/github.com/containernetworking/plugins/plugins/main/windows/build.sh b/vendor/github.com/containernetworking/plugins/plugins/main/windows/build.sh new file mode 100755 index 000000000..d6bcd57fe --- /dev/null +++ b/vendor/github.com/containernetworking/plugins/plugins/main/windows/build.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +set -e + +PLUGINS=$(cat plugins/windows_only.txt) +for d in $PLUGINS; do + if [ -d "$d" ]; then + plugin="$(basename "$d").exe" + + echo " $plugin" + CXX=x86_64-w64-mingw32-g++ CC=x86_64-w64-mingw32-gcc CGO_ENABLED=1 \ + $GO build -o "${PWD}/bin/$plugin" "$@" "$REPO_PATH"/$d + fi +done diff --git a/vendor/github.com/containernetworking/plugins/plugins/main/windows/win-bridge/README.md b/vendor/github.com/containernetworking/plugins/plugins/main/windows/win-bridge/README.md new file mode 100644 index 000000000..024ce48c8 --- /dev/null +++ b/vendor/github.com/containernetworking/plugins/plugins/main/windows/win-bridge/README.md @@ -0,0 +1,55 @@ +# win-bridge plugin + +## Overview + +With win-bridge plugin, all containers (on the same host) are plugged into an L2Bridge network that has one endpoint in the host namespace. + +## Example configuration +``` +{ + "name": "mynet", + "type": "win-bridge", + "ipMasqNetwork": "10.244.0.0/16", + "ipam": { + "type": "host-local", + "subnet": "10.10.0.0/16" + }, + "policies":[ + { + "name":"EndpointPolicy", + "value":{ + "Type":"ROUTE", + "DestinationPrefix":"10.137.198.27/32", + "NeedEncap":true + } + } + ], + "HcnPolicyArgs": [ + { + "Type": "SDNRoute" + "Settings": { + "DestinationPrefix": "11.0.0.0/8", + "NeedEncap": true + } + } + ]. + "capabilities": { + "dns": true + } +} +``` + +## Network configuration reference + +* `ApiVersion` (integer, optional): ApiVersion to use, will default to hns. If set to "2" will try to use hcn APIs. +* `name` (string, required): the name of the network. +* `type` (string, required): "win-bridge". +* `ipMasqNetwork` (string, optional): setup NAT if not empty. +* `dns` (dictionary, optional): dns config to be used. + * `Nameservers` (list, optional): list of strings to be used for dns nameservers. + * `Search` (list, optional): list of stings to be used for dns search. +* `ipam` (dictionary, optional): IPAM configuration to be used for this network. +* `Policies` (list, optional): List of hns policies to be used (only used when ApiVersion is < 2). +* `HcnPolicyArgs` (list, optional): List of hcn policies to be used (only used when ApiVersion is 2). +* `capabilities` (dictionary, optional): runtime capabilities to enable. + * `dns` (boolean, optional): if true will take the dns config supplied by the runtime and override other settings. \ No newline at end of file diff --git a/vendor/github.com/containernetworking/plugins/plugins/main/windows/win-bridge/sample.conf b/vendor/github.com/containernetworking/plugins/plugins/main/windows/win-bridge/sample.conf new file mode 100755 index 000000000..319d555e5 --- /dev/null +++ b/vendor/github.com/containernetworking/plugins/plugins/main/windows/win-bridge/sample.conf @@ -0,0 +1,44 @@ +{ + "name":"cbr0", + "type":"flannel", + "delegate":{ + "type":"win-bridge", + "dns":{ + "nameservers":[ + "11.0.0.10" + ], + "search":[ + "svc.cluster.local" + ] + }, + "policies":[ + { + "name":"EndpointPolicy", + "value":{ + "Type":"OutBoundNAT", + "ExceptionList":[ + "192.168.0.0/16", + "11.0.0.0/8", + "10.137.196.0/23" + ] + } + }, + { + "name":"EndpointPolicy", + "value":{ + "Type":"ROUTE", + "DestinationPrefix":"11.0.0.0/8", + "NeedEncap":true + } + }, + { + "name":"EndpointPolicy", + "value":{ + "Type":"ROUTE", + "DestinationPrefix":"10.137.198.27/32", + "NeedEncap":true + } + } + ] + } +} \ No newline at end of file diff --git a/vendor/github.com/containernetworking/plugins/plugins/main/windows/win-bridge/win-bridge_windows.go b/vendor/github.com/containernetworking/plugins/plugins/main/windows/win-bridge/win-bridge_windows.go new file mode 100644 index 000000000..5b05e73a0 --- /dev/null +++ b/vendor/github.com/containernetworking/plugins/plugins/main/windows/win-bridge/win-bridge_windows.go @@ -0,0 +1,233 @@ +// Copyright 2017 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 main + +import ( + "encoding/json" + "fmt" + "runtime" + "strings" + "os" + + "github.com/Microsoft/hcsshim" + "github.com/Microsoft/hcsshim/hcn" + "github.com/juju/errors" + + "github.com/containernetworking/cni/pkg/skel" + "github.com/containernetworking/cni/pkg/types" + "github.com/containernetworking/cni/pkg/types/current" + "github.com/containernetworking/cni/pkg/version" + + "github.com/containernetworking/plugins/pkg/hns" + "github.com/containernetworking/plugins/pkg/ipam" + bv "github.com/containernetworking/plugins/pkg/utils/buildversion" +) + +type NetConf struct { + hns.NetConf + + IPMasqNetwork string `json:"ipMasqNetwork,omitempty"` + ApiVersion int `json:"ApiVersion"` +} + +func init() { + // this ensures that main runs only on main thread (thread group leader). + // since namespace ops (unshare, setns) are done for a single thread, we + // must ensure that the goroutine does not jump from OS thread to thread + runtime.LockOSThread() +} + +func loadNetConf(bytes []byte) (*NetConf, string, error) { + n := &NetConf{} + if err := json.Unmarshal(bytes, n); err != nil { + return nil, "", fmt.Errorf("failed to load netconf: %v", err) + } + return n, n.CNIVersion, nil +} + +func ProcessEndpointArgs(args *skel.CmdArgs, n *NetConf) (*hns.EndpointInfo, error) { + epInfo := new(hns.EndpointInfo) + epInfo.NetworkName = n.Name + epInfo.EndpointName = hns.ConstructEndpointName(args.ContainerID, args.Netns, epInfo.NetworkName) + // It's not necessary to have have an IPAM in windows as hns can provide IP/GW + if n.IPAM.Type != "" { + r, err := ipam.ExecAdd(n.IPAM.Type, args.StdinData) + if err != nil { + return nil, errors.Annotatef(err, "error while ipam.ExecAdd") + } + + // Convert whatever the IPAM result was into the current Result type + result, err := current.NewResultFromResult(r) + if err != nil { + return nil, errors.Annotatef(err, "error while NewResultFromResult") + } else { + if len(result.IPs) == 0 { + return nil, errors.New("IPAM plugin return is missing IP config") + } + epInfo.IpAddress = result.IPs[0].Address.IP + epInfo.Gateway = result.IPs[0].Address.IP.Mask(result.IPs[0].Address.Mask) + + // Calculate gateway for bridge network (needs to be x.2) + epInfo.Gateway[len(epInfo.Gateway)-1] += 2 + } + } + // NAT based on the the configured cluster network + if len(n.IPMasqNetwork) != 0 { + n.ApplyOutboundNatPolicy(n.IPMasqNetwork) + } + + epInfo.DNS = n.GetDNS() + + return epInfo, nil +} + +func cmdHnsAdd(args *skel.CmdArgs, n *NetConf) (*current.Result, error) { + networkName := n.Name + hnsNetwork, err := hcsshim.GetHNSNetworkByName(networkName) + if err != nil { + return nil, errors.Annotatef(err, "error while GETHNSNewtorkByName(%s)", networkName) + } + + if hnsNetwork == nil { + return nil, fmt.Errorf("network %v not found", networkName) + } + + if !strings.EqualFold(hnsNetwork.Type, "L2Bridge") { + return nil, fmt.Errorf("network %v is of an unexpected type: %v", networkName, hnsNetwork.Type) + } + + epName := hns.ConstructEndpointName(args.ContainerID, args.Netns, n.Name) + + hnsEndpoint, err := hns.ProvisionEndpoint(epName, hnsNetwork.Id, args.ContainerID, args.Netns, func() (*hcsshim.HNSEndpoint, error) { + epInfo, err := ProcessEndpointArgs(args, n) + epInfo.NetworkId = hnsNetwork.Id + if err != nil { + return nil, errors.Annotatef(err, "error while ProcessEndpointArgs") + } + hnsEndpoint, err := hns.GenerateHnsEndpoint(epInfo, &n.NetConf) + if err != nil { + return nil, errors.Annotatef(err, "error while GenerateHnsEndpoint") + } + return hnsEndpoint, nil + }) + if err != nil { + return nil, errors.Annotatef(err, "error while ProvisionEndpoint(%v,%v,%v)", epName, hnsNetwork.Id, args.ContainerID) + } + + result, err := hns.ConstructResult(hnsNetwork, hnsEndpoint) + if err != nil { + return nil, errors.Annotatef(err, "error while constructResult") + } + + return result, nil + +} + +func cmdHcnAdd(args *skel.CmdArgs, n *NetConf) (*current.Result, error) { + networkName := n.Name + hcnNetwork, err := hcn.GetNetworkByName(networkName) + if err != nil { + return nil, errors.Annotatef(err, "error while GetNetworkByName(%s)", networkName) + } + + if hcnNetwork == nil { + return nil, fmt.Errorf("network %v not found", networkName) + } + + if hcnNetwork.Type != hcn.L2Bridge { + return nil, fmt.Errorf("network %v is of unexpected type: %v", networkName, hcnNetwork.Type) + } + + epName := hns.ConstructEndpointName(args.ContainerID, args.Netns, n.Name) + + hcnEndpoint, err := hns.AddHcnEndpoint(epName, hcnNetwork.Id, args.Netns, func() (*hcn.HostComputeEndpoint, error) { + epInfo, err := ProcessEndpointArgs(args, n) + if err != nil { + return nil, errors.Annotatef(err, "error while ProcessEndpointArgs") + } + epInfo.NetworkId = hcnNetwork.Id + + hcnEndpoint, err := hns.GenerateHcnEndpoint(epInfo, &n.NetConf) + if err != nil { + return nil, errors.Annotatef(err, "error while GenerateHcnEndpoint") + } + return hcnEndpoint, nil + }) + if err != nil { + return nil, errors.Annotatef(err, "error while AddHcnEndpoint(%v,%v,%v)", epName, hcnNetwork.Id, args.Netns) + } + + result, err := hns.ConstructHcnResult(hcnNetwork, hcnEndpoint) + if err != nil { + return nil, errors.Annotatef(err, "error while ConstructHcnResult") + } + + return result, nil +} + +func cmdAdd(args *skel.CmdArgs) error { + n, cniVersion, err := loadNetConf(args.StdinData) + if err != nil { + return errors.Annotate(err, "error while loadNetConf") + } + + var result *current.Result + if n.ApiVersion == 2 { + result, err = cmdHcnAdd(args, n) + } else { + result, err = cmdHnsAdd(args, n) + } + + if err != nil { + os.Setenv("CNI_COMMAND", "DEL") + ipam.ExecDel(n.IPAM.Type, args.StdinData) + os.Setenv("CNI_COMMAND", "ADD") + return errors.Annotate(err, "error while executing ADD command") + } + + if (result == nil) { + return errors.New("result for ADD not populated correctly") + } + return types.PrintResult(result, cniVersion) +} + +func cmdDel(args *skel.CmdArgs) error { + n, _, err := loadNetConf(args.StdinData) + if err != nil { + return err + } + + if n.IPAM.Type != "" { + if err := ipam.ExecDel(n.IPAM.Type, args.StdinData); err != nil { + return err + } + } + epName := hns.ConstructEndpointName(args.ContainerID, args.Netns, n.Name) + + if n.ApiVersion == 2 { + return hns.RemoveHcnEndpoint(epName) + } else { + return hns.DeprovisionEndpoint(epName, args.Netns, args.ContainerID) + } +} + +func cmdCheck(_ *skel.CmdArgs) error { + // TODO: implement + return nil +} + +func main() { + skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.PluginSupports("0.1.0", "0.2.0", "0.3.0"), bv.BuildString("win-bridge")) +} diff --git a/vendor/github.com/containernetworking/plugins/plugins/main/windows/win-overlay/README.md b/vendor/github.com/containernetworking/plugins/plugins/main/windows/win-overlay/README.md new file mode 100644 index 000000000..d963fc7e8 --- /dev/null +++ b/vendor/github.com/containernetworking/plugins/plugins/main/windows/win-overlay/README.md @@ -0,0 +1,37 @@ +# win-overlay plugin + +## Overview + +With win-overlay plugin, all containers (on the same host) are plugged into an Overlay network based on VXLAN encapsulation. + +## Example configuration +``` +{ + "name": "mynet", + "type": "win-overlay", + "ipMasq": true, + "endpointMacPrefix": "0E-2A", + "ipam": { + "type": "host-local", + "subnet": "10.10.0.0/16" + } + "capabilites": { + "dns": true + } + +} +``` + +## Network configuration reference + +* `name` (string, required): the name of the network. +* `type` (string, required): "win-overlay". +* `ipMasq` (bool, optional): the inverse of `$FLANNEL_IPMASQ`, setup NAT for the hnsNetwork subnet. +* `dns` (dictionary, optional): dns config to be used. + * `Nameservers` (list, optional): list of strings to be used for dns nameservers. + * `Search` (list, optional): list of stings to be used for dns search. +* `endpointMacPrefix` (string, optional): set to the MAC prefix configured for Flannel. +* `Policies` (list, optional): List of hns policies to be used. +* `ipam` (dictionary, required): IPAM configuration to be used for this network. +* `capabilities` (dictionary, optional): runtime capabilities to be parsed and injected by runtime. + * `dns` (boolean, optional): if true will take the dns config supplied by the runtime and override other settings. \ No newline at end of file diff --git a/vendor/github.com/containernetworking/plugins/plugins/main/windows/win-overlay/sample.conf b/vendor/github.com/containernetworking/plugins/plugins/main/windows/win-overlay/sample.conf new file mode 100755 index 000000000..fcb6a2fa8 --- /dev/null +++ b/vendor/github.com/containernetworking/plugins/plugins/main/windows/win-overlay/sample.conf @@ -0,0 +1,36 @@ +{ + "cniVersion":"0.2.0", + "name":"vxlan0", + "type":"flannel", + "delegate":{ + "type":"win-overlay", + "dns":{ + "nameservers":[ + "11.0.0.10" + ], + "search":[ + "svc.cluster.local" + ] + }, + "policies":[ + { + "name":"EndpointPolicy", + "value":{ + "Type":"OutBoundNAT", + "ExceptionList":[ + "192.168.0.0/16", + "11.0.0.0/8" + ] + } + }, + { + "name":"EndpointPolicy", + "value":{ + "Type":"ROUTE", + "DestinationPrefix":"11.0.0.0/8", + "NeedEncap":true + } + } + ] + } +} \ No newline at end of file diff --git a/vendor/github.com/containernetworking/plugins/plugins/main/windows/win-overlay/win-overlay_windows.go b/vendor/github.com/containernetworking/plugins/plugins/main/windows/win-overlay/win-overlay_windows.go new file mode 100644 index 000000000..befd664f1 --- /dev/null +++ b/vendor/github.com/containernetworking/plugins/plugins/main/windows/win-overlay/win-overlay_windows.go @@ -0,0 +1,178 @@ +// Copyright 2017 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 main + +import ( + "encoding/json" + "fmt" + "runtime" + "strings" + "os" + + "github.com/Microsoft/hcsshim" + "github.com/juju/errors" + + "github.com/containernetworking/cni/pkg/skel" + "github.com/containernetworking/cni/pkg/types" + "github.com/containernetworking/cni/pkg/types/current" + "github.com/containernetworking/cni/pkg/version" + + "github.com/containernetworking/plugins/pkg/hns" + "github.com/containernetworking/plugins/pkg/ipam" + bv "github.com/containernetworking/plugins/pkg/utils/buildversion" +) + +type NetConf struct { + hns.NetConf + + IPMasq bool `json:"ipMasq"` + EndpointMacPrefix string `json:"endpointMacPrefix,omitempty"` +} + +func init() { + // this ensures that main runs only on main thread (thread group leader). + // since namespace ops (unshare, setns) are done for a single thread, we + // must ensure that the goroutine does not jump from OS thread to thread + runtime.LockOSThread() +} + +func loadNetConf(bytes []byte) (*NetConf, string, error) { + n := &NetConf{} + if err := json.Unmarshal(bytes, n); err != nil { + return nil, "", fmt.Errorf("failed to load netconf: %v", err) + } + return n, n.CNIVersion, nil +} + +func cmdAdd(args *skel.CmdArgs) error { + success := false + n, cniVersion, err := loadNetConf(args.StdinData) + if err != nil { + return errors.Annotate(err, "error while loadNetConf") + } + + if len(n.EndpointMacPrefix) != 0 { + if len(n.EndpointMacPrefix) != 5 || n.EndpointMacPrefix[2] != '-' { + return fmt.Errorf("endpointMacPrefix [%v] is invalid, value must be of the format xx-xx", n.EndpointMacPrefix) + } + } else { + n.EndpointMacPrefix = "0E-2A" + } + + networkName := n.Name + hnsNetwork, err := hcsshim.GetHNSNetworkByName(networkName) + if err != nil { + return errors.Annotatef(err, "error while GETHNSNewtorkByName(%s)", networkName) + } + + if hnsNetwork == nil { + return fmt.Errorf("network %v not found", networkName) + } + + if !strings.EqualFold(hnsNetwork.Type, "Overlay") { + return fmt.Errorf("network %v is of an unexpected type: %v", networkName, hnsNetwork.Type) + } + + epName := hns.ConstructEndpointName(args.ContainerID, args.Netns, n.Name) + + hnsEndpoint, err := hns.ProvisionEndpoint(epName, hnsNetwork.Id, args.ContainerID, args.Netns, func() (*hcsshim.HNSEndpoint, error) { + // run the IPAM plugin and get back the config to apply + r, err := ipam.ExecAdd(n.IPAM.Type, args.StdinData) + if err != nil { + return nil, errors.Annotatef(err, "error while ipam.ExecAdd") + } + + // Convert whatever the IPAM result was into the current Result type + result, err := current.NewResultFromResult(r) + if err != nil { + return nil, errors.Annotatef(err, "error while NewResultFromResult") + } + + if len(result.IPs) == 0 { + return nil, errors.New("IPAM plugin return is missing IP config") + } + + ipAddr := result.IPs[0].Address.IP.To4() + if ipAddr == nil { + return nil, errors.New("win-overlay doesn't support IPv6 now") + } + + // conjure a MAC based on the IP for Overlay + macAddr := fmt.Sprintf("%v-%02x-%02x-%02x-%02x", n.EndpointMacPrefix, ipAddr[0], ipAddr[1], ipAddr[2], ipAddr[3]) + // use the HNS network gateway + gw := hnsNetwork.Subnets[0].GatewayAddress + n.ApplyDefaultPAPolicy(hnsNetwork.ManagementIP) + if n.IPMasq { + n.ApplyOutboundNatPolicy(hnsNetwork.Subnets[0].AddressPrefix) + } + + result.DNS = n.GetDNS() + + hnsEndpoint := &hcsshim.HNSEndpoint{ + Name: epName, + VirtualNetwork: hnsNetwork.Id, + DNSServerList: strings.Join(result.DNS.Nameservers, ","), + DNSSuffix: strings.Join(result.DNS.Search, ","), + GatewayAddress: gw, + IPAddress: ipAddr, + MacAddress: macAddr, + Policies: n.MarshalPolicies(), + } + + return hnsEndpoint, nil + }) + defer func() { + if !success { + os.Setenv("CNI_COMMAND", "DEL") + ipam.ExecDel(n.IPAM.Type, args.StdinData) + os.Setenv("CNI_COMMAND", "ADD") + } + }() + if err != nil { + return errors.Annotatef(err, "error while ProvisionEndpoint(%v,%v,%v)", epName, hnsNetwork.Id, args.ContainerID) + } + + result, err := hns.ConstructResult(hnsNetwork, hnsEndpoint) + if err != nil { + return errors.Annotatef(err, "error while constructResult") + } + + success = true + return types.PrintResult(result, cniVersion) +} + +func cmdDel(args *skel.CmdArgs) error { + n, _, err := loadNetConf(args.StdinData) + if err != nil { + return err + } + + if err := ipam.ExecDel(n.IPAM.Type, args.StdinData); err != nil { + return err + } + + epName := hns.ConstructEndpointName(args.ContainerID, args.Netns, n.Name) + + return hns.DeprovisionEndpoint(epName, args.Netns, args.ContainerID) +} + +func cmdCheck(_ *skel.CmdArgs) error { + // TODO: implement + return nil +} + +func main() { + skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.PluginSupports("0.1.0", "0.2.0", "0.3.0"), bv.BuildString("win-overlay")) +} diff --git a/vendor/github.com/containernetworking/plugins/plugins/meta/bandwidth/README.md b/vendor/github.com/containernetworking/plugins/plugins/meta/bandwidth/README.md index 7129055ea..3fa49acbe 100644 --- a/vendor/github.com/containernetworking/plugins/plugins/meta/bandwidth/README.md +++ b/vendor/github.com/containernetworking/plugins/plugins/meta/bandwidth/README.md @@ -48,10 +48,10 @@ The following is an example [json configuration list](https://github.com/contain The result is an `ifb` device in the host namespace redirecting to the `host-interface`, with `tc tbf` applied on the `ifb` device and the `container-interface` ## Network configuration reference -* ingressRate: is the rate in Kbps at which traffic can enter an interface. (See http://man7.org/linux/man-pages/man8/tbf.8.html) -* ingressBurst: is the maximum amount in Kb that tokens can be made available for instantaneously. (See http://man7.org/linux/man-pages/man8/tbf.8.html) -* egressRate: is the rate in Kbps at which traffic can leave an interface. (See http://man7.org/linux/man-pages/man8/tbf.8.html) -* egressBurst: is the maximum amount in Kb that tokens can be made available for instantaneously. (See http://man7.org/linux/man-pages/man8/tbf.8.html) +* ingressRate: is the rate in bps at which traffic can enter an interface. (See http://man7.org/linux/man-pages/man8/tbf.8.html) +* ingressBurst: is the maximum amount in bits that tokens can be made available for instantaneously. (See http://man7.org/linux/man-pages/man8/tbf.8.html) +* egressRate: is the rate in bps at which traffic can leave an interface. (See http://man7.org/linux/man-pages/man8/tbf.8.html) +* egressBurst: is the maximum amount in bits that tokens can be made available for instantaneously. (See http://man7.org/linux/man-pages/man8/tbf.8.html) Both ingressRate and ingressBurst must be set in order to limit ingress bandwidth. If neither one is set, then ingress bandwidth is not limited. Both egressRate and egressBurst must be set in order to limit egress bandwidth. If neither one is set, then egress bandwidth is not limited. diff --git a/vendor/github.com/containernetworking/plugins/plugins/meta/bandwidth/bandwidth_linux_test.go b/vendor/github.com/containernetworking/plugins/plugins/meta/bandwidth/bandwidth_linux_test.go index bd0418c76..f09980fa3 100644 --- a/vendor/github.com/containernetworking/plugins/plugins/meta/bandwidth/bandwidth_linux_test.go +++ b/vendor/github.com/containernetworking/plugins/plugins/meta/bandwidth/bandwidth_linux_test.go @@ -15,6 +15,7 @@ package main import ( + "context" "encoding/json" "fmt" "net" @@ -34,6 +35,49 @@ import ( "github.com/onsi/gomega/gexec" ) +func buildOneConfig(name, cniVersion string, orig *PluginConf, prevResult types.Result) (*PluginConf, []byte, error) { + var err error + + inject := map[string]interface{}{ + "name": name, + "cniVersion": cniVersion, + } + // Add previous plugin result + if prevResult != nil { + inject["prevResult"] = prevResult + } + + // Ensure every config uses the same name and version + config := make(map[string]interface{}) + + confBytes, err := json.Marshal(orig) + if err != nil { + return nil, nil, err + } + + err = json.Unmarshal(confBytes, &config) + if err != nil { + return nil, nil, fmt.Errorf("unmarshal existing network bytes: %s", err) + } + + for key, value := range inject { + config[key] = value + } + + newBytes, err := json.Marshal(config) + if err != nil { + return nil, nil, err + } + + conf := &PluginConf{} + if err := json.Unmarshal(newBytes, &conf); err != nil { + return nil, nil, fmt.Errorf("error parsing configuration: %s", err) + } + + return conf, newBytes, nil + +} + var _ = Describe("bandwidth test", func() { var ( hostNs ns.NetNS @@ -80,7 +124,7 @@ var _ = Describe("bandwidth test", func() { "ingressRate": 8, "ingressBurst": 8, "egressRate": 16, - "egressBurst": 9, + "egressBurst": 8, "prevResult": { "interfaces": [ { @@ -135,7 +179,7 @@ var _ = Describe("bandwidth test", func() { Expect(qdiscs[0]).To(BeAssignableToTypeOf(&netlink.Tbf{})) Expect(qdiscs[0].(*netlink.Tbf).Rate).To(Equal(uint64(2))) - Expect(qdiscs[0].(*netlink.Tbf).Limit).To(Equal(uint32(9))) + Expect(qdiscs[0].(*netlink.Tbf).Limit).To(Equal(uint32(1))) hostVethLink, err := netlink.LinkByName(hostIfname) Expect(err).NotTo(HaveOccurred()) @@ -163,7 +207,7 @@ var _ = Describe("bandwidth test", func() { Expect(qdiscs[0]).To(BeAssignableToTypeOf(&netlink.Tbf{})) Expect(qdiscs[0].(*netlink.Tbf).Rate).To(Equal(uint64(1))) - Expect(qdiscs[0].(*netlink.Tbf).Limit).To(Equal(uint32(8))) + Expect(qdiscs[0].(*netlink.Tbf).Limit).To(Equal(uint32(1))) return nil })).To(Succeed()) @@ -176,8 +220,8 @@ var _ = Describe("bandwidth test", func() { "type": "bandwidth", "ingressRate": 0, "ingressBurst": 0, - "egressRate": 8, - "egressBurst": 1, + "egressRate": 8000, + "egressBurst": 80, "prevResult": { "interfaces": [ { @@ -245,8 +289,8 @@ var _ = Describe("bandwidth test", func() { "type": "bandwidth", "egressRate": 0, "egressBurst": 0, - "ingressRate": 8, - "ingressBurst": 1, + "ingressRate": 8000, + "ingressBurst": 80, "prevResult": { "interfaces": [ { @@ -302,8 +346,8 @@ var _ = Describe("bandwidth test", func() { Expect(qdiscs[0].Attrs().LinkIndex).To(Equal(containerIfLink.Attrs().Index)) Expect(qdiscs[0]).To(BeAssignableToTypeOf(&netlink.Tbf{})) - Expect(qdiscs[0].(*netlink.Tbf).Rate).To(Equal(uint64(1))) - Expect(qdiscs[0].(*netlink.Tbf).Limit).To(Equal(uint32(1))) + Expect(qdiscs[0].(*netlink.Tbf).Rate).To(Equal(uint64(1000))) + Expect(qdiscs[0].(*netlink.Tbf).Limit).To(Equal(uint32(35))) return nil })).To(Succeed()) @@ -426,7 +470,7 @@ var _ = Describe("bandwidth test", func() { Expect(qdiscs[0]).To(BeAssignableToTypeOf(&netlink.Tbf{})) Expect(qdiscs[0].(*netlink.Tbf).Rate).To(Equal(uint64(2))) - Expect(qdiscs[0].(*netlink.Tbf).Limit).To(Equal(uint32(9))) + Expect(qdiscs[0].(*netlink.Tbf).Limit).To(Equal(uint32(1))) hostVethLink, err := netlink.LinkByName(hostIfname) Expect(err).NotTo(HaveOccurred()) @@ -454,7 +498,7 @@ var _ = Describe("bandwidth test", func() { Expect(qdiscs[0]).To(BeAssignableToTypeOf(&netlink.Tbf{})) Expect(qdiscs[0].(*netlink.Tbf).Rate).To(Equal(uint64(1))) - Expect(qdiscs[0].(*netlink.Tbf).Limit).To(Equal(uint32(8))) + Expect(qdiscs[0].(*netlink.Tbf).Limit).To(Equal(uint32(1))) return nil })).To(Succeed()) @@ -625,7 +669,7 @@ var _ = Describe("bandwidth test", func() { defer GinkgoRecover() containerWithTbfRes, _, err = testutils.CmdAdd(containerWithTbfNS.Path(), "dummy", containerWithTbfIFName, []byte(ptpConf), func() error { - r, err := invoke.DelegateAdd("ptp", []byte(ptpConf)) + r, err := invoke.DelegateAdd(context.TODO(), "ptp", []byte(ptpConf), nil) Expect(r.Print()).To(Succeed()) return err @@ -633,7 +677,7 @@ var _ = Describe("bandwidth test", func() { Expect(err).NotTo(HaveOccurred()) containerWithoutTbfRes, _, err = testutils.CmdAdd(containerWithoutTbfNS.Path(), "dummy2", containerWithoutTbfIFName, []byte(ptpConf), func() error { - r, err := invoke.DelegateAdd("ptp", []byte(ptpConf)) + r, err := invoke.DelegateAdd(context.TODO(), "ptp", []byte(ptpConf), nil) Expect(r.Print()).To(Succeed()) return err @@ -642,7 +686,6 @@ var _ = Describe("bandwidth test", func() { containerWithTbfResult, err := current.GetResult(containerWithTbfRes) Expect(err).NotTo(HaveOccurred()) - tbfPluginConf := PluginConf{} tbfPluginConf.RuntimeConfig.Bandwidth = &BandwidthEntry{ IngressBurst: burstInBits, @@ -653,7 +696,7 @@ var _ = Describe("bandwidth test", func() { tbfPluginConf.Name = "mynet" tbfPluginConf.CNIVersion = "0.3.0" tbfPluginConf.Type = "bandwidth" - tbfPluginConf.RawPrevResult = &map[string]interface{}{ + tbfPluginConf.RawPrevResult = map[string]interface{}{ "ips": containerWithTbfResult.IPs, "interfaces": containerWithTbfResult.Interfaces, } @@ -662,7 +705,6 @@ var _ = Describe("bandwidth test", func() { IPs: containerWithTbfResult.IPs, Interfaces: containerWithTbfResult.Interfaces, } - conf, err := json.Marshal(tbfPluginConf) Expect(err).NotTo(HaveOccurred()) @@ -724,4 +766,169 @@ var _ = Describe("bandwidth test", func() { }, 1) }) + Context("when chaining bandwidth plugin with PTP using 0.4.0 config", func() { + var ptpConf string + var rateInBits int + var burstInBits int + var packetInBytes int + var containerWithoutTbfNS ns.NetNS + var containerWithTbfNS ns.NetNS + var portServerWithTbf int + var portServerWithoutTbf int + + var containerWithTbfRes types.Result + var containerWithoutTbfRes types.Result + var echoServerWithTbf *gexec.Session + var echoServerWithoutTbf *gexec.Session + + BeforeEach(func() { + rateInBytes := 1000 + rateInBits = rateInBytes * 8 + burstInBits = rateInBits * 2 + packetInBytes = rateInBytes * 25 + + ptpConf = `{ + "cniVersion": "0.4.0", + "name": "myBWnet", + "type": "ptp", + "ipMasq": true, + "mtu": 512, + "ipam": { + "type": "host-local", + "subnet": "10.1.2.0/24" + } +}` + + containerWithTbfIFName := "ptp0" + containerWithoutTbfIFName := "ptp1" + + var err error + containerWithTbfNS, err = testutils.NewNS() + Expect(err).NotTo(HaveOccurred()) + + containerWithoutTbfNS, err = testutils.NewNS() + Expect(err).NotTo(HaveOccurred()) + + By("create two containers, and use the bandwidth plugin on one of them") + Expect(hostNs.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + containerWithTbfRes, _, err = testutils.CmdAdd(containerWithTbfNS.Path(), "dummy", containerWithTbfIFName, []byte(ptpConf), func() error { + r, err := invoke.DelegateAdd(context.TODO(), "ptp", []byte(ptpConf), nil) + Expect(r.Print()).To(Succeed()) + + return err + }) + Expect(err).NotTo(HaveOccurred()) + + containerWithoutTbfRes, _, err = testutils.CmdAdd(containerWithoutTbfNS.Path(), "dummy2", containerWithoutTbfIFName, []byte(ptpConf), func() error { + r, err := invoke.DelegateAdd(context.TODO(), "ptp", []byte(ptpConf), nil) + Expect(r.Print()).To(Succeed()) + + return err + }) + Expect(err).NotTo(HaveOccurred()) + + containerWithTbfResult, err := current.GetResult(containerWithTbfRes) + Expect(err).NotTo(HaveOccurred()) + + tbfPluginConf := &PluginConf{} + err = json.Unmarshal([]byte(ptpConf), &tbfPluginConf) + Expect(err).NotTo(HaveOccurred()) + + tbfPluginConf.RuntimeConfig.Bandwidth = &BandwidthEntry{ + IngressBurst: burstInBits, + IngressRate: rateInBits, + EgressBurst: burstInBits, + EgressRate: rateInBits, + } + tbfPluginConf.Type = "bandwidth" + cniVersion := "0.4.0" + _, newConfBytes, err := buildOneConfig("myBWnet", cniVersion, tbfPluginConf, containerWithTbfResult) + Expect(err).NotTo(HaveOccurred()) + + args := &skel.CmdArgs{ + ContainerID: "dummy3", + Netns: containerWithTbfNS.Path(), + IfName: containerWithTbfIFName, + StdinData: newConfBytes, + } + + result, out, err := testutils.CmdAdd(containerWithTbfNS.Path(), args.ContainerID, "", newConfBytes, func() error { return cmdAdd(args) }) + Expect(err).NotTo(HaveOccurred(), string(out)) + + // Do CNI Check + checkConf := &PluginConf{} + err = json.Unmarshal([]byte(ptpConf), &checkConf) + Expect(err).NotTo(HaveOccurred()) + + checkConf.RuntimeConfig.Bandwidth = &BandwidthEntry{ + IngressBurst: burstInBits, + IngressRate: rateInBits, + EgressBurst: burstInBits, + EgressRate: rateInBits, + } + checkConf.Type = "bandwidth" + + _, newCheckBytes, err := buildOneConfig("myBWnet", cniVersion, checkConf, result) + Expect(err).NotTo(HaveOccurred()) + + args = &skel.CmdArgs{ + ContainerID: "dummy3", + Netns: containerWithTbfNS.Path(), + IfName: containerWithTbfIFName, + StdinData: newCheckBytes, + } + + err = testutils.CmdCheck(containerWithTbfNS.Path(), args.ContainerID, "", newCheckBytes, func() error { return cmdCheck(args) }) + Expect(err).NotTo(HaveOccurred()) + + return nil + })).To(Succeed()) + + By("starting a tcp server on both containers") + portServerWithTbf, echoServerWithTbf, err = startEchoServerInNamespace(containerWithTbfNS) + Expect(err).NotTo(HaveOccurred()) + portServerWithoutTbf, echoServerWithoutTbf, err = startEchoServerInNamespace(containerWithoutTbfNS) + Expect(err).NotTo(HaveOccurred()) + }) + + AfterEach(func() { + containerWithTbfNS.Close() + containerWithoutTbfNS.Close() + if echoServerWithoutTbf != nil { + echoServerWithoutTbf.Kill() + } + if echoServerWithTbf != nil { + echoServerWithTbf.Kill() + } + }) + + Measure("limits ingress traffic on veth device", func(b Benchmarker) { + var runtimeWithLimit time.Duration + var runtimeWithoutLimit time.Duration + + By("gather timing statistics about both containers") + By("sending tcp traffic to the container that has traffic shaped", func() { + runtimeWithLimit = b.Time("with tbf", func() { + result, err := current.GetResult(containerWithTbfRes) + Expect(err).NotTo(HaveOccurred()) + + makeTcpClientInNS(hostNs.Path(), result.IPs[0].Address.IP.String(), portServerWithTbf, packetInBytes) + }) + }) + + By("sending tcp traffic to the container that does not have traffic shaped", func() { + runtimeWithoutLimit = b.Time("without tbf", func() { + result, err := current.GetResult(containerWithoutTbfRes) + Expect(err).NotTo(HaveOccurred()) + + makeTcpClientInNS(hostNs.Path(), result.IPs[0].Address.IP.String(), portServerWithoutTbf, packetInBytes) + }) + }) + + Expect(runtimeWithLimit).To(BeNumerically(">", runtimeWithoutLimit+1000*time.Millisecond)) + }, 1) + }) + }) diff --git a/vendor/github.com/containernetworking/plugins/plugins/meta/bandwidth/bandwidth_suite_test.go b/vendor/github.com/containernetworking/plugins/plugins/meta/bandwidth/bandwidth_suite_test.go index 5aea87631..d59bb594c 100644 --- a/vendor/github.com/containernetworking/plugins/plugins/meta/bandwidth/bandwidth_suite_test.go +++ b/vendor/github.com/containernetworking/plugins/plugins/meta/bandwidth/bandwidth_suite_test.go @@ -167,6 +167,7 @@ func createVeth(hostNamespace string, hostVethIfName string, containerNamespace Expect(err).NotTo(HaveOccurred()) containerNs, err := ns.GetNS(containerNamespace) + Expect(err).NotTo(HaveOccurred()) err = containerNs.Do(func(_ ns.NetNS) error { peerAddr := &net.IPNet{ IP: hostIP, diff --git a/vendor/github.com/containernetworking/plugins/plugins/meta/bandwidth/ifb_creator.go b/vendor/github.com/containernetworking/plugins/plugins/meta/bandwidth/ifb_creator.go index a7032c0e8..c7f90cd05 100644 --- a/vendor/github.com/containernetworking/plugins/plugins/meta/bandwidth/ifb_creator.go +++ b/vendor/github.com/containernetworking/plugins/plugins/meta/bandwidth/ifb_creator.go @@ -125,9 +125,10 @@ func createTBF(rateInBits, burstInBits, linkIndex int) error { return fmt.Errorf("invalid burst: %d", burstInBits) } rateInBytes := rateInBits / 8 - bufferInBytes := buffer(uint64(rateInBytes), uint32(burstInBits)) + burstInBytes := burstInBits / 8 + bufferInBytes := buffer(uint64(rateInBytes), uint32(burstInBytes)) latency := latencyInUsec(latencyInMillis) - limitInBytes := limit(uint64(rateInBytes), latency, uint32(bufferInBytes)) + limitInBytes := limit(uint64(rateInBytes), latency, uint32(burstInBytes)) qdisc := &netlink.Tbf{ QdiscAttrs: netlink.QdiscAttrs{ @@ -159,7 +160,7 @@ func buffer(rate uint64, burst uint32) uint32 { } func limit(rate uint64, latency float64, buffer uint32) uint32 { - return uint32(float64(rate) / float64(netlink.TIME_UNITS_PER_SEC) * (latency + float64(tick2Time(buffer)))) + return uint32(float64(rate)*latency/float64(netlink.TIME_UNITS_PER_SEC)) + buffer } func latencyInUsec(latencyInMillis float64) float64 { diff --git a/vendor/github.com/containernetworking/plugins/plugins/meta/bandwidth/main.go b/vendor/github.com/containernetworking/plugins/plugins/meta/bandwidth/main.go index da580ae06..6d800589b 100644 --- a/vendor/github.com/containernetworking/plugins/plugins/meta/bandwidth/main.go +++ b/vendor/github.com/containernetworking/plugins/plugins/meta/bandwidth/main.go @@ -20,23 +20,25 @@ import ( "errors" "fmt" + "github.com/vishvananda/netlink" + "github.com/containernetworking/cni/pkg/skel" "github.com/containernetworking/cni/pkg/types" "github.com/containernetworking/cni/pkg/types/current" "github.com/containernetworking/cni/pkg/version" - "github.com/containernetworking/plugins/pkg/ip" - "github.com/vishvananda/netlink" + "github.com/containernetworking/plugins/pkg/ip" + bv "github.com/containernetworking/plugins/pkg/utils/buildversion" ) // BandwidthEntry corresponds to a single entry in the bandwidth argument, // see CONVENTIONS.md type BandwidthEntry struct { - IngressRate int `json:"ingressRate"` //Bandwidth rate in Kbps for traffic through container. 0 for no limit. If ingressRate is set, ingressBurst must also be set - IngressBurst int `json:"ingressBurst"` //Bandwidth burst in Kb for traffic through container. 0 for no limit. If ingressBurst is set, ingressRate must also be set + IngressRate int `json:"ingressRate"` //Bandwidth rate in bps for traffic through container. 0 for no limit. If ingressRate is set, ingressBurst must also be set + IngressBurst int `json:"ingressBurst"` //Bandwidth burst in bits for traffic through container. 0 for no limit. If ingressBurst is set, ingressRate must also be set - EgressRate int `json:"egressRate"` //Bandwidth rate in Kbps for traffic through container. 0 for no limit. If egressRate is set, egressBurst must also be set - EgressBurst int `json:"egressBurst"` //Bandwidth burst in Kb for traffic through container. 0 for no limit. If egressBurst is set, egressRate must also be set + EgressRate int `json:"egressRate"` //Bandwidth rate in bps for traffic through container. 0 for no limit. If egressRate is set, egressBurst must also be set + EgressBurst int `json:"egressBurst"` //Bandwidth burst in bits for traffic through container. 0 for no limit. If egressBurst is set, egressRate must also be set } func (bw *BandwidthEntry) isZero() bool { @@ -50,10 +52,6 @@ type PluginConf struct { Bandwidth *BandwidthEntry `json:"bandwidth,omitempty"` } `json:"runtimeConfig,omitempty"` - // RuntimeConfig *struct{} `json:"runtimeConfig"` - - RawPrevResult *map[string]interface{} `json:"prevResult"` - PrevResult *current.Result `json:"-"` *BandwidthEntry } @@ -65,21 +63,6 @@ func parseConfig(stdin []byte) (*PluginConf, error) { return nil, fmt.Errorf("failed to parse network configuration: %v", err) } - if conf.RawPrevResult != nil { - resultBytes, err := json.Marshal(conf.RawPrevResult) - if err != nil { - return nil, fmt.Errorf("could not serialize prevResult: %v", err) - } - res, err := version.NewResult(conf.CNIVersion, resultBytes) - if err != nil { - return nil, fmt.Errorf("could not parse prevResult: %v", err) - } - conf.RawPrevResult = nil - conf.PrevResult, err = current.NewResultFromResult(res) - if err != nil { - return nil, fmt.Errorf("could not convert result to current version: %v", err) - } - } bandwidth := getBandwidth(&conf) if bandwidth != nil { err := validateRateAndBurst(bandwidth.IngressRate, bandwidth.IngressBurst) @@ -92,6 +75,18 @@ func parseConfig(stdin []byte) (*PluginConf, error) { } } + if conf.RawPrevResult != nil { + var err error + if err = version.ParsePrevResult(&conf.NetConf); err != nil { + return nil, fmt.Errorf("could not parse prevResult: %v", err) + } + + _, err = current.NewResultFromResult(conf.PrevResult) + if err != nil { + return nil, fmt.Errorf("could not convert result to current version: %v", err) + } + } + return &conf, nil } @@ -167,7 +162,11 @@ func cmdAdd(args *skel.CmdArgs) error { return fmt.Errorf("must be called as chained plugin") } - hostInterface, err := getHostInterface(conf.PrevResult.Interfaces) + result, err := current.NewResultFromResult(conf.PrevResult) + if err != nil { + return fmt.Errorf("could not convert result to current version: %v", err) + } + hostInterface, err := getHostInterface(result.Interfaces) if err != nil { return err } @@ -200,7 +199,7 @@ func cmdAdd(args *skel.CmdArgs) error { return err } - conf.PrevResult.Interfaces = append(conf.PrevResult.Interfaces, ¤t.Interface{ + result.Interfaces = append(result.Interfaces, ¤t.Interface{ Name: ifbDeviceName, Mac: ifbDevice.Attrs().HardwareAddr.String(), }) @@ -210,7 +209,7 @@ func cmdAdd(args *skel.CmdArgs) error { } } - return types.PrintResult(conf.PrevResult, conf.CNIVersion) + return types.PrintResult(result, conf.CNIVersion) } func cmdDel(args *skel.CmdArgs) error { @@ -232,5 +231,125 @@ func cmdDel(args *skel.CmdArgs) error { } func main() { - skel.PluginMain(cmdAdd, cmdDel, version.PluginSupports("0.3.0", "0.3.1", version.Current())) + skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.PluginSupports("0.3.0", "0.3.1", version.Current()), bv.BuildString("bandwidth")) +} + +func SafeQdiscList(link netlink.Link) ([]netlink.Qdisc, error) { + qdiscs, err := netlink.QdiscList(link) + if err != nil { + return nil, err + } + result := []netlink.Qdisc{} + for _, qdisc := range qdiscs { + // filter out pfifo_fast qdiscs because + // older kernels don't return them + _, pfifo := qdisc.(*netlink.PfifoFast) + if !pfifo { + result = append(result, qdisc) + } + } + return result, nil +} + +func cmdCheck(args *skel.CmdArgs) error { + bwConf, err := parseConfig(args.StdinData) + if err != nil { + return err + } + + if bwConf.PrevResult == nil { + return fmt.Errorf("must be called as a chained plugin") + } + + result, err := current.NewResultFromResult(bwConf.PrevResult) + if err != nil { + return fmt.Errorf("could not convert result to current version: %v", err) + } + + hostInterface, err := getHostInterface(result.Interfaces) + if err != nil { + return err + } + link, err := netlink.LinkByName(hostInterface.Name) + if err != nil { + return err + } + + bandwidth := getBandwidth(bwConf) + + if bandwidth.IngressRate > 0 && bandwidth.IngressBurst > 0 { + rateInBytes := bandwidth.IngressRate / 8 + burstInBytes := bandwidth.IngressBurst / 8 + bufferInBytes := buffer(uint64(rateInBytes), uint32(burstInBytes)) + latency := latencyInUsec(latencyInMillis) + limitInBytes := limit(uint64(rateInBytes), latency, uint32(burstInBytes)) + + qdiscs, err := SafeQdiscList(link) + if err != nil { + return err + } + if len(qdiscs) == 0 { + return fmt.Errorf("Failed to find qdisc") + } + + for _, qdisc := range qdiscs { + tbf, isTbf := qdisc.(*netlink.Tbf) + if !isTbf { + break + } + if tbf.Rate != uint64(rateInBytes) { + return fmt.Errorf("Rate doesn't match") + } + if tbf.Limit != uint32(limitInBytes) { + return fmt.Errorf("Limit doesn't match") + } + if tbf.Buffer != uint32(bufferInBytes) { + return fmt.Errorf("Buffer doesn't match") + } + } + } + + if bandwidth.EgressRate > 0 && bandwidth.EgressBurst > 0 { + rateInBytes := bandwidth.EgressRate / 8 + burstInBytes := bandwidth.EgressBurst / 8 + bufferInBytes := buffer(uint64(rateInBytes), uint32(burstInBytes)) + latency := latencyInUsec(latencyInMillis) + limitInBytes := limit(uint64(rateInBytes), latency, uint32(burstInBytes)) + + ifbDeviceName, err := getIfbDeviceName(bwConf.Name, args.ContainerID) + if err != nil { + return err + } + + ifbDevice, err := netlink.LinkByName(ifbDeviceName) + if err != nil { + return fmt.Errorf("get ifb device: %s", err) + } + + qdiscs, err := SafeQdiscList(ifbDevice) + if err != nil { + return err + } + if len(qdiscs) == 0 { + return fmt.Errorf("Failed to find qdisc") + } + + for _, qdisc := range qdiscs { + tbf, isTbf := qdisc.(*netlink.Tbf) + if !isTbf { + break + } + if tbf.Rate != uint64(rateInBytes) { + return fmt.Errorf("Rate doesn't match") + } + if tbf.Limit != uint32(limitInBytes) { + return fmt.Errorf("Limit doesn't match") + } + if tbf.Buffer != uint32(bufferInBytes) { + return fmt.Errorf("Buffer doesn't match") + } + } + } + + return nil } diff --git a/vendor/github.com/containernetworking/plugins/plugins/meta/firewall/README.md b/vendor/github.com/containernetworking/plugins/plugins/meta/firewall/README.md new file mode 100644 index 000000000..f73a41213 --- /dev/null +++ b/vendor/github.com/containernetworking/plugins/plugins/meta/firewall/README.md @@ -0,0 +1,135 @@ +# firewall plugin + +## Overview + +This plugin creates firewall rules to allow traffic to/from container IP address via the host network . +It does not create any network interfaces and therefore does not set up connectivity by itself. +It is intended to be used as a chained plugins. + +## Operation +The following network configuration file + +```json +{ + "cniVersion": "0.3.1", + "name": "bridge-firewalld", + "plugins": [ + { + "type": "bridge", + "bridge": "cni0", + "isGateway": true, + "ipMasq": true, + "ipam": { + "type": "host-local", + "subnet": "10.88.0.0/16", + "routes": [ + { "dst": "0.0.0.0/0" } + ] + } + }, + { + "type": "firewall", + } + ] +} +``` + +will allow any IP addresses configured by earlier plugins to send/receive traffic via the host. + +A successful result would simply be an empty result, unless a previous plugin passed a previous result, in which case this plugin will return that previous result. + +## Backends + +This plugin supports multiple firewall backends that implement the desired functionality. +Available backends include `iptables` and `firewalld` and may be selected with the `backend` key. +If no `backend` key is given, the plugin will use firewalld if the service exists on the D-Bus system bus. +If no firewalld service is found, it will fall back to iptables. + +## firewalld backend rule structure +When the `firewalld` backend is used, this example will place the IPAM allocated address for the container (e.g. 10.88.0.2) into firewalld's `trusted` zone, allowing it to send/receive traffic. + + +A sample standalone config list (with the file extension .conflist) using firewalld backend might +look like: + +```json +{ + "cniVersion": "0.3.1", + "name": "bridge-firewalld", + "plugins": [ + { + "type": "bridge", + "bridge": "cni0", + "isGateway": true, + "ipMasq": true, + "ipam": { + "type": "host-local", + "subnet": "10.88.0.0/16", + "routes": [ + { "dst": "0.0.0.0/0" } + ] + } + }, + { + "type": "firewall", + "backend": "firewalld" + } + ] +} +``` + + +`FORWARD_IN_ZONES_SOURCE` chain: +- `-d 10.88.0.2 -j FWDI_trusted` + +`CNI_FORWARD_OUT_ZONES_SOURCE` chain: +- `-s 10.88.0.2 -j FWDO_trusted` + + +## iptables backend rule structure + +A sample standalone config list (with the file extension .conflist) using iptables backend might +look like: + +```json +{ + "cniVersion": "0.3.1", + "name": "bridge-firewalld", + "plugins": [ + { + "type": "bridge", + "bridge": "cni0", + "isGateway": true, + "ipMasq": true, + "ipam": { + "type": "host-local", + "subnet": "10.88.0.0/16", + "routes": [ + { "dst": "0.0.0.0/0" } + ] + } + }, + { + "type": "firewall", + "backend": "iptables" + } + ] +} +``` + +When the `iptables` backend is used, the above example will create two new iptables chains in the `filter` table and add rules that allow the given interface to send/receive traffic. + +### FORWARD +A new chain, CNI-FORWARD is added to the FORWARD chain. CNI-FORWARD is the chain where rules will be added +when containers are created and from where rules will be removed when containers terminate. + +`FORWARD` chain: +- `-j CNI-FORWARD` + +CNI-FORWARD will have a pair of rules added, one for each direction, using the IPAM assigned IP address +of the container as shown: + +`CNI_FORWARD` chain: +- `-s 10.88.0.2 -m conntrack --ctstate RELATED,ESTABLISHED -j CNI-FORWARD` +- `-d 10.88.0.2 -j CNI-FORWARD` + diff --git a/vendor/github.com/containernetworking/plugins/plugins/meta/firewall/firewall.go b/vendor/github.com/containernetworking/plugins/plugins/meta/firewall/firewall.go new file mode 100644 index 000000000..c2eef93a1 --- /dev/null +++ b/vendor/github.com/containernetworking/plugins/plugins/meta/firewall/firewall.go @@ -0,0 +1,184 @@ +// Copyright 2016 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. + +// This is a "meta-plugin". It reads in its own netconf, it does not create +// any network interface but just changes the network sysctl. + +package main + +import ( + "encoding/json" + "fmt" + "net" + + "github.com/containernetworking/cni/pkg/skel" + "github.com/containernetworking/cni/pkg/types" + "github.com/containernetworking/cni/pkg/types/current" + "github.com/containernetworking/cni/pkg/version" + + "github.com/containernetworking/plugins/pkg/ns" + bv "github.com/containernetworking/plugins/pkg/utils/buildversion" +) + +// FirewallNetConf represents the firewall configuration. +type FirewallNetConf struct { + types.NetConf + + // Backend is the firewall type to add rules to. Allowed values are + // 'iptables' and 'firewalld'. + Backend string `json:"backend"` + + // IptablesAdminChainName is an optional name to use instead of the default + // admin rules override chain name that includes the interface name. + IptablesAdminChainName string `json:"iptablesAdminChainName,omitempty"` + + // FirewalldZone is an optional firewalld zone to place the interface into. If + // the firewalld backend is used but the zone is not given, it defaults + // to 'trusted' + FirewalldZone string `json:"firewalldZone,omitempty"` +} + +type FirewallBackend interface { + Add(*FirewallNetConf, *current.Result) error + Del(*FirewallNetConf, *current.Result) error + Check(*FirewallNetConf, *current.Result) error +} + +func ipString(ip net.IPNet) string { + if ip.IP.To4() == nil { + return ip.IP.String() + "/128" + } + return ip.IP.String() + "/32" +} + +func parseConf(data []byte) (*FirewallNetConf, *current.Result, error) { + conf := FirewallNetConf{} + if err := json.Unmarshal(data, &conf); err != nil { + return nil, nil, fmt.Errorf("failed to load netconf: %v", err) + } + + // Parse previous result. + if conf.RawPrevResult == nil { + return nil, nil, fmt.Errorf("missing prevResult from earlier plugin") + } + + // Parse previous result. + var result *current.Result + var err error + if err = version.ParsePrevResult(&conf.NetConf); err != nil { + return nil, nil, fmt.Errorf("could not parse prevResult: %v", err) + } + + result, err = current.NewResultFromResult(conf.PrevResult) + if err != nil { + return nil, nil, fmt.Errorf("could not convert result to current version: %v", err) + } + + // Default the firewalld zone to trusted + if conf.FirewalldZone == "" { + conf.FirewalldZone = "trusted" + } + + return &conf, result, nil +} + +func getBackend(conf *FirewallNetConf) (FirewallBackend, error) { + switch conf.Backend { + case "iptables": + return newIptablesBackend(conf) + case "firewalld": + return newFirewalldBackend(conf) + } + + // Default to firewalld if it's running + if isFirewalldRunning() { + return newFirewalldBackend(conf) + } + + // Otherwise iptables + return newIptablesBackend(conf) +} + +func cmdAdd(args *skel.CmdArgs) error { + conf, result, err := parseConf(args.StdinData) + if err != nil { + return err + } + + backend, err := getBackend(conf) + if err != nil { + return err + } + + if err := backend.Add(conf, result); err != nil { + return err + } + + if result == nil { + result = ¤t.Result{} + } + return types.PrintResult(result, conf.CNIVersion) +} + +func cmdDel(args *skel.CmdArgs) error { + conf, result, err := parseConf(args.StdinData) + if err != nil { + return err + } + + backend, err := getBackend(conf) + if err != nil { + return err + } + + // Tolerate errors if the container namespace has been torn down already + containerNS, err := ns.GetNS(args.Netns) + if err == nil { + defer containerNS.Close() + } + + // Runtime errors are ignored + if err := backend.Del(conf, result); err != nil { + return err + } + + return nil +} + +func main() { + skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.PluginSupports("0.4.0"), bv.BuildString("firewall")) +} + +func cmdCheck(args *skel.CmdArgs) error { + conf, result, err := parseConf(args.StdinData) + if err != nil { + return err + } + + // Ensure we have previous result. + if result == nil { + return fmt.Errorf("Required prevResult missing") + } + + backend, err := getBackend(conf) + if err != nil { + return err + } + + if err := backend.Check(conf, result); err != nil { + return err + } + + return nil +} diff --git a/vendor/github.com/containernetworking/plugins/plugins/meta/firewall/firewall_firewalld_test.go b/vendor/github.com/containernetworking/plugins/plugins/meta/firewall/firewall_firewalld_test.go new file mode 100644 index 000000000..68a00793e --- /dev/null +++ b/vendor/github.com/containernetworking/plugins/plugins/meta/firewall/firewall_firewalld_test.go @@ -0,0 +1,343 @@ +// Copyright 2018 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 main + +import ( + "bufio" + "fmt" + "os/exec" + "strings" + "sync" + "syscall" + + "github.com/containernetworking/cni/pkg/invoke" + "github.com/containernetworking/cni/pkg/skel" + "github.com/containernetworking/cni/pkg/types/current" + "github.com/containernetworking/plugins/pkg/ns" + "github.com/containernetworking/plugins/pkg/testutils" + + "github.com/godbus/dbus" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +const ( + confTmpl = `{ + "cniVersion": "0.3.1", + "name": "firewalld-test", + "type": "firewall", + "backend": "firewalld", + "zone": "trusted", + "prevResult": { + "cniVersion": "0.3.0", + "interfaces": [ + {"name": "%s", "sandbox": "%s"} + ], + "ips": [ + { + "version": "4", + "address": "10.0.0.2/24", + "gateway": "10.0.0.1", + "interface": 0 + } + ] + } +}` + ifname = "eth0" +) + +type fakeFirewalld struct { + zone string + source string +} + +func (f *fakeFirewalld) clear() { + f.zone = "" + f.source = "" +} + +func (f *fakeFirewalld) AddSource(zone, source string) (string, *dbus.Error) { + f.zone = zone + f.source = source + return "", nil +} + +func (f *fakeFirewalld) RemoveSource(zone, source string) (string, *dbus.Error) { + f.zone = zone + f.source = source + return "", nil +} + +func (f *fakeFirewalld) QuerySource(zone, source string) (bool, *dbus.Error) { + if f.zone != zone { + return false, nil + } + if f.source != source { + return false, nil + } + return true, nil +} + +func spawnSessionDbus(wg *sync.WaitGroup) (string, *exec.Cmd) { + // Start a private D-Bus session bus + path, err := invoke.FindInPath("dbus-daemon", []string{ + "/bin", "/sbin", "/usr/bin", "/usr/sbin", + }) + Expect(err).NotTo(HaveOccurred()) + cmd := exec.Command(path, "--session", "--print-address", "--nofork", "--nopidfile") + stdout, err := cmd.StdoutPipe() + Expect(err).NotTo(HaveOccurred()) + err = cmd.Start() + Expect(err).NotTo(HaveOccurred()) + + // Wait for dbus-daemon to print the bus address + bytes, err := bufio.NewReader(stdout).ReadString('\n') + Expect(err).NotTo(HaveOccurred()) + busAddr := strings.TrimSpace(string(bytes)) + Expect(strings.HasPrefix(busAddr, "unix:abstract")).To(BeTrue()) + + var startWg sync.WaitGroup + wg.Add(1) + startWg.Add(1) + go func() { + defer GinkgoRecover() + + startWg.Done() + err = cmd.Wait() + Expect(err).NotTo(HaveOccurred()) + wg.Done() + }() + + startWg.Wait() + return busAddr, cmd +} + +var _ = Describe("firewalld test", func() { + var ( + targetNs ns.NetNS + cmd *exec.Cmd + conn *dbus.Conn + wg sync.WaitGroup + fwd *fakeFirewalld + busAddr string + ) + + BeforeEach(func() { + var err error + targetNs, err = testutils.NewNS() + Expect(err).NotTo(HaveOccurred()) + + // Start a private D-Bus session bus + busAddr, cmd = spawnSessionDbus(&wg) + conn, err = dbus.Dial(busAddr) + Expect(err).NotTo(HaveOccurred()) + err = conn.Auth(nil) + Expect(err).NotTo(HaveOccurred()) + err = conn.Hello() + Expect(err).NotTo(HaveOccurred()) + + // Start our fake firewalld + reply, err := conn.RequestName(firewalldName, dbus.NameFlagDoNotQueue) + Expect(err).NotTo(HaveOccurred()) + Expect(reply).To(Equal(dbus.RequestNameReplyPrimaryOwner)) + + fwd = &fakeFirewalld{} + // Because firewalld D-Bus methods start with lower-case, and + // because in Go lower-case methods are private, we need to remap + // Go public methods to the D-Bus name + methods := map[string]string{ + "AddSource": firewalldAddSourceMethod, + "QuerySource": firewalldQuerySourceMethod, + "RemoveSource": firewalldRemoveSourceMethod, + } + conn.ExportWithMap(fwd, methods, firewalldPath, firewalldZoneInterface) + + // Make sure the plugin uses our private session bus + testConn = conn + }) + + AfterEach(func() { + _, err := conn.ReleaseName(firewalldName) + Expect(err).NotTo(HaveOccurred()) + + err = cmd.Process.Signal(syscall.SIGTERM) + Expect(err).NotTo(HaveOccurred()) + + wg.Wait() + }) + + It("works with a 0.3.1 config", func() { + Expect(isFirewalldRunning()).To(BeTrue()) + + conf := fmt.Sprintf(confTmpl, ifname, targetNs.Path()) + args := &skel.CmdArgs{ + ContainerID: "dummy", + Netns: targetNs.Path(), + IfName: ifname, + StdinData: []byte(conf), + } + _, _, err := testutils.CmdAdd(targetNs.Path(), args.ContainerID, ifname, []byte(conf), func() error { + return cmdAdd(args) + }) + Expect(err).NotTo(HaveOccurred()) + Expect(fwd.zone).To(Equal("trusted")) + Expect(fwd.source).To(Equal("10.0.0.2/32")) + fwd.clear() + + err = testutils.CmdDel(targetNs.Path(), args.ContainerID, ifname, func() error { + return cmdDel(args) + }) + Expect(err).NotTo(HaveOccurred()) + Expect(fwd.zone).To(Equal("trusted")) + Expect(fwd.source).To(Equal("10.0.0.2/32")) + }) + + It("defaults to the firewalld backend", func() { + conf := `{ + "cniVersion": "0.3.1", + "name": "firewalld-test", + "type": "firewall", + "zone": "trusted", + "prevResult": { + "cniVersion": "0.3.0", + "interfaces": [ + {"name": "eth0", "sandbox": "/foobar"} + ], + "ips": [ + { + "version": "4", + "address": "10.0.0.2/24", + "gateway": "10.0.0.1", + "interface": 0 + } + ] + } + }` + + Expect(isFirewalldRunning()).To(BeTrue()) + + args := &skel.CmdArgs{ + ContainerID: "dummy", + Netns: targetNs.Path(), + IfName: ifname, + StdinData: []byte(conf), + } + _, _, err := testutils.CmdAdd(targetNs.Path(), args.ContainerID, ifname, []byte(conf), func() error { + return cmdAdd(args) + }) + Expect(err).NotTo(HaveOccurred()) + Expect(fwd.zone).To(Equal("trusted")) + Expect(fwd.source).To(Equal("10.0.0.2/32")) + }) + + It("passes through the prevResult", func() { + conf := `{ + "cniVersion": "0.3.1", + "name": "firewalld-test", + "type": "firewall", + "zone": "trusted", + "prevResult": { + "cniVersion": "0.3.0", + "interfaces": [ + {"name": "eth0", "sandbox": "/foobar"} + ], + "ips": [ + { + "version": "4", + "address": "10.0.0.2/24", + "gateway": "10.0.0.1", + "interface": 0 + } + ] + } + }` + + Expect(isFirewalldRunning()).To(BeTrue()) + + args := &skel.CmdArgs{ + ContainerID: "dummy", + Netns: targetNs.Path(), + IfName: ifname, + StdinData: []byte(conf), + } + r, _, err := testutils.CmdAdd(targetNs.Path(), args.ContainerID, ifname, []byte(conf), func() error { + return cmdAdd(args) + }) + Expect(err).NotTo(HaveOccurred()) + + result, err := current.GetResult(r) + Expect(err).NotTo(HaveOccurred()) + + Expect(len(result.Interfaces)).To(Equal(1)) + Expect(result.Interfaces[0].Name).To(Equal("eth0")) + Expect(len(result.IPs)).To(Equal(1)) + Expect(result.IPs[0].Address.String()).To(Equal("10.0.0.2/24")) + }) + + It("works with a 0.4.0 config, including Check", func() { + Expect(isFirewalldRunning()).To(BeTrue()) + + conf := `{ + "cniVersion": "0.4.0", + "name": "firewalld-test", + "type": "firewall", + "backend": "firewalld", + "zone": "trusted", + "prevResult": { + "cniVersion": "0.4.0", + "interfaces": [ + {"name": "eth0", "sandbox": "/foobar"} + ], + "ips": [ + { + "version": "4", + "address": "10.0.0.2/24", + "gateway": "10.0.0.1", + "interface": 0 + } + ] + } + }` + + args := &skel.CmdArgs{ + ContainerID: "dummy", + Netns: targetNs.Path(), + IfName: ifname, + StdinData: []byte(conf), + } + r, _, err := testutils.CmdAddWithArgs(args, func() error { + return cmdAdd(args) + }) + Expect(err).NotTo(HaveOccurred()) + Expect(fwd.zone).To(Equal("trusted")) + Expect(fwd.source).To(Equal("10.0.0.2/32")) + + _, err = current.GetResult(r) + Expect(err).NotTo(HaveOccurred()) + + err = testutils.CmdCheckWithArgs(args, func() error { + return cmdCheck(args) + }) + Expect(err).NotTo(HaveOccurred()) + + err = testutils.CmdDelWithArgs(args, func() error { + return cmdDel(args) + }) + Expect(err).NotTo(HaveOccurred()) + Expect(fwd.zone).To(Equal("trusted")) + Expect(fwd.source).To(Equal("10.0.0.2/32")) + }) +}) diff --git a/vendor/github.com/containernetworking/plugins/plugins/meta/firewall/firewall_iptables_test.go b/vendor/github.com/containernetworking/plugins/plugins/meta/firewall/firewall_iptables_test.go new file mode 100644 index 000000000..6c7358f42 --- /dev/null +++ b/vendor/github.com/containernetworking/plugins/plugins/meta/firewall/firewall_iptables_test.go @@ -0,0 +1,512 @@ +// Copyright 2017 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 main + +import ( + "encoding/json" + "fmt" + "strings" + + "github.com/containernetworking/cni/pkg/skel" + "github.com/containernetworking/cni/pkg/types" + "github.com/containernetworking/cni/pkg/types/current" + "github.com/containernetworking/cni/pkg/version" + "github.com/containernetworking/plugins/pkg/ns" + "github.com/containernetworking/plugins/pkg/testutils" + + "github.com/vishvananda/netlink" + + "github.com/coreos/go-iptables/iptables" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +func findChains(chains []string) (bool, bool) { + var foundAdmin, foundPriv bool + for _, ch := range chains { + if ch == "CNI-ADMIN" { + foundAdmin = true + } else if ch == "CNI-FORWARD" { + foundPriv = true + } + } + return foundAdmin, foundPriv +} + +func findForwardJumpRules(rules []string) (bool, bool) { + var foundAdmin, foundPriv bool + for _, rule := range rules { + if strings.Contains(rule, "-j CNI-ADMIN") { + foundAdmin = true + } else if strings.Contains(rule, "-j CNI-FORWARD") { + foundPriv = true + } + } + return foundAdmin, foundPriv +} + +func findForwardAllowRules(rules []string, ip string) (bool, bool) { + var foundOne, foundTwo bool + for _, rule := range rules { + if !strings.HasSuffix(rule, "-j ACCEPT") { + continue + } + if strings.Contains(rule, fmt.Sprintf(" -s %s ", ip)) { + foundOne = true + } else if strings.Contains(rule, fmt.Sprintf(" -d %s ", ip)) && strings.Contains(rule, "RELATED,ESTABLISHED") { + foundTwo = true + } + } + return foundOne, foundTwo +} + +func getPrevResult(bytes []byte) *current.Result { + type TmpConf struct { + types.NetConf + RawPrevResult map[string]interface{} `json:"prevResult,omitempty"` + PrevResult *current.Result `json:"-"` + } + + conf := &TmpConf{} + err := json.Unmarshal(bytes, conf) + Expect(err).NotTo(HaveOccurred()) + if conf.RawPrevResult == nil { + return nil + } + + resultBytes, err := json.Marshal(conf.RawPrevResult) + Expect(err).NotTo(HaveOccurred()) + res, err := version.NewResult(conf.CNIVersion, resultBytes) + Expect(err).NotTo(HaveOccurred()) + prevResult, err := current.NewResultFromResult(res) + Expect(err).NotTo(HaveOccurred()) + + return prevResult +} + +func validateFullRuleset(bytes []byte) { + prevResult := getPrevResult(bytes) + + for _, ip := range prevResult.IPs { + ipt, err := iptables.NewWithProtocol(protoForIP(ip.Address)) + Expect(err).NotTo(HaveOccurred()) + + // Ensure chains + chains, err := ipt.ListChains("filter") + Expect(err).NotTo(HaveOccurred()) + foundAdmin, foundPriv := findChains(chains) + Expect(foundAdmin).To(Equal(true)) + Expect(foundPriv).To(Equal(true)) + + // Look for the FORWARD chain jump rules to our custom chains + rules, err := ipt.List("filter", "FORWARD") + Expect(err).NotTo(HaveOccurred()) + Expect(len(rules)).Should(BeNumerically(">", 1)) + _, foundPriv = findForwardJumpRules(rules) + Expect(foundPriv).To(Equal(true)) + + // Look for the allow rules in our custom FORWARD chain + rules, err = ipt.List("filter", "CNI-FORWARD") + Expect(err).NotTo(HaveOccurred()) + Expect(len(rules)).Should(BeNumerically(">", 1)) + foundAdmin, _ = findForwardJumpRules(rules) + Expect(foundAdmin).To(Equal(true)) + + // Look for the IP allow rules + foundOne, foundTwo := findForwardAllowRules(rules, ipString(ip.Address)) + Expect(foundOne).To(Equal(true)) + Expect(foundTwo).To(Equal(true)) + } +} + +func validateCleanedUp(bytes []byte) { + prevResult := getPrevResult(bytes) + + for _, ip := range prevResult.IPs { + ipt, err := iptables.NewWithProtocol(protoForIP(ip.Address)) + Expect(err).NotTo(HaveOccurred()) + + // Our private and admin chains don't get cleaned up + chains, err := ipt.ListChains("filter") + Expect(err).NotTo(HaveOccurred()) + foundAdmin, foundPriv := findChains(chains) + Expect(foundAdmin).To(Equal(true)) + Expect(foundPriv).To(Equal(true)) + + // Look for the FORWARD chain jump rules to our custom chains + rules, err := ipt.List("filter", "FORWARD") + Expect(err).NotTo(HaveOccurred()) + _, foundPriv = findForwardJumpRules(rules) + Expect(foundPriv).To(Equal(true)) + + // Look for the allow rules in our custom FORWARD chain + rules, err = ipt.List("filter", "CNI-FORWARD") + Expect(err).NotTo(HaveOccurred()) + foundAdmin, _ = findForwardJumpRules(rules) + Expect(foundAdmin).To(Equal(true)) + + // Expect no IP address rules for this IP + foundOne, foundTwo := findForwardAllowRules(rules, ipString(ip.Address)) + Expect(foundOne).To(Equal(false)) + Expect(foundTwo).To(Equal(false)) + } +} + +var _ = Describe("firewall plugin iptables backend", func() { + var originalNS, targetNS ns.NetNS + const IFNAME string = "dummy0" + + fullConf := []byte(`{ + "name": "test", + "type": "firewall", + "backend": "iptables", + "ifName": "dummy0", + "cniVersion": "0.3.1", + "prevResult": { + "interfaces": [ + {"name": "dummy0"} + ], + "ips": [ + { + "version": "4", + "address": "10.0.0.2/24", + "interface": 0 + }, + { + "version": "6", + "address": "2001:db8:1:2::1/64", + "interface": 0 + } + ] + } + }`) + + BeforeEach(func() { + // Create a new NetNS so we don't modify the host + var err error + originalNS, err = testutils.NewNS() + Expect(err).NotTo(HaveOccurred()) + + err = originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + err = netlink.LinkAdd(&netlink.Dummy{ + LinkAttrs: netlink.LinkAttrs{ + Name: IFNAME, + }, + }) + Expect(err).NotTo(HaveOccurred()) + _, err = netlink.LinkByName(IFNAME) + Expect(err).NotTo(HaveOccurred()) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + + targetNS, err = testutils.NewNS() + Expect(err).NotTo(HaveOccurred()) + }) + + AfterEach(func() { + Expect(originalNS.Close()).To(Succeed()) + Expect(targetNS.Close()).To(Succeed()) + }) + + It("passes prevResult through unchanged", func() { + args := &skel.CmdArgs{ + ContainerID: "dummy", + Netns: targetNS.Path(), + IfName: IFNAME, + StdinData: fullConf, + } + + err := originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + r, _, err := testutils.CmdAdd(targetNS.Path(), args.ContainerID, IFNAME, fullConf, func() error { + return cmdAdd(args) + }) + Expect(err).NotTo(HaveOccurred()) + + result, err := current.GetResult(r) + Expect(err).NotTo(HaveOccurred()) + + Expect(len(result.Interfaces)).To(Equal(1)) + Expect(result.Interfaces[0].Name).To(Equal(IFNAME)) + Expect(len(result.IPs)).To(Equal(2)) + Expect(result.IPs[0].Address.String()).To(Equal("10.0.0.2/24")) + Expect(result.IPs[1].Address.String()).To(Equal("2001:db8:1:2::1/64")) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + }) + + It("installs the right iptables rules on the host", func() { + args := &skel.CmdArgs{ + ContainerID: "dummy", + Netns: targetNS.Path(), + IfName: IFNAME, + StdinData: fullConf, + } + + err := originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + _, _, err := testutils.CmdAdd(targetNS.Path(), args.ContainerID, IFNAME, fullConf, func() error { + return cmdAdd(args) + }) + Expect(err).NotTo(HaveOccurred()) + + validateFullRuleset(fullConf) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + }) + + It("correctly handles a custom IptablesAdminChainName", func() { + conf := []byte(`{ + "name": "test", + "type": "firewall", + "backend": "iptables", + "ifName": "dummy0", + "cniVersion": "0.3.1", + "iptablesAdminChainName": "CNI-foobar", + "prevResult": { + "interfaces": [ + {"name": "dummy0"} + ], + "ips": [ + { + "version": "4", + "address": "10.0.0.2/24", + "interface": 0 + }, + { + "version": "6", + "address": "2001:db8:1:2::1/64", + "interface": 0 + } + ] + } +}`) + + args := &skel.CmdArgs{ + ContainerID: "dummy", + Netns: targetNS.Path(), + IfName: IFNAME, + StdinData: conf, + } + + err := originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + _, _, err := testutils.CmdAdd(targetNS.Path(), args.ContainerID, IFNAME, conf, func() error { + return cmdAdd(args) + }) + Expect(err).NotTo(HaveOccurred()) + + var ipt *iptables.IPTables + for _, proto := range []iptables.Protocol{iptables.ProtocolIPv4, iptables.ProtocolIPv6} { + ipt, err = iptables.NewWithProtocol(proto) + Expect(err).NotTo(HaveOccurred()) + + // Ensure custom admin chain name + chains, err := ipt.ListChains("filter") + Expect(err).NotTo(HaveOccurred()) + var foundAdmin bool + for _, ch := range chains { + if ch == "CNI-foobar" { + foundAdmin = true + } + } + Expect(foundAdmin).To(Equal(true)) + } + + return nil + }) + Expect(err).NotTo(HaveOccurred()) + }) + + It("cleans up on delete", func() { + args := &skel.CmdArgs{ + ContainerID: "dummy", + Netns: targetNS.Path(), + IfName: IFNAME, + StdinData: fullConf, + } + + err := originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + _, _, err := testutils.CmdAdd(targetNS.Path(), args.ContainerID, IFNAME, fullConf, func() error { + return cmdAdd(args) + }) + Expect(err).NotTo(HaveOccurred()) + validateFullRuleset(fullConf) + + err = testutils.CmdDel(targetNS.Path(), args.ContainerID, IFNAME, func() error { + return cmdDel(args) + }) + Expect(err).NotTo(HaveOccurred()) + validateCleanedUp(fullConf) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + }) + + It("installs the right iptables rules on the host v4.0.x and check is successful", func() { + args := &skel.CmdArgs{ + ContainerID: "dummy", + Netns: targetNS.Path(), + IfName: IFNAME, + StdinData: fullConf, + } + + err := originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + _, _, err := testutils.CmdAdd(targetNS.Path(), args.ContainerID, IFNAME, fullConf, func() error { + return cmdAdd(args) + }) + Expect(err).NotTo(HaveOccurred()) + + validateFullRuleset(fullConf) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + }) + + It("cleans up on delete v4.0.x", func() { + args := &skel.CmdArgs{ + ContainerID: "dummy", + Netns: targetNS.Path(), + IfName: IFNAME, + StdinData: fullConf, + } + + err := originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + _, _, err := testutils.CmdAdd(targetNS.Path(), args.ContainerID, IFNAME, fullConf, func() error { + return cmdAdd(args) + }) + Expect(err).NotTo(HaveOccurred()) + validateFullRuleset(fullConf) + + err = testutils.CmdDel(targetNS.Path(), args.ContainerID, IFNAME, func() error { + return cmdDel(args) + }) + Expect(err).NotTo(HaveOccurred()) + validateCleanedUp(fullConf) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + }) +}) + +var _ = Describe("firewall plugin iptables backend v0.4.x", func() { + var originalNS, targetNS ns.NetNS + const IFNAME string = "dummy0" + + fullConf := []byte(`{ + "name": "test", + "type": "firewall", + "backend": "iptables", + "ifName": "dummy0", + "cniVersion": "0.4.0", + "prevResult": { + "interfaces": [ + {"name": "dummy0"} + ], + "ips": [ + { + "version": "4", + "address": "10.0.0.2/24", + "interface": 0 + }, + { + "version": "6", + "address": "2001:db8:1:2::1/64", + "interface": 0 + } + ] + } + }`) + + BeforeEach(func() { + // Create a new NetNS so we don't modify the host + var err error + originalNS, err = testutils.NewNS() + Expect(err).NotTo(HaveOccurred()) + + err = originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + err = netlink.LinkAdd(&netlink.Dummy{ + LinkAttrs: netlink.LinkAttrs{ + Name: IFNAME, + }, + }) + Expect(err).NotTo(HaveOccurred()) + _, err = netlink.LinkByName(IFNAME) + Expect(err).NotTo(HaveOccurred()) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + + targetNS, err = testutils.NewNS() + Expect(err).NotTo(HaveOccurred()) + }) + + AfterEach(func() { + Expect(originalNS.Close()).To(Succeed()) + Expect(targetNS.Close()).To(Succeed()) + }) + + It("installs iptables rules, Check rules then cleans up on delete using v4.0.x", func() { + args := &skel.CmdArgs{ + ContainerID: "dummy", + Netns: targetNS.Path(), + IfName: IFNAME, + StdinData: fullConf, + } + + err := originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + r, _, err := testutils.CmdAddWithArgs(args, func() error { + return cmdAdd(args) + }) + Expect(err).NotTo(HaveOccurred()) + + _, err = current.GetResult(r) + Expect(err).NotTo(HaveOccurred()) + + err = testutils.CmdCheckWithArgs(args, func() error { + return cmdCheck(args) + }) + Expect(err).NotTo(HaveOccurred()) + validateFullRuleset(fullConf) + + err = testutils.CmdDelWithArgs(args, func() error { + return cmdDel(args) + }) + Expect(err).NotTo(HaveOccurred()) + validateCleanedUp(fullConf) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + }) +}) diff --git a/vendor/github.com/containernetworking/plugins/plugins/meta/firewall/firewall_suite_test.go b/vendor/github.com/containernetworking/plugins/plugins/meta/firewall/firewall_suite_test.go new file mode 100644 index 000000000..d3b10e218 --- /dev/null +++ b/vendor/github.com/containernetworking/plugins/plugins/meta/firewall/firewall_suite_test.go @@ -0,0 +1,27 @@ +// Copyright 2017 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 main + +import ( + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + "testing" +) + +func TestFirewall(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "firewall Suite") +} diff --git a/vendor/github.com/containernetworking/plugins/plugins/meta/firewall/firewalld.go b/vendor/github.com/containernetworking/plugins/plugins/meta/firewall/firewalld.go new file mode 100644 index 000000000..ac9328c3f --- /dev/null +++ b/vendor/github.com/containernetworking/plugins/plugins/meta/firewall/firewalld.go @@ -0,0 +1,122 @@ +// Copyright 2018 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 main + +import ( + "fmt" + "strings" + + "github.com/containernetworking/cni/pkg/types/current" + "github.com/godbus/dbus" +) + +const ( + dbusName = "org.freedesktop.DBus" + dbusPath = "/org/freedesktop/DBus" + dbusGetNameOwnerMethod = "GetNameOwner" + + firewalldName = "org.fedoraproject.FirewallD1" + firewalldPath = "/org/fedoraproject/FirewallD1" + firewalldZoneInterface = "org.fedoraproject.FirewallD1.zone" + firewalldAddSourceMethod = "addSource" + firewalldRemoveSourceMethod = "removeSource" + firewalldQuerySourceMethod = "querySource" + + errZoneAlreadySet = "ZONE_ALREADY_SET" +) + +// Only used for testcases to override the D-Bus connection +var testConn *dbus.Conn + +type fwdBackend struct { + conn *dbus.Conn +} + +// fwdBackend implements the FirewallBackend interface +var _ FirewallBackend = &fwdBackend{} + +func getConn() (*dbus.Conn, error) { + if testConn != nil { + return testConn, nil + } + return dbus.SystemBus() +} + +// isFirewalldRunning checks whether firewalld is running. +func isFirewalldRunning() bool { + conn, err := getConn() + if err != nil { + return false + } + + dbusObj := conn.Object(dbusName, dbusPath) + var res string + if err := dbusObj.Call(dbusName+"."+dbusGetNameOwnerMethod, 0, firewalldName).Store(&res); err != nil { + return false + } + + return true +} + +func newFirewalldBackend(conf *FirewallNetConf) (FirewallBackend, error) { + conn, err := getConn() + if err != nil { + return nil, err + } + + backend := &fwdBackend{ + conn: conn, + } + return backend, nil +} + +func (fb *fwdBackend) Add(conf *FirewallNetConf, result *current.Result) error { + for _, ip := range result.IPs { + ipStr := ipString(ip.Address) + // Add a firewalld rule which assigns the given source IP to the given zone + firewalldObj := fb.conn.Object(firewalldName, firewalldPath) + var res string + if err := firewalldObj.Call(firewalldZoneInterface+"."+firewalldAddSourceMethod, 0, conf.FirewalldZone, ipStr).Store(&res); err != nil { + if !strings.Contains(err.Error(), errZoneAlreadySet) { + return fmt.Errorf("failed to add the address %v to %v zone: %v", ipStr, conf.FirewalldZone, err) + } + } + } + return nil +} + +func (fb *fwdBackend) Del(conf *FirewallNetConf, result *current.Result) error { + for _, ip := range result.IPs { + ipStr := ipString(ip.Address) + // Remove firewalld rules which assigned the given source IP to the given zone + firewalldObj := fb.conn.Object(firewalldName, firewalldPath) + var res string + firewalldObj.Call(firewalldZoneInterface+"."+firewalldRemoveSourceMethod, 0, conf.FirewalldZone, ipStr).Store(&res) + } + return nil +} + +func (fb *fwdBackend) Check(conf *FirewallNetConf, result *current.Result) error { + for _, ip := range result.IPs { + ipStr := ipString(ip.Address) + // Check for a firewalld rule for the given source IP to the given zone + firewalldObj := fb.conn.Object(firewalldName, firewalldPath) + var res bool + if err := firewalldObj.Call(firewalldZoneInterface+"."+firewalldQuerySourceMethod, 0, conf.FirewalldZone, ipStr).Store(&res); err != nil { + return fmt.Errorf("failed to find the address %v in %v zone", ipStr, conf.FirewalldZone) + } + } + return nil +} diff --git a/vendor/github.com/containernetworking/plugins/plugins/meta/firewall/iptables.go b/vendor/github.com/containernetworking/plugins/plugins/meta/firewall/iptables.go new file mode 100644 index 000000000..faae35c61 --- /dev/null +++ b/vendor/github.com/containernetworking/plugins/plugins/meta/firewall/iptables.go @@ -0,0 +1,279 @@ +// Copyright 2016 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. + +// This is a "meta-plugin". It reads in its own netconf, it does not create +// any network interface but just changes the network sysctl. + +package main + +import ( + "fmt" + "net" + + "github.com/containernetworking/cni/pkg/types/current" + "github.com/coreos/go-iptables/iptables" +) + +func getPrivChainRules(ip string) [][]string { + var rules [][]string + rules = append(rules, []string{"-d", ip, "-m", "conntrack", "--ctstate", "RELATED,ESTABLISHED", "-j", "ACCEPT"}) + rules = append(rules, []string{"-s", ip, "-j", "ACCEPT"}) + return rules +} + +func ensureChain(ipt *iptables.IPTables, table, chain string) error { + chains, err := ipt.ListChains(table) + if err != nil { + return fmt.Errorf("failed to list iptables chains: %v", err) + } + for _, ch := range chains { + if ch == chain { + return nil + } + } + + return ipt.NewChain(table, chain) +} + +func generateFilterRule(privChainName string) []string { + return []string{"-m", "comment", "--comment", "CNI firewall plugin rules", "-j", privChainName} +} + +func generateAdminRule(adminChainName string) []string { + return []string{"-m", "comment", "--comment", "CNI firewall plugin admin overrides", "-j", adminChainName} +} + +func cleanupRules(ipt *iptables.IPTables, privChainName string, rules [][]string) { + for _, rule := range rules { + ipt.Delete("filter", privChainName, rule...) + } +} + +func ensureFirstChainRule(ipt *iptables.IPTables, chain string, rule []string) error { + exists, err := ipt.Exists("filter", chain, rule...) + if !exists && err == nil { + err = ipt.Insert("filter", chain, 1, rule...) + } + return err +} + +func (ib *iptablesBackend) setupChains(ipt *iptables.IPTables) error { + privRule := generateFilterRule(ib.privChainName) + adminRule := generateFilterRule(ib.adminChainName) + + // Ensure our private chains exist + if err := ensureChain(ipt, "filter", ib.privChainName); err != nil { + return err + } + if err := ensureChain(ipt, "filter", ib.adminChainName); err != nil { + return err + } + + // Ensure our filter rule exists in the forward chain + if err := ensureFirstChainRule(ipt, "FORWARD", privRule); err != nil { + return err + } + + // Ensure our admin override chain rule exists in our private chain + if err := ensureFirstChainRule(ipt, ib.privChainName, adminRule); err != nil { + return err + } + + return nil +} + +func protoForIP(ip net.IPNet) iptables.Protocol { + if ip.IP.To4() != nil { + return iptables.ProtocolIPv4 + } + return iptables.ProtocolIPv6 +} + +func (ib *iptablesBackend) addRules(conf *FirewallNetConf, result *current.Result, ipt *iptables.IPTables, proto iptables.Protocol) error { + rules := make([][]string, 0) + for _, ip := range result.IPs { + if protoForIP(ip.Address) == proto { + rules = append(rules, getPrivChainRules(ipString(ip.Address))...) + } + } + + if len(rules) > 0 { + if err := ib.setupChains(ipt); err != nil { + return err + } + + // Clean up on any errors + var err error + defer func() { + if err != nil { + cleanupRules(ipt, ib.privChainName, rules) + } + }() + + for _, rule := range rules { + err = ipt.AppendUnique("filter", ib.privChainName, rule...) + if err != nil { + return err + } + } + } + + return nil +} + +func (ib *iptablesBackend) delRules(conf *FirewallNetConf, result *current.Result, ipt *iptables.IPTables, proto iptables.Protocol) error { + rules := make([][]string, 0) + for _, ip := range result.IPs { + if protoForIP(ip.Address) == proto { + rules = append(rules, getPrivChainRules(ipString(ip.Address))...) + } + } + + if len(rules) > 0 { + cleanupRules(ipt, ib.privChainName, rules) + } + + return nil +} + +func (ib *iptablesBackend) checkRules(conf *FirewallNetConf, result *current.Result, ipt *iptables.IPTables, proto iptables.Protocol) error { + rules := make([][]string, 0) + for _, ip := range result.IPs { + if protoForIP(ip.Address) == proto { + rules = append(rules, getPrivChainRules(ipString(ip.Address))...) + } + } + + if len(rules) <= 0 { + return nil + } + + // Ensure our private chains exist + if err := ensureChain(ipt, "filter", ib.privChainName); err != nil { + return err + } + if err := ensureChain(ipt, "filter", ib.adminChainName); err != nil { + return err + } + + // Ensure our filter rule exists in the forward chain + privRule := generateFilterRule(ib.privChainName) + privExists, err := ipt.Exists("filter", "FORWARD", privRule...) + if err != nil { + return err + } + if !privExists { + return fmt.Errorf("expected %v rule %v not found", "FORWARD", privRule) + } + + // Ensure our admin override chain rule exists in our private chain + adminRule := generateFilterRule(ib.adminChainName) + adminExists, err := ipt.Exists("filter", ib.privChainName, adminRule...) + if err != nil { + return err + } + if !adminExists { + return fmt.Errorf("expected %v rule %v not found", ib.privChainName, adminRule) + } + + // ensure rules for this IP address exist + for _, rule := range rules { + // Ensure our rule exists in our private chain + exists, err := ipt.Exists("filter", ib.privChainName, rule...) + if err != nil { + return err + } + if !exists { + return fmt.Errorf("expected rule %v not found", rule) + } + } + + return nil +} + +func findProtos(conf *FirewallNetConf) []iptables.Protocol { + protos := []iptables.Protocol{iptables.ProtocolIPv4, iptables.ProtocolIPv6} + if conf.PrevResult != nil { + // If PrevResult is given, scan all IP addresses to figure out + // which IP versions to use + protos = []iptables.Protocol{} + result, _ := current.NewResultFromResult(conf.PrevResult) + for _, addr := range result.IPs { + if addr.Address.IP.To4() != nil { + protos = append(protos, iptables.ProtocolIPv4) + } else { + protos = append(protos, iptables.ProtocolIPv6) + } + } + } + return protos +} + +type iptablesBackend struct { + protos map[iptables.Protocol]*iptables.IPTables + privChainName string + adminChainName string + ifName string +} + +// iptablesBackend implements the FirewallBackend interface +var _ FirewallBackend = &iptablesBackend{} + +func newIptablesBackend(conf *FirewallNetConf) (FirewallBackend, error) { + adminChainName := conf.IptablesAdminChainName + if adminChainName == "" { + adminChainName = "CNI-ADMIN" + } + + backend := &iptablesBackend{ + privChainName: "CNI-FORWARD", + adminChainName: adminChainName, + protos: make(map[iptables.Protocol]*iptables.IPTables), + } + + for _, proto := range []iptables.Protocol{iptables.ProtocolIPv4, iptables.ProtocolIPv6} { + ipt, err := iptables.NewWithProtocol(proto) + if err != nil { + return nil, fmt.Errorf("could not initialize iptables protocol %v: %v", proto, err) + } + backend.protos[proto] = ipt + } + + return backend, nil +} + +func (ib *iptablesBackend) Add(conf *FirewallNetConf, result *current.Result) error { + for proto, ipt := range ib.protos { + if err := ib.addRules(conf, result, ipt, proto); err != nil { + return err + } + } + return nil +} + +func (ib *iptablesBackend) Del(conf *FirewallNetConf, result *current.Result) error { + for proto, ipt := range ib.protos { + ib.delRules(conf, result, ipt, proto) + } + return nil +} + +func (ib *iptablesBackend) Check(conf *FirewallNetConf, result *current.Result) error { + for proto, ipt := range ib.protos { + if err := ib.checkRules(conf, result, ipt, proto); err != nil { + return err + } + } + return nil +} diff --git a/vendor/github.com/containernetworking/plugins/plugins/meta/flannel/README.md b/vendor/github.com/containernetworking/plugins/plugins/meta/flannel/README.md index 0efb69059..da7961b90 100644 --- a/vendor/github.com/containernetworking/plugins/plugins/meta/flannel/README.md +++ b/vendor/github.com/containernetworking/plugins/plugins/meta/flannel/README.md @@ -86,3 +86,50 @@ flannel plugin will set the following fields in the delegated plugin configurati * `mtu`: `$FLANNEL_MTU` Additionally, for the bridge plugin, `isGateway` will be set to `true`, if not present. + +## Windows Support (Experimental) +This plugin supports delegating to the windows CNI plugins (overlay.exe, l2bridge.exe) to work in conjunction with [Flannel on Windows](https://github.com/coreos/flannel/issues/833). +Flannel sets up an [HNS Network](https://docs.microsoft.com/en-us/virtualization/windowscontainers/manage-containers/container-networking) in L2Bridge mode for host-gw and in Overlay mode for vxlan. + +The following fields must be set in the delegated plugin configuration: +* `name` (string, required): the name of the network (must match the name in Flannel config / name of the HNS network) +* `type` (string, optional): set to `win-l2bridge` by default. Can be set to `win-overlay` or other custom windows CNI +* `ipMasq`: the inverse of `$FLANNEL_IPMASQ` +* `endpointMacPrefix` (string, optional): required for `win-overlay` mode, set to the MAC prefix configured for Flannel +* `clusterNetworkPrefix` (string, optional): required for `win-l2bridge` mode, setup NAT if `ipMasq` is set to true + +For `win-l2bridge`, the Flannel CNI plugin will set: +* `ipam`: "host-local" type will be used with "subnet" set to `$FLANNEL_SUBNET` and gateway as the .2 address in `$FLANNEL_NETWORK` + +For `win-overlay`, the Flannel CNI plugin will set: +* `ipam`: "host-local" type will be used with "subnet" set to `$FLANNEL_SUBNET` and gateway as the .1 address in `$FLANNEL_NETWORK` + +If IPMASQ is true, the Flannel CNI plugin will setup an OutBoundNAT policy and add FLANNEL_SUBNET to any existing exclusions. + +All other delegate config e.g. other HNS endpoint policies in AdditionalArgs will be passed to WINCNI as-is. + +Example VXLAN Flannel CNI config +``` +{ + "name": "mynet", + "type": "flannel", + "delegate": { + "type": "win-overlay", + "endpointMacPrefix": "0E-2A" + } +} +``` + +For this example, Flannel CNI would generate the following config to delegate to the windows CNI when FLANNEL_NETWORK=10.244.0.0/16, FLANNEL_SUBNET=10.244.1.0/24 and IPMASQ=true +``` +{ + "name": "mynet", + "type": "win-overlay", + "endpointMacPrefix": "0E-2A", + "ipMasq": true, + "ipam": { + "subnet": "10.244.1.0/24", + "type": "host-local" + } +} +``` \ No newline at end of file diff --git a/vendor/github.com/containernetworking/plugins/plugins/meta/flannel/flannel.go b/vendor/github.com/containernetworking/plugins/plugins/meta/flannel/flannel.go index 6ec0e8fcf..56f1de0c1 100644 --- a/vendor/github.com/containernetworking/plugins/plugins/meta/flannel/flannel.go +++ b/vendor/github.com/containernetworking/plugins/plugins/meta/flannel/flannel.go @@ -20,6 +20,7 @@ package main import ( "bufio" + "context" "encoding/json" "fmt" "io/ioutil" @@ -33,6 +34,8 @@ import ( "github.com/containernetworking/cni/pkg/skel" "github.com/containernetworking/cni/pkg/types" "github.com/containernetworking/cni/pkg/version" + + bv "github.com/containernetworking/plugins/pkg/utils/buildversion" ) const ( @@ -42,9 +45,11 @@ const ( type NetConf struct { types.NetConf - SubnetFile string `json:"subnetFile"` - DataDir string `json:"dataDir"` - Delegate map[string]interface{} `json:"delegate"` + + SubnetFile string `json:"subnetFile"` + DataDir string `json:"dataDir"` + Delegate map[string]interface{} `json:"delegate"` + RuntimeConfig map[string]interface{} `json:"runtimeConfig,omitempty"` } type subnetEnv struct { @@ -80,6 +85,7 @@ func loadFlannelNetConf(bytes []byte) (*NetConf, error) { if err := json.Unmarshal(bytes, n); err != nil { return nil, fmt.Errorf("failed to load netconf: %v", err) } + return n, nil } @@ -159,7 +165,7 @@ func delegateAdd(cid, dataDir string, netconf map[string]interface{}) error { return err } - result, err := invoke.DelegateAdd(netconf["type"].(string), netconfBytes) + result, err := invoke.DelegateAdd(context.TODO(), netconf["type"].(string), netconfBytes, nil) if err != nil { return err } @@ -202,43 +208,11 @@ func cmdAdd(args *skel.CmdArgs) error { } } - n.Delegate["name"] = n.Name - - if !hasKey(n.Delegate, "type") { - n.Delegate["type"] = "bridge" - } - - if !hasKey(n.Delegate, "ipMasq") { - // if flannel is not doing ipmasq, we should - ipmasq := !*fenv.ipmasq - n.Delegate["ipMasq"] = ipmasq - } - - if !hasKey(n.Delegate, "mtu") { - mtu := fenv.mtu - n.Delegate["mtu"] = mtu - } - - if n.Delegate["type"].(string) == "bridge" { - if !hasKey(n.Delegate, "isGateway") { - n.Delegate["isGateway"] = true - } - } - if n.CNIVersion != "" { - n.Delegate["cniVersion"] = n.CNIVersion - } - - n.Delegate["ipam"] = map[string]interface{}{ - "type": "host-local", - "subnet": fenv.sn.String(), - "routes": []types.Route{ - types.Route{ - Dst: *fenv.nw, - }, - }, + if n.RuntimeConfig != nil { + n.Delegate["runtimeConfig"] = n.RuntimeConfig } - return delegateAdd(args.ContainerID, n.DataDir, n.Delegate) + return doCmdAdd(args, n, fenv) } func cmdDel(args *skel.CmdArgs) error { @@ -247,23 +221,21 @@ func cmdDel(args *skel.CmdArgs) error { return err } - netconfBytes, err := consumeScratchNetConf(args.ContainerID, nc.DataDir) - if err != nil { - if os.IsNotExist(err) { - // Per spec should ignore error if resources are missing / already removed - return nil + if nc.RuntimeConfig != nil { + if nc.Delegate == nil { + nc.Delegate = make(map[string]interface{}) } - return err - } - - n := &types.NetConf{} - if err = json.Unmarshal(netconfBytes, n); err != nil { - return fmt.Errorf("failed to parse netconf: %v", err) + nc.Delegate["runtimeConfig"] = nc.RuntimeConfig } - return invoke.DelegateDel(n.Type, netconfBytes) + return doCmdDel(args, nc) } func main() { - skel.PluginMain(cmdAdd, cmdDel, version.All) + skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("flannel")) +} + +func cmdCheck(args *skel.CmdArgs) error { + // TODO: implement + return nil } diff --git a/vendor/github.com/containernetworking/plugins/plugins/meta/flannel/flannel_linux.go b/vendor/github.com/containernetworking/plugins/plugins/meta/flannel/flannel_linux.go new file mode 100644 index 000000000..b4ee5c09c --- /dev/null +++ b/vendor/github.com/containernetworking/plugins/plugins/meta/flannel/flannel_linux.go @@ -0,0 +1,87 @@ +// Copyright 2018 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. + +// This is a "meta-plugin". It reads in its own netconf, combines it with +// the data from flannel generated subnet file and then invokes a plugin +// like bridge or ipvlan to do the real work. + +package main + +import ( + "context" + "encoding/json" + "fmt" + "github.com/containernetworking/cni/pkg/invoke" + "github.com/containernetworking/cni/pkg/skel" + "github.com/containernetworking/cni/pkg/types" + "os" +) + +func doCmdAdd(args *skel.CmdArgs, n *NetConf, fenv *subnetEnv) error { + n.Delegate["name"] = n.Name + + if !hasKey(n.Delegate, "type") { + n.Delegate["type"] = "bridge" + } + + if !hasKey(n.Delegate, "ipMasq") { + // if flannel is not doing ipmasq, we should + ipmasq := !*fenv.ipmasq + n.Delegate["ipMasq"] = ipmasq + } + + if !hasKey(n.Delegate, "mtu") { + mtu := fenv.mtu + n.Delegate["mtu"] = mtu + } + + if n.Delegate["type"].(string) == "bridge" { + if !hasKey(n.Delegate, "isGateway") { + n.Delegate["isGateway"] = true + } + } + if n.CNIVersion != "" { + n.Delegate["cniVersion"] = n.CNIVersion + } + + n.Delegate["ipam"] = map[string]interface{}{ + "type": "host-local", + "subnet": fenv.sn.String(), + "routes": []types.Route{ + { + Dst: *fenv.nw, + }, + }, + } + + return delegateAdd(args.ContainerID, n.DataDir, n.Delegate) +} + +func doCmdDel(args *skel.CmdArgs, n *NetConf) error { + netconfBytes, err := consumeScratchNetConf(args.ContainerID, n.DataDir) + if err != nil { + if os.IsNotExist(err) { + // Per spec should ignore error if resources are missing / already removed + return nil + } + return err + } + + nc := &types.NetConf{} + if err = json.Unmarshal(netconfBytes, nc); err != nil { + return fmt.Errorf("failed to parse netconf: %v", err) + } + + return invoke.DelegateDel(context.TODO(), nc.Type, netconfBytes, nil) +} diff --git a/vendor/github.com/containernetworking/plugins/plugins/meta/flannel/flannel_windows.go b/vendor/github.com/containernetworking/plugins/plugins/meta/flannel/flannel_windows.go new file mode 100644 index 000000000..b482acbb0 --- /dev/null +++ b/vendor/github.com/containernetworking/plugins/plugins/meta/flannel/flannel_windows.go @@ -0,0 +1,74 @@ +// Copyright 2018 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. + +// This is a "meta-plugin". It reads in its own netconf, combines it with +// the data from flannel generated subnet file and then invokes a plugin +// like bridge or ipvlan to do the real work. + +package main + +import ( + "context" + "encoding/json" + "fmt" + "github.com/containernetworking/cni/pkg/invoke" + "github.com/containernetworking/cni/pkg/skel" + "github.com/containernetworking/cni/pkg/types" + "github.com/containernetworking/cni/pkg/types/020" + "github.com/containernetworking/plugins/pkg/hns" + "os" +) + +func doCmdAdd(args *skel.CmdArgs, n *NetConf, fenv *subnetEnv) error { + n.Delegate["name"] = n.Name + + if !hasKey(n.Delegate, "type") { + n.Delegate["type"] = "win-bridge" + } + + // if flannel needs ipmasq - get the plugin to configure it + // (this is the opposite of how linux works - on linux the flannel daemon configure ipmasq) + n.Delegate["ipMasq"] = *fenv.ipmasq + n.Delegate["ipMasqNetwork"] = fenv.nw.String() + + n.Delegate["cniVersion"] = types020.ImplementedSpecVersion + if len(n.CNIVersion) != 0 { + n.Delegate["cniVersion"] = n.CNIVersion + } + + n.Delegate["ipam"] = map[string]interface{}{ + "type": "host-local", + "subnet": fenv.sn.String(), + } + + return delegateAdd(hns.GetSandboxContainerID(args.ContainerID, args.Netns), n.DataDir, n.Delegate) +} + +func doCmdDel(args *skel.CmdArgs, n *NetConf) error { + netconfBytes, err := consumeScratchNetConf(hns.GetSandboxContainerID(args.ContainerID, args.Netns), n.DataDir) + if err != nil { + if os.IsNotExist(err) { + // Per spec should ignore error if resources are missing / already removed + return nil + } + return err + } + + nc := &types.NetConf{} + if err = json.Unmarshal(netconfBytes, nc); err != nil { + return fmt.Errorf("failed to parse netconf: %v", err) + } + + return invoke.DelegateDel(context.TODO(), nc.Type, netconfBytes, nil) +} diff --git a/vendor/github.com/containernetworking/plugins/plugins/meta/portmap/chain.go b/vendor/github.com/containernetworking/plugins/plugins/meta/portmap/chain.go index 5ebfe6a31..bca8214a9 100644 --- a/vendor/github.com/containernetworking/plugins/plugins/meta/portmap/chain.go +++ b/vendor/github.com/containernetworking/plugins/plugins/meta/portmap/chain.go @@ -19,7 +19,7 @@ import ( "strings" "github.com/coreos/go-iptables/iptables" - shellwords "github.com/mattn/go-shellwords" + "github.com/mattn/go-shellwords" ) type chain struct { @@ -29,6 +29,8 @@ type chain struct { entryRules [][]string // the rules that "point" to this chain rules [][]string // the rules this chain contains + + prependEntry bool // whether or not the entry rules should be prepended } // setup idempotently creates the chain. It will not error if the chain exists. @@ -45,19 +47,19 @@ func (c *chain) setup(ipt *iptables.IPTables) error { } // Add the rules to the chain - for i := len(c.rules) - 1; i >= 0; i-- { - if err := prependUnique(ipt, c.table, c.name, c.rules[i]); err != nil { + for _, rule := range c.rules { + if err := insertUnique(ipt, c.table, c.name, false, rule); err != nil { return err } } // Add the entry rules to the entry chains for _, entryChain := range c.entryChains { - for i := len(c.entryRules) - 1; i >= 0; i-- { + for _, rule := range c.entryRules { r := []string{} - r = append(r, c.entryRules[i]...) + r = append(r, rule...) r = append(r, "-j", c.name) - if err := prependUnique(ipt, c.table, entryChain, r); err != nil { + if err := insertUnique(ipt, c.table, entryChain, c.prependEntry, r); err != nil { return err } } @@ -78,7 +80,7 @@ func (c *chain) teardown(ipt *iptables.IPTables) error { for _, entryChain := range c.entryChains { entryChainRules, err := ipt.List(c.table, entryChain) - if err != nil { + if err != nil || len(entryChainRules) < 1 { // Swallow error here - probably the chain doesn't exist. // If we miss something the deletion will fail continue @@ -105,8 +107,9 @@ func (c *chain) teardown(ipt *iptables.IPTables) error { return nil } -// prependUnique will prepend a rule to a chain, if it does not already exist -func prependUnique(ipt *iptables.IPTables, table, chain string, rule []string) error { +// insertUnique will add a rule to a chain if it does not already exist. +// By default the rule is appended, unless prepend is true. +func insertUnique(ipt *iptables.IPTables, table, chain string, prepend bool, rule []string) error { exists, err := ipt.Exists(table, chain, rule...) if err != nil { return err @@ -115,7 +118,11 @@ func prependUnique(ipt *iptables.IPTables, table, chain string, rule []string) e return nil } - return ipt.Insert(table, chain, 1, rule...) + if prepend { + return ipt.Insert(table, chain, 1, rule...) + } else { + return ipt.Append(table, chain, rule...) + } } func chainExists(ipt *iptables.IPTables, tableName, chainName string) (bool, error) { @@ -131,3 +138,44 @@ func chainExists(ipt *iptables.IPTables, tableName, chainName string) (bool, err } return false, nil } + +// check the chain. +func (c *chain) check(ipt *iptables.IPTables) error { + + exists, err := chainExists(ipt, c.table, c.name) + if err != nil { + return err + } + if !exists { + return fmt.Errorf("chain %s not found in iptables table %s", c.name, c.table) + } + + for i := len(c.rules) - 1; i >= 0; i-- { + match := checkRule(ipt, c.table, c.name, c.rules[i]) + if !match { + return fmt.Errorf("rule %s in chain %s not found in table %s", c.rules, c.name, c.table) + } + } + + for _, entryChain := range c.entryChains { + for i := len(c.entryRules) - 1; i >= 0; i-- { + r := []string{} + r = append(r, c.entryRules[i]...) + r = append(r, "-j", c.name) + matchEntryChain := checkRule(ipt, c.table, entryChain, r) + if !matchEntryChain { + return fmt.Errorf("rule %s in chain %s not found in table %s", c.entryRules, entryChain, c.table) + } + } + } + + return nil +} + +func checkRule(ipt *iptables.IPTables, table, chain string, rule []string) bool { + exists, err := ipt.Exists(table, chain, rule...) + if err != nil { + return false + } + return exists +} diff --git a/vendor/github.com/containernetworking/plugins/plugins/meta/portmap/chain_test.go b/vendor/github.com/containernetworking/plugins/plugins/meta/portmap/chain_test.go index e5f575e2b..93e4be13e 100644 --- a/vendor/github.com/containernetworking/plugins/plugins/meta/portmap/chain_test.go +++ b/vendor/github.com/containernetworking/plugins/plugins/meta/portmap/chain_test.go @@ -1,4 +1,4 @@ -// Copyright 2017 CNI authors +// Copyright 2017-2018 CNI authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -117,8 +117,8 @@ var _ = Describe("chain tests", func() { Expect(err).NotTo(HaveOccurred()) Expect(haveRules).To(Equal([]string{ "-N " + tlChainName, - "-A " + tlChainName + " -d 203.0.113.1/32 -j " + testChain.name, "-A " + tlChainName + ` -m comment --comment "canary value" -j ACCEPT`, + "-A " + tlChainName + " -d 203.0.113.1/32 -j " + testChain.name, })) // Check that the chain and rule was created @@ -178,6 +178,7 @@ var _ = Describe("chain tests", func() { Expect(err).NotTo(HaveOccurred()) chains, err := ipt.ListChains(TABLE) + Expect(err).NotTo(HaveOccurred()) for _, chain := range chains { if chain == testChain.name { Fail("Chain was not deleted") @@ -187,6 +188,7 @@ var _ = Describe("chain tests", func() { err = testChain.teardown(ipt) Expect(err).NotTo(HaveOccurred()) chains, err = ipt.ListChains(TABLE) + Expect(err).NotTo(HaveOccurred()) for _, chain := range chains { if chain == testChain.name { Fail("Chain was not deleted") diff --git a/vendor/github.com/containernetworking/plugins/plugins/meta/portmap/main.go b/vendor/github.com/containernetworking/plugins/plugins/meta/portmap/main.go index dfc52994a..1df147d95 100644 --- a/vendor/github.com/containernetworking/plugins/plugins/meta/portmap/main.go +++ b/vendor/github.com/containernetworking/plugins/plugins/meta/portmap/main.go @@ -34,6 +34,8 @@ import ( "github.com/containernetworking/cni/pkg/types" "github.com/containernetworking/cni/pkg/types/current" "github.com/containernetworking/cni/pkg/version" + + bv "github.com/containernetworking/plugins/pkg/utils/buildversion" ) // PortMapEntry corresponds to a single entry in the port_mappings argument, @@ -55,8 +57,6 @@ type PortMapConf struct { RuntimeConfig struct { PortMaps []PortMapEntry `json:"portMappings,omitempty"` } `json:"runtimeConfig,omitempty"` - RawPrevResult map[string]interface{} `json:"prevResult,omitempty"` - PrevResult *current.Result `json:"-"` // These are fields parsed out of the config or the environment; // included here for convenience @@ -70,7 +70,7 @@ type PortMapConf struct { const DefaultMarkBit = 13 func cmdAdd(args *skel.CmdArgs) error { - netConf, err := parseConfig(args.StdinData, args.IfName) + netConf, _, err := parseConfig(args.StdinData, args.IfName) if err != nil { return fmt.Errorf("failed to parse config: %v", err) } @@ -102,7 +102,7 @@ func cmdAdd(args *skel.CmdArgs) error { } func cmdDel(args *skel.CmdArgs) error { - netConf, err := parseConfig(args.StdinData, args.IfName) + netConf, _, err := parseConfig(args.StdinData, args.IfName) if err != nil { return fmt.Errorf("failed to parse config: %v", err) } @@ -118,31 +118,60 @@ func cmdDel(args *skel.CmdArgs) error { } func main() { - skel.PluginMain(cmdAdd, cmdDel, version.PluginSupports("", "0.1.0", "0.2.0", "0.3.0", version.Current())) + skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("portmap")) +} + +func cmdCheck(args *skel.CmdArgs) error { + conf, result, err := parseConfig(args.StdinData, args.IfName) + if err != nil { + return err + } + + // Ensure we have previous result. + if result == nil { + return fmt.Errorf("Required prevResult missing") + } + + if len(conf.RuntimeConfig.PortMaps) == 0 { + return nil + } + + conf.ContainerID = args.ContainerID + + if conf.ContIPv4 != nil { + if err := checkPorts(conf, conf.ContIPv4); err != nil { + return err + } + } + + if conf.ContIPv6 != nil { + if err := checkPorts(conf, conf.ContIPv6); err != nil { + return err + } + } + + return nil } // parseConfig parses the supplied configuration (and prevResult) from stdin. -func parseConfig(stdin []byte, ifName string) (*PortMapConf, error) { +func parseConfig(stdin []byte, ifName string) (*PortMapConf, *current.Result, error) { conf := PortMapConf{} if err := json.Unmarshal(stdin, &conf); err != nil { - return nil, fmt.Errorf("failed to parse network configuration: %v", err) + return nil, nil, fmt.Errorf("failed to parse network configuration: %v", err) } // Parse previous result. + var result *current.Result if conf.RawPrevResult != nil { - resultBytes, err := json.Marshal(conf.RawPrevResult) - if err != nil { - return nil, fmt.Errorf("could not serialize prevResult: %v", err) + var err error + if err = version.ParsePrevResult(&conf.NetConf); err != nil { + return nil, nil, fmt.Errorf("could not parse prevResult: %v", err) } - res, err := version.NewResult(conf.CNIVersion, resultBytes) - if err != nil { - return nil, fmt.Errorf("could not parse prevResult: %v", err) - } - conf.RawPrevResult = nil - conf.PrevResult, err = current.NewResultFromResult(res) + + result, err = current.NewResultFromResult(conf.PrevResult) if err != nil { - return nil, fmt.Errorf("could not convert result to current version: %v", err) + return nil, nil, fmt.Errorf("could not convert result to current version: %v", err) } } @@ -152,7 +181,7 @@ func parseConfig(stdin []byte, ifName string) (*PortMapConf, error) { } if conf.MarkMasqBit != nil && conf.ExternalSetMarkChain != nil { - return nil, fmt.Errorf("Cannot specify externalSetMarkChain and markMasqBit") + return nil, nil, fmt.Errorf("Cannot specify externalSetMarkChain and markMasqBit") } if conf.MarkMasqBit == nil { @@ -161,21 +190,21 @@ func parseConfig(stdin []byte, ifName string) (*PortMapConf, error) { } if *conf.MarkMasqBit < 0 || *conf.MarkMasqBit > 31 { - return nil, fmt.Errorf("MasqMarkBit must be between 0 and 31") + return nil, nil, fmt.Errorf("MasqMarkBit must be between 0 and 31") } // Reject invalid port numbers for _, pm := range conf.RuntimeConfig.PortMaps { if pm.ContainerPort <= 0 { - return nil, fmt.Errorf("Invalid container port number: %d", pm.ContainerPort) + return nil, nil, fmt.Errorf("Invalid container port number: %d", pm.ContainerPort) } if pm.HostPort <= 0 { - return nil, fmt.Errorf("Invalid host port number: %d", pm.HostPort) + return nil, nil, fmt.Errorf("Invalid host port number: %d", pm.HostPort) } } if conf.PrevResult != nil { - for _, ip := range conf.PrevResult.IPs { + for _, ip := range result.IPs { if ip.Version == "6" && conf.ContIPv6 != nil { continue } else if ip.Version == "4" && conf.ContIPv4 != nil { @@ -186,9 +215,9 @@ func parseConfig(stdin []byte, ifName string) (*PortMapConf, error) { if ip.Interface != nil { intIdx := *ip.Interface if intIdx >= 0 && - intIdx < len(conf.PrevResult.Interfaces) && - (conf.PrevResult.Interfaces[intIdx].Name != ifName || - conf.PrevResult.Interfaces[intIdx].Sandbox == "") { + intIdx < len(result.Interfaces) && + (result.Interfaces[intIdx].Name != ifName || + result.Interfaces[intIdx].Sandbox == "") { continue } } @@ -201,5 +230,5 @@ func parseConfig(stdin []byte, ifName string) (*PortMapConf, error) { } } - return &conf, nil + return &conf, result, nil } diff --git a/vendor/github.com/containernetworking/plugins/plugins/meta/portmap/portmap.go b/vendor/github.com/containernetworking/plugins/plugins/meta/portmap/portmap.go index 870552f63..d73dda208 100644 --- a/vendor/github.com/containernetworking/plugins/plugins/meta/portmap/portmap.go +++ b/vendor/github.com/containernetworking/plugins/plugins/meta/portmap/portmap.go @@ -111,6 +111,46 @@ func forwardPorts(config *PortMapConf, containerIP net.IP) error { return nil } +func checkPorts(config *PortMapConf, containerIP net.IP) error { + + dnatChain := genDnatChain(config.Name, config.ContainerID) + fillDnatRules(&dnatChain, config, containerIP) + + ip4t := maybeGetIptables(false) + ip6t := maybeGetIptables(true) + if ip4t == nil && ip6t == nil { + return fmt.Errorf("neither iptables nor ip6tables usable") + } + + if ip4t != nil { + exists, err := chainExists(ip4t, dnatChain.table, dnatChain.name) + if err != nil { + return err + } + if !exists { + return err + } + if err := dnatChain.check(ip4t); err != nil { + return fmt.Errorf("could not check ipv4 dnat: %v", err) + } + } + + if ip6t != nil { + exists, err := chainExists(ip6t, dnatChain.table, dnatChain.name) + if err != nil { + return err + } + if !exists { + return err + } + if err := dnatChain.check(ip6t); err != nil { + return fmt.Errorf("could not check ipv6 dnat: %v", err) + } + } + + return nil +} + // genToplevelDnatChain creates the top-level summary chain that we'll // add our chain to. This is easy, because creating chains is idempotent. // IMPORTANT: do not change this, or else upgrading plugins will require @@ -255,6 +295,10 @@ func genMarkMasqChain(markBit int) chain { table: "nat", name: MarkMasqChainName, entryChains: []string{"POSTROUTING"}, + // Only this entry chain needs to be prepended, because otherwise it is + // stomped on by the masquerading rules created by the CNI ptp and bridge + // plugins. + prependEntry: true, entryRules: [][]string{{ "-m", "comment", "--comment", "CNI portfwd requiring masquerade", diff --git a/vendor/github.com/containernetworking/plugins/plugins/meta/portmap/portmap_integ_test.go b/vendor/github.com/containernetworking/plugins/plugins/meta/portmap/portmap_integ_test.go index 22c1e1f78..ce4eebef0 100644 --- a/vendor/github.com/containernetworking/plugins/plugins/meta/portmap/portmap_integ_test.go +++ b/vendor/github.com/containernetworking/plugins/plugins/meta/portmap/portmap_integ_test.go @@ -16,6 +16,7 @@ package main import ( "bytes" + "context" "fmt" "math/rand" "net" @@ -121,7 +122,7 @@ var _ = Describe("portmap integration tests", func() { return nil } netDeleted = true - return cniConf.DelNetworkList(configList, &runtimeConfig) + return cniConf.DelNetworkList(context.TODO(), configList, &runtimeConfig) } // we'll also manually check the iptables chains @@ -130,7 +131,7 @@ var _ = Describe("portmap integration tests", func() { dnatChainName := genDnatChain("cni-portmap-unit-test", runtimeConfig.ContainerID).name // Create the network - resI, err := cniConf.AddNetworkList(configList, &runtimeConfig) + resI, err := cniConf.AddNetworkList(context.TODO(), configList, &runtimeConfig) Expect(err).NotTo(HaveOccurred()) defer deleteNetwork() @@ -164,6 +165,12 @@ var _ = Describe("portmap integration tests", func() { fmt.Fprintf(GinkgoWriter, "hostIP: %s:%d, contIP: %s:%d\n", hostIP, hostPort, contIP, containerPort) + // dump iptables-save output for debugging + cmd = exec.Command("iptables-save") + cmd.Stderr = GinkgoWriter + cmd.Stdout = GinkgoWriter + Expect(cmd.Run()).To(Succeed()) + // Sanity check: verify that the container is reachable directly contOK := testEchoServer(contIP.String(), containerPort, "") diff --git a/vendor/github.com/containernetworking/plugins/plugins/meta/portmap/portmap_test.go b/vendor/github.com/containernetworking/plugins/plugins/meta/portmap/portmap_test.go index d47b483c2..8b04bf78a 100644 --- a/vendor/github.com/containernetworking/plugins/plugins/meta/portmap/portmap_test.go +++ b/vendor/github.com/containernetworking/plugins/plugins/meta/portmap/portmap_test.go @@ -68,7 +68,7 @@ var _ = Describe("portmapping configuration", func() { ] } }`) - c, err := parseConfig(configBytes, "container") + c, _, err := parseConfig(configBytes, "container") Expect(err).NotTo(HaveOccurred()) Expect(c.CNIVersion).To(Equal("0.3.1")) Expect(c.ConditionsV4).To(Equal(&[]string{"a", "b"})) @@ -91,7 +91,7 @@ var _ = Describe("portmapping configuration", func() { "conditionsV4": ["a", "b"], "conditionsV6": ["c", "d"] }`) - c, err := parseConfig(configBytes, "container") + c, _, err := parseConfig(configBytes, "container") Expect(err).NotTo(HaveOccurred()) Expect(c.CNIVersion).To(Equal("0.3.1")) Expect(c.ConditionsV4).To(Equal(&[]string{"a", "b"})) @@ -115,7 +115,7 @@ var _ = Describe("portmapping configuration", func() { ] } }`) - _, err := parseConfig(configBytes, "container") + _, _, err := parseConfig(configBytes, "container") Expect(err).To(MatchError("Invalid host port number: 0")) }) @@ -143,7 +143,7 @@ var _ = Describe("portmapping configuration", func() { ] } }`) - _, err := parseConfig(configBytes, "container") + _, _, err := parseConfig(configBytes, "container") Expect(err).NotTo(HaveOccurred()) }) }) @@ -175,7 +175,7 @@ var _ = Describe("portmapping configuration", func() { "conditionsV6": ["c", "d"] }`) - conf, err := parseConfig(configBytes, "foo") + conf, _, err := parseConfig(configBytes, "foo") Expect(err).NotTo(HaveOccurred()) conf.ContainerID = containerID @@ -271,7 +271,7 @@ var _ = Describe("portmapping configuration", func() { "conditionsV6": ["c", "d"] }`) - conf, err := parseConfig(configBytes, "foo") + conf, _, err := parseConfig(configBytes, "foo") Expect(err).NotTo(HaveOccurred()) conf.ContainerID = containerID @@ -323,6 +323,7 @@ var _ = Describe("portmapping configuration", func() { "--mark", "0x20/0x20", "-j", "MASQUERADE", }}, + prependEntry: true, })) }) }) diff --git a/vendor/github.com/containernetworking/plugins/plugins/meta/sbr/README.md b/vendor/github.com/containernetworking/plugins/plugins/meta/sbr/README.md new file mode 100644 index 000000000..622b7ff58 --- /dev/null +++ b/vendor/github.com/containernetworking/plugins/plugins/meta/sbr/README.md @@ -0,0 +1,127 @@ +# Source based routing plugin + +## Introduction + +This plugin performs Source Based Routing (SBR). The most common and standard way to +perform routing is to base it purely on the destination. However, in some +applications which are using network separation for traffic management and +security, there is no way to tell *a priori* which interface should be used, +but the application is capable of making the decision. + +As an example, a Telco application might have two networks, a management +network and a SIP (telephony) network for traffic, with rules which state that: + +- SIP traffic (only) must be routed over the SIP network; + +- all other traffic (but no SIP traffic) must be routed over the management + network. + +There is no way of configuring this based on destination IP, since there is no +way of telling whether a destination IP on the internet is (say) an address +used for downloading updated software packages or a remote SIP endpoint. + +Hence Source Based Routing is used. + +- The application explicitly listens on the correct interface for incoming + traffic. + +- When the application wishes to send to an address via the SIP network, it + explicitly binds to the IP of the device on that network. + +- Routes for the SIP interface are configured in a separate routing table, and + a rule is configured to use that table based on the source IP address. + +Note that in most cases there is a management device (the first one) and that +the SBR plugin is called once for each device after the first, leaving the +default routing table applied to the management device. However, this not +mandatory, and source based routing may be configured on any or all of the +devices as appropriate. + +## Usage + +This plugin runs as a chained plugin, and requires the following information +passed in from the previous plugin (which has just set up the network device): + +- What is the network interface in question? + +- What is the IP address (or addresses) of that network interface? + +- What is the default gateway for that interface (if any)? + +It then reads all routes to the network interface in use. + +Here is an example of what the plugin would do. (The `ip` based commands are +implemented in go, but easier to describe via the command line.) Suppose that +it reads that: + +- The interface is `net1`. + +- The IP address on that interface is `192.168.1.209`. + +- The default gateway on that interface is `192.168.1.1`. + +- There is one route configured on that network interface, which is + `192.168.1.0/24`. + +Then the actions it takes are the following. + +- It creates a new routing table, and sets a rule to use it for the IP address in question. + + ip rule add from 192.168.1.209/32 table 100 + +- It adds a route to the default gateway for the relevant table. + + ip route add default via 192.168.1.1 dev net1 table 100 + +- It moves every existing route on the device to the new table. + + ip route del 192.168.1.0/24 dev net1 src 192.168.1.209 + ip route add 192.168.1.0/24 dev net1 src 192.168.1.209 table 100 + +On deletion it: + +- deletes the rule (`ip rule del from 192.168.1.209/32 table 100`), which it + finds by deleting rules relating to IPs on the device which is about to be + deleted. + +- does nothing with routes (since the kernel automatically removes routes when + the device with which they are associated is deleted). + +## Future enhancements and known limitations + +The following are possible future enhancements. + +- The table number is currently selected by starting at 100, then incrementing + the value until an unused table number is found. It might be nice to have an + option to pass the table number as an input. + +- There is no log severity, and there is no logging to file (pending changes to + CNI logging generally). + +- This plugin sets up Source Based Routing, as described above. In future, + there may be a need for a VRF plugin (that uses + [VRF routing](https://www.kernel.org/doc/Documentation/networking/vrf.txt) + instead of source based routing). If and when this happens, it is likely that + the logic would be virtually identical for the plugin, and so the same plugin + might offer either SBR or VRF routing depending on configuration. + +## Configuration + +This plugin must be used as a chained plugin. There are no specific configuration parameters. + +A sample configuration for this plugin acting as a chained plugin after flannel +is the following. + +~~~json +{ + "name": "flannel-sbr", + "cniVersion": "0.3.0", + "plugins": + [ + { "type": "flannel" }, + { + "type": "sbr", + } + ] +} +~~~ diff --git a/vendor/github.com/containernetworking/plugins/plugins/meta/sbr/main.go b/vendor/github.com/containernetworking/plugins/plugins/meta/sbr/main.go new file mode 100644 index 000000000..10dcb655e --- /dev/null +++ b/vendor/github.com/containernetworking/plugins/plugins/meta/sbr/main.go @@ -0,0 +1,380 @@ +// Copyright 2017 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. + +// This is the Source Based Routing plugin that sets up source based routing. +package main + +import ( + "encoding/json" + "fmt" + "log" + "net" + + "github.com/alexflint/go-filemutex" + "github.com/vishvananda/netlink" + + "github.com/containernetworking/cni/pkg/skel" + "github.com/containernetworking/cni/pkg/types" + "github.com/containernetworking/cni/pkg/types/current" + "github.com/containernetworking/cni/pkg/version" + + "github.com/containernetworking/plugins/pkg/ns" + bv "github.com/containernetworking/plugins/pkg/utils/buildversion" +) + +const firstTableID = 100 + +// PluginConf is the configuration document passed in. +type PluginConf struct { + types.NetConf + + // This is the previous result, when called in the context of a chained + // plugin. Because this plugin supports multiple versions, we'll have to + // parse this in two passes. If your plugin is not chained, this can be + // removed (though you may wish to error if a non-chainable plugin is + // chained). + RawPrevResult *map[string]interface{} `json:"prevResult"` + PrevResult *current.Result `json:"-"` + + // Add plugin-specific flags here +} + +// Wrapper that does a lock before and unlock after operations to serialise +// this plugin. +func withLockAndNetNS(nspath string, toRun func(_ ns.NetNS) error) error { + // We lock on the network namespace to ensure that no other instance + // clashes with this one. + log.Printf("Network namespace to use and lock: %s", nspath) + lock, err := filemutex.New(nspath) + if err != nil { + return err + } + + err = lock.Lock() + if err != nil { + return err + } + + err = ns.WithNetNSPath(nspath, toRun) + + if err != nil { + return err + } + + // Cleaner to unlock even though about to exit + err = lock.Unlock() + + return err +} + +// parseConfig parses the supplied configuration (and prevResult) from stdin. +func parseConfig(stdin []byte) (*PluginConf, error) { + conf := PluginConf{} + + if err := json.Unmarshal(stdin, &conf); err != nil { + return nil, fmt.Errorf("failed to parse network configuration: %v", err) + } + + // Parse previous result. + if conf.RawPrevResult != nil { + resultBytes, err := json.Marshal(conf.RawPrevResult) + if err != nil { + return nil, fmt.Errorf("could not serialize prevResult: %v", err) + } + res, err := version.NewResult(conf.CNIVersion, resultBytes) + if err != nil { + return nil, fmt.Errorf("could not parse prevResult: %v", err) + } + conf.RawPrevResult = nil + conf.PrevResult, err = current.NewResultFromResult(res) + if err != nil { + return nil, fmt.Errorf("could not convert result to current version: %v", err) + } + } + // End previous result parsing + + return &conf, nil +} + +// getIPCfgs finds the IPs on the supplied interface, returning as IPConfig structures +func getIPCfgs(iface string, prevResult *current.Result) ([]*current.IPConfig, error) { + + if len(prevResult.IPs) == 0 { + // No IP addresses; that makes no sense. Pack it in. + return nil, fmt.Errorf("No IP addresses supplied on interface: %s", iface) + } + + // We do a single interface name, stored in args.IfName + log.Printf("Checking for relevant interface: %s", iface) + + // ips contains the IPConfig structures that were passed, filtered somewhat + ipCfgs := make([]*current.IPConfig, 0, len(prevResult.IPs)) + + for _, ipCfg := range prevResult.IPs { + // IPs have an interface that is an index into the interfaces array. + // We assume a match if this index is missing. + if ipCfg.Interface == nil { + log.Printf("No interface for IP address %s", ipCfg.Address.IP) + ipCfgs = append(ipCfgs, ipCfg) + continue + } + + // Skip all IPs we know belong to an interface with the wrong name. + intIdx := *ipCfg.Interface + if intIdx >= 0 && intIdx < len(prevResult.Interfaces) && prevResult.Interfaces[intIdx].Name != iface { + log.Printf("Incorrect interface for IP address %s", ipCfg.Address.IP) + continue + } + + log.Printf("Found IP address %s", ipCfg.Address.IP.String()) + ipCfgs = append(ipCfgs, ipCfg) + } + + return ipCfgs, nil +} + +// cmdAdd is called for ADD requests +func cmdAdd(args *skel.CmdArgs) error { + conf, err := parseConfig(args.StdinData) + if err != nil { + return err + } + + log.Printf("Configure SBR for new interface %s - previous result: %v", + args.IfName, conf.PrevResult) + + if conf.PrevResult == nil { + return fmt.Errorf("This plugin must be called as chained plugin") + } + + // Get the list of relevant IPs. + ipCfgs, err := getIPCfgs(args.IfName, conf.PrevResult) + if err != nil { + return err + } + + // Do the actual work. + err = withLockAndNetNS(args.Netns, func(_ ns.NetNS) error { + return doRoutes(ipCfgs, conf.PrevResult.Routes, args.IfName) + }) + if err != nil { + return err + } + + // Pass through the result for the next plugin + return types.PrintResult(conf.PrevResult, conf.CNIVersion) +} + +// doRoutes does all the work to set up routes and rules during an add. +func doRoutes(ipCfgs []*current.IPConfig, origRoutes []*types.Route, iface string) error { + // Get a list of rules and routes ready. + rules, err := netlink.RuleList(netlink.FAMILY_ALL) + if err != nil { + return fmt.Errorf("Failed to list all rules: %v", err) + } + + routes, err := netlink.RouteList(nil, netlink.FAMILY_ALL) + if err != nil { + return fmt.Errorf("Failed to list all routes: %v", err) + } + + // Pick a table ID to use. We pick the first table ID from firstTableID + // on that has no existing rules mapping to it and no existing routes in + // it. + table := firstTableID + for { + foundExisting := false + for _, rule := range rules { + if rule.Table == table { + foundExisting = true + break + } + } + + for _, route := range routes { + if route.Table == table { + foundExisting = true + break + } + } + + if foundExisting { + table++ + } else { + break + } + } + + log.Printf("First unreferenced table: %d", table) + + link, err := netlink.LinkByName(iface) + if err != nil { + return fmt.Errorf("Cannot find network interface %s: %v", iface, err) + } + + linkIndex := link.Attrs().Index + + // Loop through setting up source based rules and default routes. + for _, ipCfg := range ipCfgs { + log.Printf("Set rule for source %s", ipCfg.String()) + rule := netlink.NewRule() + rule.Table = table + + // Source must be restricted to a single IP, not a full subnet + var src net.IPNet + src.IP = ipCfg.Address.IP + if ipCfg.Version == "4" { + src.Mask = net.CIDRMask(32, 32) + } else { + src.Mask = net.CIDRMask(64, 64) + } + + log.Printf("Source to use %s", src.String()) + rule.Src = &src + + if err = netlink.RuleAdd(rule); err != nil { + return fmt.Errorf("Failed to add rule: %v", err) + } + + // Add a default route, since this may have been removed by previous + // plugin. + if ipCfg.Gateway != nil { + log.Printf("Adding default route to gateway %s", ipCfg.Gateway.String()) + + var dest net.IPNet + if ipCfg.Version == "4" { + dest.IP = net.IPv4zero + dest.Mask = net.CIDRMask(0, 32) + } else { + dest.IP = net.IPv6zero + dest.Mask = net.CIDRMask(0, 64) + } + + route := netlink.Route{ + Dst: &dest, + Gw: ipCfg.Gateway, + Table: table, + LinkIndex: linkIndex} + + err = netlink.RouteAdd(&route) + if err != nil { + return fmt.Errorf("Failed to add default route to %s: %v", + ipCfg.Gateway.String(), + err) + } + } + } + + // Move all routes into the correct table. We are taking a shortcut; all + // the routes have been added to the interface anyway but in the wrong + // table, so instead of removing them we just move them to the table we + // want them in. + routes, err = netlink.RouteList(link, netlink.FAMILY_ALL) + if err != nil { + return fmt.Errorf("Unable to list routes: %v", err) + } + + for _, route := range routes { + log.Printf("Moving route %s from table %d to %d", + route.String(), route.Table, table) + + err := netlink.RouteDel(&route) + if err != nil { + return fmt.Errorf("Failed to delete route: %v", err) + } + + route.Table = table + + // We use route replace in case the route already exists, which + // is possible for the default gateway we added above. + err = netlink.RouteReplace(&route) + if err != nil { + return fmt.Errorf("Failed to readd route: %v", err) + } + } + + return nil +} + +// cmdDel is called for DELETE requests +func cmdDel(args *skel.CmdArgs) error { + // We care a bit about config because it sets log level. + _, err := parseConfig(args.StdinData) + if err != nil { + return err + } + + log.Printf("Cleaning up SBR for %s", args.IfName) + err = withLockAndNetNS(args.Netns, func(_ ns.NetNS) error { + return tidyRules(args.IfName) + }) + + return err +} + +// Tidy up the rules for the deleted interface +func tidyRules(iface string) error { + + // We keep on going on rule deletion error, but return the last failure. + var errReturn error + + rules, err := netlink.RuleList(netlink.FAMILY_ALL) + if err != nil { + log.Printf("Failed to list all rules to tidy: %v", err) + return fmt.Errorf("Failed to list all rules to tidy: %v", err) + } + + link, err := netlink.LinkByName(iface) + if err != nil { + log.Printf("Failed to get link %s: %v", iface, err) + return fmt.Errorf("Failed to get link %s: %v", iface, err) + } + + addrs, err := netlink.AddrList(link, netlink.FAMILY_ALL) + if err != nil { + log.Printf("Failed to list all addrs: %v", err) + return fmt.Errorf("Failed to list all addrs: %v", err) + } + +RULE_LOOP: + for _, rule := range rules { + log.Printf("Check rule: %v", rule) + if rule.Src == nil { + continue + } + + for _, addr := range addrs { + if rule.Src.IP.Equal(addr.IP) { + log.Printf("Delete rule %v", rule) + err := netlink.RuleDel(&rule) + if err != nil { + errReturn = fmt.Errorf("Failed to delete rule %v", err) + log.Printf("... Failed! %v", err) + } + continue RULE_LOOP + } + } + + } + + return errReturn +} + +func main() { + skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("sbr")) +} + +func cmdCheck(args *skel.CmdArgs) error { + return nil +} diff --git a/vendor/github.com/containernetworking/plugins/plugins/meta/sbr/sbr_linux_test.go b/vendor/github.com/containernetworking/plugins/plugins/meta/sbr/sbr_linux_test.go new file mode 100644 index 000000000..442d4aa11 --- /dev/null +++ b/vendor/github.com/containernetworking/plugins/plugins/meta/sbr/sbr_linux_test.go @@ -0,0 +1,430 @@ +// Copyright 2017 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 main + +import ( + "fmt" + "log" + "net" + + "github.com/containernetworking/cni/pkg/skel" + "github.com/containernetworking/plugins/pkg/ns" + "github.com/containernetworking/plugins/pkg/testutils" + + "github.com/vishvananda/netlink" + "golang.org/x/sys/unix" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +// Structures specifying state at start. +type Device struct { + Name string + Addrs []net.IPNet + Routes []netlink.Route +} + +type Rule struct { + Src string + Table int +} + +type netStatus struct { + Devices []Device + Rules []netlink.Rule +} + +// Create a link, shove some IP addresses on it, and push it into the network +// namespace. +func setup(targetNs ns.NetNS, status netStatus) error { + // Get the status right + err := targetNs.Do(func(_ ns.NetNS) error { + for _, dev := range status.Devices { + log.Printf("Adding dev %s\n", dev.Name) + link := &netlink.Dummy{LinkAttrs: netlink.LinkAttrs{Name: dev.Name}} + err := netlink.LinkAdd(link) + if err != nil { + return err + } + + err = netlink.LinkSetUp(link) + if err != nil { + return err + } + + for _, addr := range dev.Addrs { + log.Printf("Adding address %v to device %s\n", addr, dev.Name) + err = netlink.AddrAdd(link, &netlink.Addr{IPNet: &addr}) + if err != nil { + return err + } + } + + for _, route := range dev.Routes { + log.Printf("Adding route %v to device %s\n", route, dev.Name) + route.LinkIndex = link.Attrs().Index + err = netlink.RouteAdd(&route) + if err != nil { + return err + } + } + } + return nil + }) + + return err +} + +// Readback the routes and rules. +func readback(targetNs ns.NetNS, devNames []string) (netStatus, error) { + // Get the status right. + var retVal netStatus + + err := targetNs.Do(func(_ ns.NetNS) error { + retVal.Devices = make([]Device, 2) + + for i, name := range devNames { + log.Printf("Checking device %s", name) + retVal.Devices[i].Name = name + + link, err := netlink.LinkByName(name) + if err != nil { + return err + } + + // Need to read all tables, so cannot use RouteList + routeFilter := &netlink.Route{ + LinkIndex: link.Attrs().Index, + Table: unix.RT_TABLE_UNSPEC, + } + + routes, err := netlink.RouteListFiltered(netlink.FAMILY_ALL, + routeFilter, + netlink.RT_FILTER_OIF|netlink.RT_FILTER_TABLE) + + if err != nil { + return err + } + + for _, route := range routes { + log.Printf("Got %s route %v", name, route) + } + + retVal.Devices[i].Routes = routes + } + + rules, err := netlink.RuleList(netlink.FAMILY_ALL) + if err != nil { + return err + } + + retVal.Rules = make([]netlink.Rule, 0, len(rules)) + + for _, rule := range rules { + // Rules over 250 are the kernel defaults, that we ignore. + if rule.Table < 250 { + log.Printf("Got interesting rule %v", rule) + retVal.Rules = append(retVal.Rules, rule) + } + } + + return nil + }) + + return retVal, err +} + +func equalRoutes(expected, actual []netlink.Route) bool { + // Compare two sets of routes, comparing only destination, gateway and + // table. Return true if equal. + match := true + + // Map used to make comparisons easy + expMap := make(map[string]bool) + + for _, route := range expected { + expMap[route.String()] = true + } + + for _, route := range actual { + routeString := route.String() + if expMap[routeString] { + log.Printf("Route %s expected and found", routeString) + delete(expMap, routeString) + } else { + log.Printf("Route %s found, not expected", routeString) + match = false + } + } + + for expRoute := range expMap { + log.Printf("Route %s expected, not found", expRoute) + match = false + } + + return match +} + +func createDefaultStatus() netStatus { + // Useful default status. + devs := make([]Device, 2) + rules := make([]netlink.Rule, 0) + + devs[0] = Device{Name: "eth0"} + devs[0].Addrs = make([]net.IPNet, 1) + devs[0].Addrs[0] = net.IPNet{ + IP: net.IPv4(10, 0, 0, 2), + Mask: net.IPv4Mask(255, 255, 255, 0), + } + devs[0].Routes = make([]netlink.Route, 2) + devs[0].Routes[0] = netlink.Route{ + Dst: &net.IPNet{ + IP: net.IPv4(10, 2, 0, 0), + Mask: net.IPv4Mask(255, 255, 255, 0), + }, + Gw: net.IPv4(10, 0, 0, 5), + } + devs[0].Routes[1] = netlink.Route{ + Gw: net.IPv4(10, 0, 0, 1), + } + + devs[1] = Device{Name: "net1"} + devs[1].Addrs = make([]net.IPNet, 1) + devs[1].Addrs[0] = net.IPNet{ + IP: net.IPv4(192, 168, 1, 209), + Mask: net.IPv4Mask(255, 255, 255, 0), + } + devs[1].Routes = make([]netlink.Route, 1) + devs[1].Routes[0] = netlink.Route{ + Dst: &net.IPNet{ + IP: net.IPv4(192, 168, 2, 0), + Mask: net.IPv4Mask(255, 255, 255, 0), + }, + Gw: net.IPv4(192, 168, 1, 2), + } + + return netStatus{ + Devices: devs, + Rules: rules} +} + +var _ = Describe("sbr test", func() { + var targetNs ns.NetNS + + BeforeEach(func() { + var err error + targetNs, err = testutils.NewNS() + Expect(err).NotTo(HaveOccurred()) + }) + + AfterEach(func() { + targetNs.Close() + }) + + It("Works with a 0.3.0 config", func() { + ifname := "net1" + conf := `{ + "cniVersion": "0.3.0", + "name": "cni-plugin-sbr-test", + "type": "sbr", + "prevResult": { + "interfaces": [ + { + "name": "%s", + "sandbox": "%s" + } + ], + "ips": [ + { + "version": "4", + "address": "192.168.1.209/24", + "gateway": "192.168.1.1", + "interface": 0 + } + ], + "routes": [] + } +}` + conf = fmt.Sprintf(conf, ifname, targetNs.Path()) + args := &skel.CmdArgs{ + ContainerID: "dummy", + Netns: targetNs.Path(), + IfName: ifname, + StdinData: []byte(conf), + } + + err := setup(targetNs, createDefaultStatus()) + Expect(err).NotTo(HaveOccurred()) + + oldStatus, err := readback(targetNs, []string{"net1", "eth0"}) + Expect(err).NotTo(HaveOccurred()) + + _, _, err = testutils.CmdAddWithArgs(args, func() error { return cmdAdd(args) }) + Expect(err).NotTo(HaveOccurred()) + + newStatus, err := readback(targetNs, []string{"net1", "eth0"}) + Expect(err).NotTo(HaveOccurred()) + + // Check results. We expect all the routes on net1 to have moved to + // table 100 except for local routes (table 255); a new default gateway + // route to have been created; and a single rule to exist. + expNet1 := oldStatus.Devices[0] + expEth0 := oldStatus.Devices[1] + for i := range expNet1.Routes { + if expNet1.Routes[i].Table != 255 { + expNet1.Routes[i].Table = 100 + } + } + expNet1.Routes = append(expNet1.Routes, + netlink.Route{ + Gw: net.IPv4(192, 168, 1, 1), + Table: 100, + LinkIndex: expNet1.Routes[0].LinkIndex}) + + Expect(len(newStatus.Rules)).To(Equal(1)) + Expect(newStatus.Rules[0].Table).To(Equal(100)) + Expect(newStatus.Rules[0].Src.String()).To(Equal("192.168.1.209/32")) + devNet1 := newStatus.Devices[0] + devEth0 := newStatus.Devices[1] + Expect(equalRoutes(expNet1.Routes, devNet1.Routes)).To(BeTrue()) + Expect(equalRoutes(expEth0.Routes, devEth0.Routes)).To(BeTrue()) + + conf = `{ + "cniVersion": "0.3.0", + "name": "cni-plugin-sbr-test", + "type": "sbr" +}` + + // And now check that we can back it all out. + args = &skel.CmdArgs{ + ContainerID: "dummy", + Netns: targetNs.Path(), + IfName: ifname, + StdinData: []byte(conf), + } + err = testutils.CmdDelWithArgs(args, func() error { return cmdDel(args) }) + Expect(err).NotTo(HaveOccurred()) + + retVal, err := readback(targetNs, []string{"net1", "eth0"}) + Expect(err).NotTo(HaveOccurred()) + + // Check results. We expect the rule to have been removed. + Expect(len(retVal.Rules)).To(Equal(0)) + }) + + It("Works with a default route already set", func() { + ifname := "net1" + conf := `{ + "cniVersion": "0.3.0", + "name": "cni-plugin-sbr-test", + "type": "sbr", + "prevResult": { + "interfaces": [ + { + "name": "%s", + "sandbox": "%s" + } + ], + "ips": [ + { + "version": "4", + "address": "192.168.1.209/24", + "gateway": "192.168.1.1", + "interface": 0 + } + ], + "routes": [] + } +}` + conf = fmt.Sprintf(conf, ifname, targetNs.Path()) + args := &skel.CmdArgs{ + ContainerID: "dummy", + Netns: targetNs.Path(), + IfName: ifname, + StdinData: []byte(conf), + } + + preStatus := createDefaultStatus() + // Remove final (default) route from eth0, then add another default + // route to net1 + preStatus.Devices[0].Routes = preStatus.Devices[0].Routes[:0] + routes := preStatus.Devices[1].Routes + preStatus.Devices[1].Routes = append(preStatus.Devices[1].Routes, + netlink.Route{ + Gw: net.IPv4(192, 168, 1, 1), + LinkIndex: routes[0].LinkIndex}) + + err := setup(targetNs, preStatus) + Expect(err).NotTo(HaveOccurred()) + + oldStatus, err := readback(targetNs, []string{"net1", "eth0"}) + Expect(err).NotTo(HaveOccurred()) + + _, _, err = testutils.CmdAddWithArgs(args, func() error { return cmdAdd(args) }) + Expect(err).NotTo(HaveOccurred()) + + newStatus, err := readback(targetNs, []string{"net1", "eth0"}) + Expect(err).NotTo(HaveOccurred()) + + // Check results. We expect all the routes on net1 to have moved to + // table 100 except for local routes (table 255); a new default gateway + // route to have been created; and a single rule to exist. + expNet1 := oldStatus.Devices[0] + expEth0 := oldStatus.Devices[1] + for i := range expNet1.Routes { + if expNet1.Routes[i].Table != 255 { + expNet1.Routes[i].Table = 100 + } + } + + Expect(len(newStatus.Rules)).To(Equal(1)) + Expect(newStatus.Rules[0].Table).To(Equal(100)) + Expect(newStatus.Rules[0].Src.String()).To(Equal("192.168.1.209/32")) + devNet1 := newStatus.Devices[0] + devEth0 := newStatus.Devices[1] + Expect(equalRoutes(expEth0.Routes, devEth0.Routes)).To(BeTrue()) + Expect(equalRoutes(expNet1.Routes, devNet1.Routes)).To(BeTrue()) + }) + + It("works with a 0.2.0 config", func() { + conf := `{ + "cniVersion": "0.2.0", + "name": "cni-plugin-sbr-test", + "type": "sbr", + "anotherAwesomeArg": "foo", + "prevResult": { + "ip4": { + "ip": "192.168.1.209/24", + "gateway": "192.168.1.1", + "routes": [] + } + } +}` + + args := &skel.CmdArgs{ + ContainerID: "dummy", + Netns: targetNs.Path(), + IfName: "net1", + StdinData: []byte(conf), + } + err := setup(targetNs, createDefaultStatus()) + Expect(err).NotTo(HaveOccurred()) + + _, _, err = testutils.CmdAddWithArgs(args, func() error { return cmdAdd(args) }) + Expect(err).NotTo(HaveOccurred()) + }) + +}) diff --git a/vendor/github.com/containernetworking/plugins/plugins/meta/sbr/sbr_suite_test.go b/vendor/github.com/containernetworking/plugins/plugins/meta/sbr/sbr_suite_test.go new file mode 100644 index 000000000..d0bc1841a --- /dev/null +++ b/vendor/github.com/containernetworking/plugins/plugins/meta/sbr/sbr_suite_test.go @@ -0,0 +1,15 @@ +// The boilerplate needed for Ginkgo + +package main + +import ( + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + "testing" +) + +func TestSample(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "plugins/sbr") +} diff --git a/vendor/github.com/containernetworking/plugins/plugins/meta/tuning/README.md b/vendor/github.com/containernetworking/plugins/plugins/meta/tuning/README.md index 7ca16d0e6..455073806 100644 --- a/vendor/github.com/containernetworking/plugins/plugins/meta/tuning/README.md +++ b/vendor/github.com/containernetworking/plugins/plugins/meta/tuning/README.md @@ -2,11 +2,11 @@ ## Overview -This plugin can change some system controls (sysctls) in the network namespace. +This plugin can change some system controls (sysctls) and several interface attributes (promiscuous mode, MTU and MAC address) in the network namespace. It does not create any network interfaces and therefore does not bring connectivity by itself. It is only useful when used in addition to other plugins. -## Operation +## System Controls Operation The following network configuration file ``` { @@ -32,3 +32,32 @@ Some network sysctls are documented in the Linux sources: - [Documentation/sysctl/net.txt](https://www.kernel.org/doc/Documentation/sysctl/net.txt) - [Documentation/networking/ip-sysctl.txt](https://www.kernel.org/doc/Documentation/networking/ip-sysctl.txt) - [Documentation/networking/](https://www.kernel.org/doc/Documentation/networking/) + +## Interface Attribute Operation +The parameters, "mac", "mtu" and "promisc", changes the interface attributes as followings: + +``` +{ + "name": "mytuning", + "type": "tuning", + "promisc": true, + "mac": "c2:b0:57:49:47:f1", + "mtu": 1454 +} +``` + +## Interface attribute configuration reference + +* `mac` (string, optional): MAC address (i.e. hardware address) of interface +* `mtu` (integer, optional): MTU of interface +* `promisc` (bool, optional): Change the promiscuous mode of interface + +## Supported arguments +The following [CNI_ARGS](https://github.com/containernetworking/cni/blob/master/SPEC.md#parameters) are supported: + +* `MAC`: request a specific MAC address for the interface + + (example: CNI_ARGS="IgnoreUnknown=true;MAC=c2:11:22:33:44:55") + +Note: You may add `IgnoreUnknown=true` to allow loose CNI argument verification (see CNI's issue[#560](https://github.com/containernetworking/cni/issues/560)). + diff --git a/vendor/github.com/containernetworking/plugins/plugins/meta/tuning/tuning.go b/vendor/github.com/containernetworking/plugins/plugins/meta/tuning/tuning.go index ec9fe2e4c..f8d0f3ad4 100644 --- a/vendor/github.com/containernetworking/plugins/plugins/meta/tuning/tuning.go +++ b/vendor/github.com/containernetworking/plugins/plugins/meta/tuning/tuning.go @@ -21,52 +21,131 @@ import ( "encoding/json" "fmt" "io/ioutil" + "net" "path/filepath" "strings" + "github.com/vishvananda/netlink" + "github.com/containernetworking/cni/pkg/skel" "github.com/containernetworking/cni/pkg/types" "github.com/containernetworking/cni/pkg/types/current" "github.com/containernetworking/cni/pkg/version" + "github.com/containernetworking/plugins/pkg/ns" + bv "github.com/containernetworking/plugins/pkg/utils/buildversion" ) // TuningConf represents the network tuning configuration. type TuningConf struct { types.NetConf - SysCtl map[string]string `json:"sysctl"` - RawPrevResult map[string]interface{} `json:"prevResult,omitempty"` - PrevResult *current.Result `json:"-"` + SysCtl map[string]string `json:"sysctl"` + Mac string `json:"mac,omitempty"` + Promisc bool `json:"promisc,omitempty"` + Mtu int `json:"mtu,omitempty"` +} + +type MACEnvArgs struct { + types.CommonArgs + MAC types.UnmarshallableString `json:"mac,omitempty"` } -func parseConf(data []byte) (*TuningConf, error) { - conf := TuningConf{} +func parseConf(data []byte, envArgs string) (*TuningConf, error) { + conf := TuningConf{Promisc: false} if err := json.Unmarshal(data, &conf); err != nil { return nil, fmt.Errorf("failed to load netconf: %v", err) } - // Parse previous result. - if conf.RawPrevResult != nil { - resultBytes, err := json.Marshal(conf.RawPrevResult) - if err != nil { - return nil, fmt.Errorf("could not serialize prevResult: %v", err) - } - res, err := version.NewResult(conf.CNIVersion, resultBytes) + // Parse custom MAC from both env args + if envArgs != "" { + e := MACEnvArgs{} + err := types.LoadArgs(envArgs, &e) if err != nil { - return nil, fmt.Errorf("could not parse prevResult: %v", err) + return nil, err } - conf.RawPrevResult = nil - conf.PrevResult, err = current.NewResultFromResult(res) - if err != nil { - return nil, fmt.Errorf("could not convert result to current version: %v", err) + + if e.MAC != "" { + conf.Mac = string(e.MAC) } } return &conf, nil } +func changeMacAddr(ifName string, newMacAddr string) error { + addr, err := net.ParseMAC(newMacAddr) + if err != nil { + return fmt.Errorf("invalid args %v for MAC addr: %v", newMacAddr, err) + } + + link, err := netlink.LinkByName(ifName) + if err != nil { + return fmt.Errorf("failed to get %q: %v", ifName, err) + } + + err = netlink.LinkSetDown(link) + if err != nil { + return fmt.Errorf("failed to set %q down: %v", ifName, err) + } + err = netlink.LinkSetHardwareAddr(link, addr) + if err != nil { + return fmt.Errorf("failed to set %q address to %q: %v", ifName, newMacAddr, err) + } + return netlink.LinkSetUp(link) +} + +func updateResultsMacAddr(config TuningConf, ifName string, newMacAddr string) { + // Parse previous result. + if config.PrevResult == nil { + return + } + + version.ParsePrevResult(&config.NetConf) + result, _ := current.NewResultFromResult(config.PrevResult) + + for _, i := range result.Interfaces { + if i.Name == ifName { + i.Mac = newMacAddr + } + } +} + +func changePromisc(ifName string, val bool) error { + link, err := netlink.LinkByName(ifName) + if err != nil { + return fmt.Errorf("failed to get %q: %v", ifName, err) + } + + if val { + return netlink.SetPromiscOn(link) + } + return netlink.SetPromiscOff(link) +} + +func changeMtu(ifName string, mtu int) error { + link, err := netlink.LinkByName(ifName) + if err != nil { + return fmt.Errorf("failed to get %q: %v", ifName, err) + } + return netlink.LinkSetMTU(link, mtu) +} + func cmdAdd(args *skel.CmdArgs) error { - tuningConf, err := parseConf(args.StdinData) + tuningConf, err := parseConf(args.StdinData, args.Args) + if err != nil { + return err + } + + // Parse previous result. + if tuningConf.RawPrevResult == nil { + return fmt.Errorf("Required prevResult missing") + } + + if err := version.ParsePrevResult(&tuningConf.NetConf); err != nil { + return err + } + + _, err = current.NewResultFromResult(tuningConf.PrevResult) if err != nil { return err } @@ -90,6 +169,25 @@ func cmdAdd(args *skel.CmdArgs) error { return err } } + + if tuningConf.Mac != "" { + if err = changeMacAddr(args.IfName, tuningConf.Mac); err != nil { + return err + } + updateResultsMacAddr(*tuningConf, args.IfName, tuningConf.Mac) + } + + if tuningConf.Promisc != false { + if err = changePromisc(args.IfName, true); err != nil { + return err + } + } + + if tuningConf.Mtu != 0 { + if err = changeMtu(args.IfName, tuningConf.Mtu); err != nil { + return err + } + } return nil }) if err != nil { @@ -107,5 +205,80 @@ func cmdDel(args *skel.CmdArgs) error { } func main() { - skel.PluginMain(cmdAdd, cmdDel, version.All) + skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("tuning")) +} + +func cmdCheck(args *skel.CmdArgs) error { + tuningConf, err := parseConf(args.StdinData, args.Args) + if err != nil { + return err + } + + // Parse previous result. + if tuningConf.RawPrevResult == nil { + return fmt.Errorf("Required prevResult missing") + } + + if err := version.ParsePrevResult(&tuningConf.NetConf); err != nil { + return err + } + + _, err = current.NewResultFromResult(tuningConf.PrevResult) + if err != nil { + return err + } + + err = ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error { + // Check each configured value vs what's currently in the container + for key, conf_value := range tuningConf.SysCtl { + fileName := filepath.Join("/proc/sys", strings.Replace(key, ".", "/", -1)) + fileName = filepath.Clean(fileName) + + contents, err := ioutil.ReadFile(fileName) + if err != nil { + return err + } + cur_value := strings.TrimSuffix(string(contents), "\n") + if conf_value != cur_value { + return fmt.Errorf("Error: Tuning configured value of %s is %s, current value is %s", fileName, conf_value, cur_value) + } + } + + link, err := netlink.LinkByName(args.IfName) + if err != nil { + return fmt.Errorf("Cannot find container link %v", args.IfName) + } + + if tuningConf.Mac != "" { + if tuningConf.Mac != link.Attrs().HardwareAddr.String() { + return fmt.Errorf("Error: Tuning configured Ethernet of %s is %s, current value is %s", + args.IfName, tuningConf.Mac, link.Attrs().HardwareAddr) + } + } + + if tuningConf.Promisc { + if link.Attrs().Promisc == 0 { + return fmt.Errorf("Error: Tuning link %s configured promisc is %v, current value is %d", + args.IfName, tuningConf.Promisc, link.Attrs().Promisc) + } + } else { + if link.Attrs().Promisc != 0 { + return fmt.Errorf("Error: Tuning link %s configured promisc is %v, current value is %d", + args.IfName, tuningConf.Promisc, link.Attrs().Promisc) + } + } + + if tuningConf.Mtu != 0 { + if tuningConf.Mtu != link.Attrs().MTU { + return fmt.Errorf("Error: Tuning configured MTU of %s is %d, current value is %d", + args.IfName, tuningConf.Mtu, link.Attrs().MTU) + } + } + return nil + }) + if err != nil { + return err + } + + return nil } diff --git a/vendor/github.com/containernetworking/plugins/plugins/meta/tuning/tuning_test.go b/vendor/github.com/containernetworking/plugins/plugins/meta/tuning/tuning_test.go index ed74123d1..87dd2619e 100644 --- a/vendor/github.com/containernetworking/plugins/plugins/meta/tuning/tuning_test.go +++ b/vendor/github.com/containernetworking/plugins/plugins/meta/tuning/tuning_test.go @@ -15,10 +15,15 @@ package main import ( + "encoding/json" + "fmt" + "github.com/containernetworking/cni/pkg/skel" + "github.com/containernetworking/cni/pkg/types" "github.com/containernetworking/cni/pkg/types/current" "github.com/containernetworking/plugins/pkg/ns" "github.com/containernetworking/plugins/pkg/testutils" + "net" "github.com/vishvananda/netlink" @@ -26,6 +31,49 @@ import ( . "github.com/onsi/gomega" ) +func buildOneConfig(name, cniVersion string, orig *TuningConf, prevResult types.Result) (*TuningConf, []byte, error) { + var err error + + inject := map[string]interface{}{ + "name": name, + "cniVersion": cniVersion, + } + // Add previous plugin result + if prevResult != nil { + inject["prevResult"] = prevResult + } + + // Ensure every config uses the same name and version + config := make(map[string]interface{}) + + confBytes, err := json.Marshal(orig) + if err != nil { + return nil, nil, err + } + + err = json.Unmarshal(confBytes, &config) + if err != nil { + return nil, nil, fmt.Errorf("unmarshal existing network bytes: %s", err) + } + + for key, value := range inject { + config[key] = value + } + + newBytes, err := json.Marshal(config) + if err != nil { + return nil, nil, err + } + + conf := &TuningConf{} + if err := json.Unmarshal(newBytes, &conf); err != nil { + return nil, nil, fmt.Errorf("error parsing configuration: %s", err) + } + + return conf, newBytes, nil + +} + var _ = Describe("tuning plugin", func() { var originalNS ns.NetNS const IFNAME string = "dummy0" @@ -109,4 +157,528 @@ var _ = Describe("tuning plugin", func() { }) Expect(err).NotTo(HaveOccurred()) }) + + It("configures and deconfigures promiscuous mode with ADD/DEL", func() { + conf := []byte(`{ + "name": "test", + "type": "iplink", + "cniVersion": "0.3.1", + "promisc": true, + "prevResult": { + "interfaces": [ + {"name": "dummy0", "sandbox":"netns"} + ], + "ips": [ + { + "version": "4", + "address": "10.0.0.2/24", + "gateway": "10.0.0.1", + "interface": 0 + } + ] + } +}`) + + args := &skel.CmdArgs{ + ContainerID: "dummy", + Netns: originalNS.Path(), + IfName: IFNAME, + StdinData: conf, + } + + err := originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + r, _, err := testutils.CmdAddWithArgs(args, func() error { + return cmdAdd(args) + }) + Expect(err).NotTo(HaveOccurred()) + + result, err := current.GetResult(r) + Expect(err).NotTo(HaveOccurred()) + + Expect(len(result.Interfaces)).To(Equal(1)) + Expect(result.Interfaces[0].Name).To(Equal(IFNAME)) + Expect(len(result.IPs)).To(Equal(1)) + Expect(result.IPs[0].Address.String()).To(Equal("10.0.0.2/24")) + + link, err := netlink.LinkByName(IFNAME) + Expect(err).NotTo(HaveOccurred()) + Expect(link.Attrs().Promisc).To(Equal(1)) + + err = testutils.CmdDel(originalNS.Path(), + args.ContainerID, "", func() error { return cmdDel(args) }) + Expect(err).NotTo(HaveOccurred()) + + return nil + }) + Expect(err).NotTo(HaveOccurred()) + }) + + It("configures and deconfigures mtu with ADD/DEL", func() { + conf := []byte(`{ + "name": "test", + "type": "iplink", + "cniVersion": "0.3.1", + "mtu": 1454, + "prevResult": { + "interfaces": [ + {"name": "dummy0", "sandbox":"netns"} + ], + "ips": [ + { + "version": "4", + "address": "10.0.0.2/24", + "gateway": "10.0.0.1", + "interface": 0 + } + ] + } +}`) + + args := &skel.CmdArgs{ + ContainerID: "dummy", + Netns: originalNS.Path(), + IfName: IFNAME, + StdinData: conf, + } + + err := originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + r, _, err := testutils.CmdAddWithArgs(args, func() error { + return cmdAdd(args) + }) + Expect(err).NotTo(HaveOccurred()) + + result, err := current.GetResult(r) + Expect(err).NotTo(HaveOccurred()) + + Expect(len(result.Interfaces)).To(Equal(1)) + Expect(result.Interfaces[0].Name).To(Equal(IFNAME)) + Expect(len(result.IPs)).To(Equal(1)) + Expect(result.IPs[0].Address.String()).To(Equal("10.0.0.2/24")) + + link, err := netlink.LinkByName(IFNAME) + Expect(err).NotTo(HaveOccurred()) + Expect(link.Attrs().MTU).To(Equal(1454)) + + err = testutils.CmdDel(originalNS.Path(), + args.ContainerID, "", func() error { return cmdDel(args) }) + Expect(err).NotTo(HaveOccurred()) + + return nil + }) + Expect(err).NotTo(HaveOccurred()) + }) + + It("configures and deconfigures mac address (from conf file) with ADD/DEL", func() { + conf := []byte(`{ + "name": "test", + "type": "iplink", + "cniVersion": "0.3.1", + "mac": "c2:11:22:33:44:55", + "prevResult": { + "interfaces": [ + {"name": "dummy0", "sandbox":"netns"} + ], + "ips": [ + { + "version": "4", + "address": "10.0.0.2/24", + "gateway": "10.0.0.1", + "interface": 0 + } + ] + } +}`) + + args := &skel.CmdArgs{ + ContainerID: "dummy", + Netns: originalNS.Path(), + IfName: IFNAME, + StdinData: conf, + } + + err := originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + r, _, err := testutils.CmdAddWithArgs(args, func() error { + return cmdAdd(args) + }) + Expect(err).NotTo(HaveOccurred()) + + result, err := current.GetResult(r) + Expect(err).NotTo(HaveOccurred()) + + Expect(len(result.Interfaces)).To(Equal(1)) + Expect(result.Interfaces[0].Name).To(Equal(IFNAME)) + Expect(len(result.IPs)).To(Equal(1)) + Expect(result.IPs[0].Address.String()).To(Equal("10.0.0.2/24")) + + link, err := netlink.LinkByName(IFNAME) + Expect(err).NotTo(HaveOccurred()) + hw, err := net.ParseMAC("c2:11:22:33:44:55") + Expect(err).NotTo(HaveOccurred()) + Expect(link.Attrs().HardwareAddr).To(Equal(hw)) + + err = testutils.CmdDel(originalNS.Path(), + args.ContainerID, "", func() error { return cmdDel(args) }) + Expect(err).NotTo(HaveOccurred()) + + return nil + }) + Expect(err).NotTo(HaveOccurred()) + }) + + It("configures and deconfigures mac address (from CNI_ARGS) with ADD/DEL", func() { + conf := []byte(`{ + "name": "test", + "type": "iplink", + "cniVersion": "0.3.1", + "prevResult": { + "interfaces": [ + {"name": "dummy0", "sandbox":"netns"} + ], + "ips": [ + { + "version": "4", + "address": "10.0.0.2/24", + "gateway": "10.0.0.1", + "interface": 0 + } + ] + } +}`) + + args := &skel.CmdArgs{ + ContainerID: "dummy", + Netns: originalNS.Path(), + IfName: IFNAME, + StdinData: conf, + Args: "IgnoreUnknown=true;MAC=c2:11:22:33:44:66", + } + + err := originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + r, _, err := testutils.CmdAddWithArgs(args, func() error { + return cmdAdd(args) + }) + Expect(err).NotTo(HaveOccurred()) + + result, err := current.GetResult(r) + Expect(err).NotTo(HaveOccurred()) + + Expect(len(result.Interfaces)).To(Equal(1)) + Expect(result.Interfaces[0].Name).To(Equal(IFNAME)) + Expect(len(result.IPs)).To(Equal(1)) + Expect(result.IPs[0].Address.String()).To(Equal("10.0.0.2/24")) + + link, err := netlink.LinkByName(IFNAME) + Expect(err).NotTo(HaveOccurred()) + hw, err := net.ParseMAC("c2:11:22:33:44:66") + Expect(err).NotTo(HaveOccurred()) + Expect(link.Attrs().HardwareAddr).To(Equal(hw)) + + err = testutils.CmdDel(originalNS.Path(), + args.ContainerID, "", func() error { return cmdDel(args) }) + Expect(err).NotTo(HaveOccurred()) + + return nil + }) + Expect(err).NotTo(HaveOccurred()) + }) + + It("configures and deconfigures promiscuous mode with CNI 0.4.0 ADD/DEL", func() { + conf := []byte(`{ + "name": "test", + "type": "iplink", + "cniVersion": "0.4.0", + "promisc": true, + "prevResult": { + "interfaces": [ + {"name": "dummy0", "sandbox":"netns"} + ], + "ips": [ + { + "version": "4", + "address": "10.0.0.2/24", + "gateway": "10.0.0.1", + "interface": 0 + } + ] + } +}`) + + args := &skel.CmdArgs{ + ContainerID: "dummy", + Netns: originalNS.Path(), + IfName: IFNAME, + StdinData: conf, + } + + err := originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + r, _, err := testutils.CmdAddWithArgs(args, func() error { + return cmdAdd(args) + }) + Expect(err).NotTo(HaveOccurred()) + + result, err := current.GetResult(r) + Expect(err).NotTo(HaveOccurred()) + + Expect(len(result.Interfaces)).To(Equal(1)) + Expect(result.Interfaces[0].Name).To(Equal(IFNAME)) + Expect(len(result.IPs)).To(Equal(1)) + Expect(result.IPs[0].Address.String()).To(Equal("10.0.0.2/24")) + + link, err := netlink.LinkByName(IFNAME) + Expect(err).NotTo(HaveOccurred()) + Expect(link.Attrs().Promisc).To(Equal(1)) + + n := &TuningConf{} + err = json.Unmarshal([]byte(conf), &n) + Expect(err).NotTo(HaveOccurred()) + + cniVersion := "0.4.0" + _, confString, err := buildOneConfig("testConfig", cniVersion, n, r) + Expect(err).NotTo(HaveOccurred()) + + args.StdinData = confString + + err = testutils.CmdCheckWithArgs(args, func() error { + return cmdCheck(args) + }) + Expect(err).NotTo(HaveOccurred()) + + err = testutils.CmdDel(originalNS.Path(), + args.ContainerID, "", func() error { return cmdDel(args) }) + Expect(err).NotTo(HaveOccurred()) + + return nil + }) + Expect(err).NotTo(HaveOccurred()) + }) + + It("configures and deconfigures mtu with CNI 0.4.0 ADD/DEL", func() { + conf := []byte(`{ + "name": "test", + "type": "iplink", + "cniVersion": "0.4.0", + "mtu": 1454, + "prevResult": { + "interfaces": [ + {"name": "dummy0", "sandbox":"netns"} + ], + "ips": [ + { + "version": "4", + "address": "10.0.0.2/24", + "gateway": "10.0.0.1", + "interface": 0 + } + ] + } +}`) + + args := &skel.CmdArgs{ + ContainerID: "dummy", + Netns: originalNS.Path(), + IfName: IFNAME, + StdinData: conf, + } + + err := originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + r, _, err := testutils.CmdAddWithArgs(args, func() error { + return cmdAdd(args) + }) + Expect(err).NotTo(HaveOccurred()) + + result, err := current.GetResult(r) + Expect(err).NotTo(HaveOccurred()) + + Expect(len(result.Interfaces)).To(Equal(1)) + Expect(result.Interfaces[0].Name).To(Equal(IFNAME)) + Expect(len(result.IPs)).To(Equal(1)) + Expect(result.IPs[0].Address.String()).To(Equal("10.0.0.2/24")) + + link, err := netlink.LinkByName(IFNAME) + Expect(err).NotTo(HaveOccurred()) + Expect(link.Attrs().MTU).To(Equal(1454)) + + n := &TuningConf{} + err = json.Unmarshal([]byte(conf), &n) + Expect(err).NotTo(HaveOccurred()) + + cniVersion := "0.4.0" + _, confString, err := buildOneConfig("testConfig", cniVersion, n, r) + Expect(err).NotTo(HaveOccurred()) + + args.StdinData = confString + + err = testutils.CmdCheckWithArgs(args, func() error { + return cmdCheck(args) + }) + Expect(err).NotTo(HaveOccurred()) + + err = testutils.CmdDel(originalNS.Path(), + args.ContainerID, "", func() error { return cmdDel(args) }) + Expect(err).NotTo(HaveOccurred()) + + return nil + }) + Expect(err).NotTo(HaveOccurred()) + }) + + It("configures and deconfigures mac address (from conf file) with CNI v4.0 ADD/DEL", func() { + conf := []byte(`{ + "name": "test", + "type": "iplink", + "cniVersion": "0.4.0", + "mac": "c2:11:22:33:44:55", + "prevResult": { + "interfaces": [ + {"name": "dummy0", "sandbox":"netns"} + ], + "ips": [ + { + "version": "4", + "address": "10.0.0.2/24", + "gateway": "10.0.0.1", + "interface": 0 + } + ] + } +}`) + + args := &skel.CmdArgs{ + ContainerID: "dummy", + Netns: originalNS.Path(), + IfName: IFNAME, + StdinData: conf, + } + + err := originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + r, _, err := testutils.CmdAddWithArgs(args, func() error { + return cmdAdd(args) + }) + Expect(err).NotTo(HaveOccurred()) + + result, err := current.GetResult(r) + Expect(err).NotTo(HaveOccurred()) + + Expect(len(result.Interfaces)).To(Equal(1)) + Expect(result.Interfaces[0].Name).To(Equal(IFNAME)) + Expect(len(result.IPs)).To(Equal(1)) + Expect(result.IPs[0].Address.String()).To(Equal("10.0.0.2/24")) + + link, err := netlink.LinkByName(IFNAME) + Expect(err).NotTo(HaveOccurred()) + hw, err := net.ParseMAC("c2:11:22:33:44:55") + Expect(err).NotTo(HaveOccurred()) + Expect(link.Attrs().HardwareAddr).To(Equal(hw)) + + n := &TuningConf{} + err = json.Unmarshal([]byte(conf), &n) + Expect(err).NotTo(HaveOccurred()) + + cniVersion := "0.4.0" + _, confString, err := buildOneConfig("testConfig", cniVersion, n, r) + Expect(err).NotTo(HaveOccurred()) + + args.StdinData = confString + + err = testutils.CmdCheckWithArgs(args, func() error { + return cmdCheck(args) + }) + Expect(err).NotTo(HaveOccurred()) + + err = testutils.CmdDel(originalNS.Path(), + args.ContainerID, "", func() error { return cmdDel(args) }) + Expect(err).NotTo(HaveOccurred()) + + return nil + }) + Expect(err).NotTo(HaveOccurred()) + }) + + It("configures and deconfigures mac address (from CNI_ARGS) with CNI v4 ADD/DEL", func() { + conf := []byte(`{ + "name": "test", + "type": "iplink", + "cniVersion": "0.4.0", + "prevResult": { + "interfaces": [ + {"name": "dummy0", "sandbox":"netns"} + ], + "ips": [ + { + "version": "4", + "address": "10.0.0.2/24", + "gateway": "10.0.0.1", + "interface": 0 + } + ] + } +}`) + + args := &skel.CmdArgs{ + ContainerID: "dummy", + Netns: originalNS.Path(), + IfName: IFNAME, + StdinData: conf, + Args: "IgnoreUnknown=true;MAC=c2:11:22:33:44:66", + } + + err := originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + r, _, err := testutils.CmdAddWithArgs(args, func() error { + return cmdAdd(args) + }) + Expect(err).NotTo(HaveOccurred()) + + result, err := current.GetResult(r) + Expect(err).NotTo(HaveOccurred()) + + Expect(len(result.Interfaces)).To(Equal(1)) + Expect(result.Interfaces[0].Name).To(Equal(IFNAME)) + Expect(len(result.IPs)).To(Equal(1)) + Expect(result.IPs[0].Address.String()).To(Equal("10.0.0.2/24")) + + link, err := netlink.LinkByName(IFNAME) + Expect(err).NotTo(HaveOccurred()) + hw, err := net.ParseMAC("c2:11:22:33:44:66") + Expect(err).NotTo(HaveOccurred()) + Expect(link.Attrs().HardwareAddr).To(Equal(hw)) + + n := &TuningConf{} + err = json.Unmarshal([]byte(conf), &n) + Expect(err).NotTo(HaveOccurred()) + + cniVersion := "0.4.0" + _, confString, err := buildOneConfig("testConfig", cniVersion, n, r) + Expect(err).NotTo(HaveOccurred()) + + args.StdinData = confString + + err = testutils.CmdCheckWithArgs(args, func() error { + return cmdCheck(args) + }) + Expect(err).NotTo(HaveOccurred()) + + err = testutils.CmdDel(originalNS.Path(), + args.ContainerID, "", func() error { return cmdDel(args) }) + Expect(err).NotTo(HaveOccurred()) + + return nil + }) + Expect(err).NotTo(HaveOccurred()) + }) }) diff --git a/vendor/github.com/containernetworking/plugins/plugins/sample/main.go b/vendor/github.com/containernetworking/plugins/plugins/sample/main.go index 1abdc1652..2fcef4128 100644 --- a/vendor/github.com/containernetworking/plugins/plugins/sample/main.go +++ b/vendor/github.com/containernetworking/plugins/plugins/sample/main.go @@ -25,6 +25,8 @@ import ( "github.com/containernetworking/cni/pkg/types" "github.com/containernetworking/cni/pkg/types/current" "github.com/containernetworking/cni/pkg/version" + + bv "github.com/containernetworking/plugins/pkg/utils/buildversion" ) // PluginConf is whatever you expect your configuration json to be. This is whatever @@ -92,13 +94,22 @@ func cmdAdd(args *skel.CmdArgs) error { return err } + // Remove this if this is an "originating" plugin if conf.PrevResult == nil { return fmt.Errorf("must be called as chained plugin") } + // Uncomment if this is an "originating" plugin + + //if conf.PrevResult != nil { + // return fmt.Errorf("must be called as the first plugin") + // } + // This is some sample code to generate the list of container-side IPs. // We're casting the prevResult to a 0.3.0 response, which can also include // host-side IPs (but doesn't when converted from a 0.2.0 response). + // + // You don't need this if you are writing an "originating" plugin. containerIPs := make([]net.IP, 0, len(conf.PrevResult.IPs)) if conf.CNIVersion != "0.3.0" { for _, ip := range conf.PrevResult.IPs { @@ -123,6 +134,8 @@ func cmdAdd(args *skel.CmdArgs) error { return fmt.Errorf("got no container IPs") } + // Implement your plugin here + // Pass through the result for the next plugin return types.PrintResult(conf.PrevResult, conf.CNIVersion) } @@ -141,5 +154,11 @@ func cmdDel(args *skel.CmdArgs) error { } func main() { - skel.PluginMain(cmdAdd, cmdDel, version.PluginSupports("", "0.1.0", "0.2.0", version.Current())) + // replace TODO with your plugin name + skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("TODO")) +} + +func cmdCheck(args *skel.CmdArgs) error { + // TODO: implement + return fmt.Errorf("not implemented") } diff --git a/vendor/github.com/containernetworking/plugins/plugins/windows_only.txt b/vendor/github.com/containernetworking/plugins/plugins/windows_only.txt new file mode 100644 index 000000000..47f310943 --- /dev/null +++ b/vendor/github.com/containernetworking/plugins/plugins/windows_only.txt @@ -0,0 +1,4 @@ +plugins/ipam/host-local +plugins/main/windows/win-bridge +plugins/main/windows/win-overlay +plugins/meta/flannel \ No newline at end of file diff --git a/vendor/github.com/containernetworking/plugins/scripts/release.sh b/vendor/github.com/containernetworking/plugins/scripts/release.sh index b01b27f88..5b9d40d11 100755 --- a/vendor/github.com/containernetworking/plugins/scripts/release.sh +++ b/vendor/github.com/containernetworking/plugins/scripts/release.sh @@ -2,11 +2,12 @@ set -xe SRC_DIR="${SRC_DIR:-$PWD}" +DOCKER="${DOCKER:-docker}" TAG=$(git describe --tags --dirty) RELEASE_DIR=release-${TAG} -BUILDFLAGS="-ldflags '-extldflags -static -X main._buildVersion=${TAG}'" +BUILDFLAGS="-ldflags '-extldflags -static -X github.com/containernetworking/plugins/pkg/utils/buildversion.BuildVersion=${TAG}'" OUTPUT_DIR=bin @@ -15,19 +16,30 @@ rm -Rf ${SRC_DIR}/${RELEASE_DIR} mkdir -p ${SRC_DIR}/${RELEASE_DIR} mkdir -p ${OUTPUT_DIR} -docker run -ti -v ${SRC_DIR}:/go/src/github.com/containernetworking/plugins --rm golang:1.10-alpine \ +$DOCKER run -v ${SRC_DIR}:/go/src/github.com/containernetworking/plugins --rm golang:1.10-alpine \ /bin/sh -xe -c "\ apk --no-cache add bash tar; cd /go/src/github.com/containernetworking/plugins; umask 0022; + for arch in amd64 arm arm64 ppc64le s390x; do \ rm -f ${OUTPUT_DIR}/*; \ - CGO_ENABLED=0 GOARCH=\$arch ./build.sh ${BUILDFLAGS}; \ + CGO_ENABLED=0 GOARCH=\$arch ./build_linux.sh ${BUILDFLAGS}; \ for format in tgz; do \ - FILENAME=cni-plugins-\$arch-${TAG}.\$format; \ + FILENAME=cni-plugins-linux-\$arch-${TAG}.\$format; \ FILEPATH=${RELEASE_DIR}/\$FILENAME; \ tar -C ${OUTPUT_DIR} --owner=0 --group=0 -caf \$FILEPATH .; \ done; \ done; + + rm -rf ${OUTPUT_DIR}/*; \ + CGO_ENABLED=0 GOARCH=amd64 ./build_windows.sh ${BUILDFLAGS}; \ + for format in tgz; do \ + FILENAME=cni-plugins-windows-amd64-${TAG}.\$format; \ + FILEPATH=${RELEASE_DIR}/\$FILENAME; \ + tar -C ${OUTPUT_DIR} --owner=0 --group=0 -caf \$FILEPATH .; \ + done; + + cd ${RELEASE_DIR}; for f in *.tgz; do sha1sum \$f > \$f.sha1; done; for f in *.tgz; do sha256sum \$f > \$f.sha256; done; diff --git a/vendor/github.com/containernetworking/plugins/test.sh b/vendor/github.com/containernetworking/plugins/test.sh deleted file mode 100755 index 0c48852d6..000000000 --- a/vendor/github.com/containernetworking/plugins/test.sh +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/env bash -# -# Run CNI plugin tests. -# -# This needs sudo, as we'll be creating net interfaces. -# -set -e - -source ./build.sh - -echo "Running tests" - -GINKGO_FLAGS="-p --randomizeAllSpecs --randomizeSuites --failOnPending --progress" - -# user has not provided PKG override -if [ -z "$PKG" ]; then - GINKGO_FLAGS="$GINKGO_FLAGS -r ." - LINT_TARGETS="./..." - -# user has provided PKG override -else - GINKGO_FLAGS="$GINKGO_FLAGS $PKG" - LINT_TARGETS="$PKG" -fi - -sudo -E bash -c "umask 0; cd ${GOPATH}/src/${REPO_PATH}; PATH=${GOROOT}/bin:$(pwd)/bin:${PATH} ginkgo ${GINKGO_FLAGS}" - -cd ${GOPATH}/src/${REPO_PATH}; -echo "Checking gofmt..." -fmtRes=$(go fmt $LINT_TARGETS) -if [ -n "${fmtRes}" ]; then - echo -e "go fmt checking failed:\n${fmtRes}" - exit 255 -fi - -echo "Checking govet..." -vetRes=$(go vet $LINT_TARGETS) -if [ -n "${vetRes}" ]; then - echo -e "govet checking failed:\n${vetRes}" - exit 255 -fi diff --git a/vendor/github.com/containernetworking/plugins/test_linux.sh b/vendor/github.com/containernetworking/plugins/test_linux.sh new file mode 100755 index 000000000..858534b3d --- /dev/null +++ b/vendor/github.com/containernetworking/plugins/test_linux.sh @@ -0,0 +1,59 @@ +#!/usr/bin/env bash +# +# Run CNI plugin tests. +# +# This needs sudo, as we'll be creating net interfaces. +# +set -e + +# switch into the repo root directory +cd "$(dirname $0)" + +# Build all plugins before testing +source ./build_linux.sh + +echo "Running tests" + +function testrun { + sudo -E bash -c "umask 0; cd ${GOPATH}/src; PATH=${GOROOT}/bin:$(pwd)/bin:${PATH} go test $@" +} + +COVERALLS=${COVERALLS:-""} + +if [ -n "${COVERALLS}" ]; then + echo "with coverage profile generation..." +else + echo "without coverage profile generation..." +fi + +PKG=${PKG:-$(cd ${GOPATH}/src/${REPO_PATH}; go list ./... | xargs echo)} + +# coverage profile only works per-package +i=0 +for t in ${PKG}; do + if [ -n "${COVERALLS}" ]; then + COVERFLAGS="-covermode set -coverprofile ${i}.coverprofile" + fi + testrun "${COVERFLAGS:-""} ${t}" + i=$((i+1)) +done + +# Submit coverage information +if [ -n "${COVERALLS}" ]; then + gover + goveralls -service=travis-ci -coverprofile=gover.coverprofile +fi + +echo "Checking gofmt..." +fmtRes=$(go fmt $PKG) +if [ -n "${fmtRes}" ]; then + echo -e "go fmt checking failed:\n${fmtRes}" + exit 255 +fi + +echo "Checking govet..." +vetRes=$(go vet $PKG) +if [ -n "${vetRes}" ]; then + echo -e "govet checking failed:\n${vetRes}" + exit 255 +fi diff --git a/vendor/github.com/containernetworking/plugins/test_windows.sh b/vendor/github.com/containernetworking/plugins/test_windows.sh new file mode 100755 index 000000000..36610dd83 --- /dev/null +++ b/vendor/github.com/containernetworking/plugins/test_windows.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +# +# Run CNI plugin tests. +# +set -e + +source ./build_windows.sh + +echo "Running tests" + +PLUGINS=$(cat plugins/windows_only.txt | tr '\n' ' ') +GINKGO_FLAGS="-p -r --randomizeAllSpecs --randomizeSuites --failOnPending --progress pkg/hns $PLUGINS" + +bash -c "cd ${GOPATH}/src/${REPO_PATH}; PATH='${GOROOT}/bin:$(pwd)/bin:${PATH}' ginkgo ${GINKGO_FLAGS}" diff --git a/vendor/github.com/pkg/errors/.travis.yml b/vendor/github.com/pkg/errors/.travis.yml index 0ac2b7ad5..588ceca18 100644 --- a/vendor/github.com/pkg/errors/.travis.yml +++ b/vendor/github.com/pkg/errors/.travis.yml @@ -1,10 +1,11 @@ language: go go_import_path: github.com/pkg/errors go: - - 1.9.x - - 1.10.x - - 1.11.x + - 1.4.3 + - 1.5.4 + - 1.6.2 + - 1.7.1 - tip script: - - make check + - go test -v ./... diff --git a/vendor/github.com/pkg/errors/Makefile b/vendor/github.com/pkg/errors/Makefile deleted file mode 100644 index ce9d7cded..000000000 --- a/vendor/github.com/pkg/errors/Makefile +++ /dev/null @@ -1,44 +0,0 @@ -PKGS := github.com/pkg/errors -SRCDIRS := $(shell go list -f '{{.Dir}}' $(PKGS)) -GO := go - -check: test vet gofmt misspell unconvert staticcheck ineffassign unparam - -test: - $(GO) test $(PKGS) - -vet: | test - $(GO) vet $(PKGS) - -staticcheck: - $(GO) get honnef.co/go/tools/cmd/staticcheck - staticcheck -checks all $(PKGS) - -misspell: - $(GO) get github.com/client9/misspell/cmd/misspell - misspell \ - -locale GB \ - -error \ - *.md *.go - -unconvert: - $(GO) get github.com/mdempsky/unconvert - unconvert -v $(PKGS) - -ineffassign: - $(GO) get github.com/gordonklaus/ineffassign - find $(SRCDIRS) -name '*.go' | xargs ineffassign - -pedantic: check errcheck - -unparam: - $(GO) get mvdan.cc/unparam - unparam ./... - -errcheck: - $(GO) get github.com/kisielk/errcheck - errcheck $(PKGS) - -gofmt: - @echo Checking code is gofmted - @test -z "$(shell gofmt -s -l -d -e $(SRCDIRS) | tee /dev/stderr)" diff --git a/vendor/github.com/pkg/errors/README.md b/vendor/github.com/pkg/errors/README.md index cf771e7dc..273db3c98 100644 --- a/vendor/github.com/pkg/errors/README.md +++ b/vendor/github.com/pkg/errors/README.md @@ -1,4 +1,4 @@ -# errors [![Travis-CI](https://travis-ci.org/pkg/errors.svg)](https://travis-ci.org/pkg/errors) [![AppVeyor](https://ci.appveyor.com/api/projects/status/b98mptawhudj53ep/branch/master?svg=true)](https://ci.appveyor.com/project/davecheney/errors/branch/master) [![GoDoc](https://godoc.org/github.com/pkg/errors?status.svg)](http://godoc.org/github.com/pkg/errors) [![Report card](https://goreportcard.com/badge/github.com/pkg/errors)](https://goreportcard.com/report/github.com/pkg/errors) [![Sourcegraph](https://sourcegraph.com/github.com/pkg/errors/-/badge.svg)](https://sourcegraph.com/github.com/pkg/errors?badge) +# errors [![Travis-CI](https://travis-ci.org/pkg/errors.svg)](https://travis-ci.org/pkg/errors) [![AppVeyor](https://ci.appveyor.com/api/projects/status/b98mptawhudj53ep/branch/master?svg=true)](https://ci.appveyor.com/project/davecheney/errors/branch/master) [![GoDoc](https://godoc.org/github.com/pkg/errors?status.svg)](http://godoc.org/github.com/pkg/errors) [![Report card](https://goreportcard.com/badge/github.com/pkg/errors)](https://goreportcard.com/report/github.com/pkg/errors) Package errors provides simple error handling primitives. @@ -41,19 +41,12 @@ default: [Read the package documentation for more information](https://godoc.org/github.com/pkg/errors). -## Roadmap - -With the upcoming [Go2 error proposals](https://go.googlesource.com/proposal/+/master/design/go2draft.md) this package is moving into maintenance mode. The roadmap for a 1.0 release is as follows: - -- 0.9. Remove pre Go 1.9 support, address outstanding pull requests (if possible) -- 1.0. Final release. - ## Contributing -Because of the Go2 errors changes, this package is not accepting proposals for new functionality. With that said, we welcome pull requests, bug fixes and issue reports. +We welcome pull requests, bug fixes and issue reports. With that said, the bar for adding new symbols to this package is intentionally set high. -Before sending a PR, please discuss your change by raising an issue. +Before proposing a change, please discuss your change by raising an issue. -## License +## Licence BSD-2-Clause diff --git a/vendor/github.com/pkg/errors/bench_test.go b/vendor/github.com/pkg/errors/bench_test.go index c906870e0..0416a3cbb 100644 --- a/vendor/github.com/pkg/errors/bench_test.go +++ b/vendor/github.com/pkg/errors/bench_test.go @@ -15,7 +15,6 @@ func noErrors(at, depth int) error { } return noErrors(at+1, depth) } - func yesErrors(at, depth int) error { if at >= depth { return New("ye error") @@ -23,11 +22,8 @@ func yesErrors(at, depth int) error { return yesErrors(at+1, depth) } -// GlobalE is an exported global to store the result of benchmark results, -// preventing the compiler from optimising the benchmark functions away. -var GlobalE interface{} - func BenchmarkErrors(b *testing.B) { + var toperr error type run struct { stack int std bool @@ -57,54 +53,7 @@ func BenchmarkErrors(b *testing.B) { err = f(0, r.stack) } b.StopTimer() - GlobalE = err - }) - } -} - -func BenchmarkStackFormatting(b *testing.B) { - type run struct { - stack int - format string - } - runs := []run{ - {10, "%s"}, - {10, "%v"}, - {10, "%+v"}, - {30, "%s"}, - {30, "%v"}, - {30, "%+v"}, - {60, "%s"}, - {60, "%v"}, - {60, "%+v"}, - } - - var stackStr string - for _, r := range runs { - name := fmt.Sprintf("%s-stack-%d", r.format, r.stack) - b.Run(name, func(b *testing.B) { - err := yesErrors(0, r.stack) - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - stackStr = fmt.Sprintf(r.format, err) - } - b.StopTimer() - }) - } - - for _, r := range runs { - name := fmt.Sprintf("%s-stacktrace-%d", r.format, r.stack) - b.Run(name, func(b *testing.B) { - err := yesErrors(0, r.stack) - st := err.(*fundamental).stack.StackTrace() - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - stackStr = fmt.Sprintf(r.format, st) - } - b.StopTimer() + toperr = err }) } - GlobalE = stackStr } diff --git a/vendor/github.com/pkg/errors/errors.go b/vendor/github.com/pkg/errors/errors.go index 8617beef1..842ee8045 100644 --- a/vendor/github.com/pkg/errors/errors.go +++ b/vendor/github.com/pkg/errors/errors.go @@ -6,7 +6,7 @@ // return err // } // -// which when applied recursively up the call stack results in error reports +// which applied recursively up the call stack results in error reports // without context or debugging information. The errors package allows // programmers to add context to the failure path in their code in a way // that does not destroy the original value of the error. @@ -15,17 +15,16 @@ // // The errors.Wrap function returns a new error that adds context to the // original error by recording a stack trace at the point Wrap is called, -// together with the supplied message. For example +// and the supplied message. For example // // _, err := ioutil.ReadAll(r) // if err != nil { // return errors.Wrap(err, "read failed") // } // -// If additional control is required, the errors.WithStack and -// errors.WithMessage functions destructure errors.Wrap into its component -// operations: annotating an error with a stack trace and with a message, -// respectively. +// If additional control is required the errors.WithStack and errors.WithMessage +// functions destructure errors.Wrap into its component operations of annotating +// an error with a stack trace and an a message, respectively. // // Retrieving the cause of an error // @@ -39,7 +38,7 @@ // } // // can be inspected by errors.Cause. errors.Cause will recursively retrieve -// the topmost error that does not implement causer, which is assumed to be +// the topmost error which does not implement causer, which is assumed to be // the original cause. For example: // // switch err := errors.Cause(err).(type) { @@ -49,16 +48,16 @@ // // unknown error // } // -// Although the causer interface is not exported by this package, it is -// considered a part of its stable public interface. +// causer interface is not exported by this package, but is considered a part +// of stable public API. // // Formatted printing of errors // // All error values returned from this package implement fmt.Formatter and can -// be formatted by the fmt package. The following verbs are supported: +// be formatted by the fmt package. The following verbs are supported // // %s print the error. If the error has a Cause it will be -// printed recursively. +// printed recursively // %v see %s // %+v extended format. Each Frame of the error's StackTrace will // be printed in detail. @@ -66,13 +65,13 @@ // Retrieving the stack trace of an error or wrapper // // New, Errorf, Wrap, and Wrapf record a stack trace at the point they are -// invoked. This information can be retrieved with the following interface: +// invoked. This information can be retrieved with the following interface. // // type stackTracer interface { // StackTrace() errors.StackTrace // } // -// The returned errors.StackTrace type is defined as +// Where errors.StackTrace is defined as // // type StackTrace []Frame // @@ -82,12 +81,12 @@ // // if err, ok := err.(stackTracer); ok { // for _, f := range err.StackTrace() { -// fmt.Printf("%+s:%d\n", f, f) +// fmt.Printf("%+s:%d", f) // } // } // -// Although the stackTracer interface is not exported by this package, it is -// considered a part of its stable public interface. +// stackTracer interface is not exported by this package, but is considered a part +// of stable public API. // // See the documentation for Frame.Format for more details. package errors @@ -193,7 +192,7 @@ func Wrap(err error, message string) error { } // Wrapf returns an error annotating err with a stack trace -// at the point Wrapf is called, and the format specifier. +// at the point Wrapf is call, and the format specifier. // If err is nil, Wrapf returns nil. func Wrapf(err error, format string, args ...interface{}) error { if err == nil { @@ -221,18 +220,6 @@ func WithMessage(err error, message string) error { } } -// WithMessagef annotates err with the format specifier. -// If err is nil, WithMessagef returns nil. -func WithMessagef(err error, format string, args ...interface{}) error { - if err == nil { - return nil - } - return &withMessage{ - cause: err, - msg: fmt.Sprintf(format, args...), - } -} - type withMessage struct { cause error msg string diff --git a/vendor/github.com/pkg/errors/errors_test.go b/vendor/github.com/pkg/errors/errors_test.go index 2089b2f76..1d8c63558 100644 --- a/vendor/github.com/pkg/errors/errors_test.go +++ b/vendor/github.com/pkg/errors/errors_test.go @@ -196,32 +196,7 @@ func TestWithMessage(t *testing.T) { t.Errorf("WithMessage(%v, %q): got: %q, want %q", tt.err, tt.message, got, tt.want) } } -} - -func TestWithMessagefNil(t *testing.T) { - got := WithMessagef(nil, "no error") - if got != nil { - t.Errorf("WithMessage(nil, \"no error\"): got %#v, expected nil", got) - } -} -func TestWithMessagef(t *testing.T) { - tests := []struct { - err error - message string - want string - }{ - {io.EOF, "read error", "read error: EOF"}, - {WithMessagef(io.EOF, "read error without format specifier"), "client error", "client error: read error without format specifier: EOF"}, - {WithMessagef(io.EOF, "read error with %d format specifier", 1), "client error", "client error: read error with 1 format specifier: EOF"}, - } - - for _, tt := range tests { - got := WithMessagef(tt.err, tt.message).Error() - if got != tt.want { - t.Errorf("WithMessage(%v, %q): got: %q, want %q", tt.err, tt.message, got, tt.want) - } - } } // errors.New, etc values are not expected to be compared by value diff --git a/vendor/github.com/pkg/errors/format_test.go b/vendor/github.com/pkg/errors/format_test.go index cb1df821f..15fd7d89d 100644 --- a/vendor/github.com/pkg/errors/format_test.go +++ b/vendor/github.com/pkg/errors/format_test.go @@ -360,32 +360,7 @@ func TestFormatGeneric(t *testing.T) { } } -func wrappedNew(message string) error { // This function will be mid-stack inlined in go 1.12+ - return New(message) -} - -func TestFormatWrappedNew(t *testing.T) { - tests := []struct { - error - format string - want string - }{{ - wrappedNew("error"), - "%+v", - "error\n" + - "github.com/pkg/errors.wrappedNew\n" + - "\t.+/github.com/pkg/errors/format_test.go:364\n" + - "github.com/pkg/errors.TestFormatWrappedNew\n" + - "\t.+/github.com/pkg/errors/format_test.go:373", - }} - - for i, tt := range tests { - testFormatRegexp(t, i, tt.error, tt.format, tt.want) - } -} - func testFormatRegexp(t *testing.T, n int, arg interface{}, format, want string) { - t.Helper() got := fmt.Sprintf(format, arg) gotLines := strings.SplitN(got, "\n", -1) wantLines := strings.SplitN(want, "\n", -1) @@ -516,7 +491,7 @@ type wrapper struct { want []string } -func prettyBlocks(blocks []string) string { +func prettyBlocks(blocks []string, prefix ...string) string { var out []string for _, b := range blocks { diff --git a/vendor/github.com/pkg/errors/stack.go b/vendor/github.com/pkg/errors/stack.go index 81f885641..6b1f2891a 100644 --- a/vendor/github.com/pkg/errors/stack.go +++ b/vendor/github.com/pkg/errors/stack.go @@ -5,13 +5,10 @@ import ( "io" "path" "runtime" - "strconv" "strings" ) // Frame represents a program counter inside a stack frame. -// For historical reasons if Frame is interpreted as a uintptr -// its value represents the program counter + 1. type Frame uintptr // pc returns the program counter for this frame; @@ -40,15 +37,6 @@ func (f Frame) line() int { return line } -// name returns the name of this function, if known. -func (f Frame) name() string { - fn := runtime.FuncForPC(f.pc()) - if fn == nil { - return "unknown" - } - return fn.Name() -} - // Format formats the frame according to the fmt.Formatter interface. // // %s source file @@ -58,24 +46,29 @@ func (f Frame) name() string { // // Format accepts flags that alter the printing of some verbs, as follows: // -// %+s function name and path of source file relative to the compile time -// GOPATH separated by \n\t (\n\t) +// %+s path of source file relative to the compile time GOPATH // %+v equivalent to %+s:%d func (f Frame) Format(s fmt.State, verb rune) { switch verb { case 's': switch { case s.Flag('+'): - io.WriteString(s, f.name()) - io.WriteString(s, "\n\t") - io.WriteString(s, f.file()) + pc := f.pc() + fn := runtime.FuncForPC(pc) + if fn == nil { + io.WriteString(s, "unknown") + } else { + file, _ := fn.FileLine(pc) + fmt.Fprintf(s, "%s\n\t%s", fn.Name(), file) + } default: io.WriteString(s, path.Base(f.file())) } case 'd': - io.WriteString(s, strconv.Itoa(f.line())) + fmt.Fprintf(s, "%d", f.line()) case 'n': - io.WriteString(s, funcname(f.name())) + name := runtime.FuncForPC(f.pc()).Name() + io.WriteString(s, funcname(name)) case 'v': f.Format(s, 's') io.WriteString(s, ":") @@ -86,44 +79,22 @@ func (f Frame) Format(s fmt.State, verb rune) { // StackTrace is stack of Frames from innermost (newest) to outermost (oldest). type StackTrace []Frame -// Format formats the stack of Frames according to the fmt.Formatter interface. -// -// %s lists source files for each Frame in the stack -// %v lists the source file and line number for each Frame in the stack -// -// Format accepts flags that alter the printing of some verbs, as follows: -// -// %+v Prints filename, function, and line number for each Frame in the stack. func (st StackTrace) Format(s fmt.State, verb rune) { switch verb { case 'v': switch { case s.Flag('+'): for _, f := range st { - io.WriteString(s, "\n") - f.Format(s, verb) + fmt.Fprintf(s, "\n%+v", f) } case s.Flag('#'): fmt.Fprintf(s, "%#v", []Frame(st)) default: - st.formatSlice(s, verb) + fmt.Fprintf(s, "%v", []Frame(st)) } case 's': - st.formatSlice(s, verb) - } -} - -// formatSlice will format this StackTrace into the given buffer as a slice of -// Frame, only valid when called with '%s' or '%v'. -func (st StackTrace) formatSlice(s fmt.State, verb rune) { - io.WriteString(s, "[") - for i, f := range st { - if i > 0 { - io.WriteString(s, " ") - } - f.Format(s, verb) + fmt.Fprintf(s, "%s", []Frame(st)) } - io.WriteString(s, "]") } // stack represents a stack of program counters. @@ -165,3 +136,43 @@ func funcname(name string) string { i = strings.Index(name, ".") return name[i+1:] } + +func trimGOPATH(name, file string) string { + // Here we want to get the source file path relative to the compile time + // GOPATH. As of Go 1.6.x there is no direct way to know the compiled + // GOPATH at runtime, but we can infer the number of path segments in the + // GOPATH. We note that fn.Name() returns the function name qualified by + // the import path, which does not include the GOPATH. Thus we can trim + // segments from the beginning of the file path until the number of path + // separators remaining is one more than the number of path separators in + // the function name. For example, given: + // + // GOPATH /home/user + // file /home/user/src/pkg/sub/file.go + // fn.Name() pkg/sub.Type.Method + // + // We want to produce: + // + // pkg/sub/file.go + // + // From this we can easily see that fn.Name() has one less path separator + // than our desired output. We count separators from the end of the file + // path until it finds two more than in the function name and then move + // one character forward to preserve the initial path segment without a + // leading separator. + const sep = "/" + goal := strings.Count(name, sep) + 2 + i := len(file) + for n := 0; n < goal; n++ { + i = strings.LastIndex(file[:i], sep) + if i == -1 { + // not enough separators found, set i so that the slice expression + // below leaves file unmodified + i = -len(sep) + break + } + } + // get back to 0 or trim the leading separator + file = file[i+len(sep):] + return file +} diff --git a/vendor/github.com/pkg/errors/stack_test.go b/vendor/github.com/pkg/errors/stack_test.go index 1acd719bc..510c27a9f 100644 --- a/vendor/github.com/pkg/errors/stack_test.go +++ b/vendor/github.com/pkg/errors/stack_test.go @@ -6,18 +6,51 @@ import ( "testing" ) -var initpc = caller() +var initpc, _, _, _ = runtime.Caller(0) + +func TestFrameLine(t *testing.T) { + var tests = []struct { + Frame + want int + }{{ + Frame(initpc), + 9, + }, { + func() Frame { + var pc, _, _, _ = runtime.Caller(0) + return Frame(pc) + }(), + 20, + }, { + func() Frame { + var pc, _, _, _ = runtime.Caller(1) + return Frame(pc) + }(), + 28, + }, { + Frame(0), // invalid PC + 0, + }} + + for _, tt := range tests { + got := tt.Frame.line() + want := tt.want + if want != got { + t.Errorf("Frame(%v): want: %v, got: %v", uintptr(tt.Frame), want, got) + } + } +} type X struct{} -// val returns a Frame pointing to itself. func (x X) val() Frame { - return caller() + var pc, _, _, _ = runtime.Caller(0) + return Frame(pc) } -// ptr returns a Frame pointing to itself. func (x *X) ptr() Frame { - return caller() + var pc, _, _, _ = runtime.Caller(0) + return Frame(pc) } func TestFrameFormat(t *testing.T) { @@ -26,32 +59,32 @@ func TestFrameFormat(t *testing.T) { format string want string }{{ - initpc, + Frame(initpc), "%s", "stack_test.go", }, { - initpc, + Frame(initpc), "%+s", "github.com/pkg/errors.init\n" + "\t.+/github.com/pkg/errors/stack_test.go", }, { - 0, + Frame(0), "%s", "unknown", }, { - 0, + Frame(0), "%+s", "unknown", }, { - initpc, + Frame(initpc), "%d", "9", }, { - 0, + Frame(0), "%d", "0", }, { - initpc, + Frame(initpc), "%n", "init", }, { @@ -69,20 +102,20 @@ func TestFrameFormat(t *testing.T) { "%n", "X.val", }, { - 0, + Frame(0), "%n", "", }, { - initpc, + Frame(initpc), "%v", "stack_test.go:9", }, { - initpc, + Frame(initpc), "%+v", "github.com/pkg/errors.init\n" + "\t.+/github.com/pkg/errors/stack_test.go:9", }, { - 0, + Frame(0), "%v", "unknown:0", }} @@ -113,6 +146,24 @@ func TestFuncname(t *testing.T) { } } +func TestTrimGOPATH(t *testing.T) { + var tests = []struct { + Frame + want string + }{{ + Frame(initpc), + "github.com/pkg/errors/stack_test.go", + }} + + for i, tt := range tests { + pc := tt.Frame.pc() + fn := runtime.FuncForPC(pc) + file, _ := fn.FileLine(pc) + got := trimGOPATH(fn.Name(), file) + testFormatRegexp(t, i, got, "%s", tt.want) + } +} + func TestStackTrace(t *testing.T) { tests := []struct { err error @@ -120,24 +171,24 @@ func TestStackTrace(t *testing.T) { }{{ New("ooh"), []string{ "github.com/pkg/errors.TestStackTrace\n" + - "\t.+/github.com/pkg/errors/stack_test.go:121", + "\t.+/github.com/pkg/errors/stack_test.go:172", }, }, { Wrap(New("ooh"), "ahh"), []string{ "github.com/pkg/errors.TestStackTrace\n" + - "\t.+/github.com/pkg/errors/stack_test.go:126", // this is the stack of Wrap, not New + "\t.+/github.com/pkg/errors/stack_test.go:177", // this is the stack of Wrap, not New }, }, { Cause(Wrap(New("ooh"), "ahh")), []string{ "github.com/pkg/errors.TestStackTrace\n" + - "\t.+/github.com/pkg/errors/stack_test.go:131", // this is the stack of New + "\t.+/github.com/pkg/errors/stack_test.go:182", // this is the stack of New }, }, { func() error { return New("ooh") }(), []string{ - `github.com/pkg/errors.TestStackTrace.func1` + - "\n\t.+/github.com/pkg/errors/stack_test.go:136", // this is the stack of New + `github.com/pkg/errors.(func·009|TestStackTrace.func1)` + + "\n\t.+/github.com/pkg/errors/stack_test.go:187", // this is the stack of New "github.com/pkg/errors.TestStackTrace\n" + - "\t.+/github.com/pkg/errors/stack_test.go:136", // this is the stack of New's caller + "\t.+/github.com/pkg/errors/stack_test.go:187", // this is the stack of New's caller }, }, { Cause(func() error { @@ -145,12 +196,12 @@ func TestStackTrace(t *testing.T) { return Errorf("hello %s", fmt.Sprintf("world")) }() }()), []string{ - `github.com/pkg/errors.TestStackTrace.func2.1` + - "\n\t.+/github.com/pkg/errors/stack_test.go:145", // this is the stack of Errorf - `github.com/pkg/errors.TestStackTrace.func2` + - "\n\t.+/github.com/pkg/errors/stack_test.go:146", // this is the stack of Errorf's caller + `github.com/pkg/errors.(func·010|TestStackTrace.func2.1)` + + "\n\t.+/github.com/pkg/errors/stack_test.go:196", // this is the stack of Errorf + `github.com/pkg/errors.(func·011|TestStackTrace.func2)` + + "\n\t.+/github.com/pkg/errors/stack_test.go:197", // this is the stack of Errorf's caller "github.com/pkg/errors.TestStackTrace\n" + - "\t.+/github.com/pkg/errors/stack_test.go:147", // this is the stack of Errorf's caller's caller + "\t.+/github.com/pkg/errors/stack_test.go:198", // this is the stack of Errorf's caller's caller }, }} for i, tt := range tests { @@ -220,31 +271,22 @@ func TestStackTraceFormat(t *testing.T) { }, { stackTrace()[:2], "%v", - `\[stack_test.go:174 stack_test.go:221\]`, + `\[stack_test.go:225 stack_test.go:272\]`, }, { stackTrace()[:2], "%+v", "\n" + "github.com/pkg/errors.stackTrace\n" + - "\t.+/github.com/pkg/errors/stack_test.go:174\n" + + "\t.+/github.com/pkg/errors/stack_test.go:225\n" + "github.com/pkg/errors.TestStackTraceFormat\n" + - "\t.+/github.com/pkg/errors/stack_test.go:225", + "\t.+/github.com/pkg/errors/stack_test.go:276", }, { stackTrace()[:2], "%#v", - `\[\]errors.Frame{stack_test.go:174, stack_test.go:233}`, + `\[\]errors.Frame{stack_test.go:225, stack_test.go:284}`, }} for i, tt := range tests { testFormatRegexp(t, i, tt.StackTrace, tt.format, tt.want) } } - -// a version of runtime.Caller that returns a Frame, not a uintptr. -func caller() Frame { - var pcs [3]uintptr - n := runtime.Callers(2, pcs[:]) - frames := runtime.CallersFrames(pcs[:n]) - frame, _ := frames.Next() - return Frame(frame.PC) -}