Compare commits

...

143 Commits

Author SHA1 Message Date
OpenShift Merge Robot
d3a1815632
Merge pull request from dougbtv/updates-09-29-19-additional-bin-dir
Bug 1752453: Adds additional bin dir functionality
2019-09-19 18:25:16 +02:00
dougbtv
c758877033 [entrypoint] Adds --additional-bin-dir option to entrypoint to generate binDir configuration option 2019-09-19 10:28:20 -04:00
dougbtv
04311d3594 Appends binDir to CNI_PATH so that invoke.DelegateAdd/Del can find CNI plugins in alternate paths 2019-09-19 10:28:20 -04:00
OpenShift Merge Robot
30f0ae914f
Merge pull request from dougbtv/grpc-dependency-updates-CVE-2019-9511-4.2
Bug 1746645: Grpc dependency updates cve 2019 9511 4.2
2019-08-31 21:11:57 -07:00
dougbtv
10692b4538 [dockerfile] Use ENV GO111MODULE=off when building from openshift/origin-release:golang-1.10 2019-08-31 08:36:07 -04:00
OpenShift Merge Robot
abb8cd406f
Merge pull request from dougbtv/entrypoint-updates-aug29-2019
Bug 1732598: Adds one-shot CRIO restart
2019-08-30 19:25:08 -07:00
dougbtv
25c25916b5 [entrypoint] Adds script parameter to optionally rename source CNI config file to .old 2019-08-30 18:07:16 -04:00
dougbtv
707017a5cd [deps] Update grpc for CVE-2019-9511 https://nvd.nist.gov/vuln/detail/CVE-2019-9511 2019-08-30 17:17:42 -04:00
Tomofumi Hayashi
81636bc0c6 Bumpup CNI repo version 2019-08-30 15:21:27 -04:00
Tomofumi Hayashi
2f70e96401 Change .goreleaser.yml to support go module build 2019-08-30 15:21:09 -04:00
Tomofumi Hayashi
89b82c1264 Support GOPATH mode build/test for its transitional situation 2019-08-30 15:21:06 -04:00
Tomofumi Hayashi
b615316342 Migrate go modules from glide 2019-08-30 15:20:48 -04:00
dougbtv
c42108e40a [build] Adds CGO_ENABLED=0 and -tags no_openssl to build script 2019-08-30 15:20:22 -04:00
OpenShift Merge Robot
4b88a0293a
Merge pull request from openshift/jeremyeder-patch-1
Update OWNERS
2019-08-30 05:18:07 -07:00
Jeremy Eder
89a3ffb08e
Update OWNERS
Removing myself.
2019-08-29 21:33:38 -04:00
dougbtv
1a2f8ccc9b [entrypoint] Adds one-shot CRIO restart, plus renames source CNI config to .old when not using the watch loop 2019-08-29 10:43:15 -04:00
OpenShift Merge Robot
d7b233d0f4
Merge pull request from dougbtv/release-v3-changes-aug21
Bug 1732598: Entrypoint changes for watch loop & CRIO restart
2019-08-21 20:28:53 -07:00
dougbtv
240ec14e3c [entrypoint] Adds CRIO restart
Co-authored-by: s1061123 <tohayash@redhat.com>
2019-08-21 10:55:26 -04:00
dougbtv
f89e7e47bb [entrypoint] Adds --cleanup-config-on-exit to the entrypoint 2019-08-21 10:55:22 -04:00
Tomofumi Hayashi
e6a9fe8b11 Add daemonset for crio again
This change introduces multus-daemonset-crio again to support crio.
The change also introduce '--override-network-name' to use previous
master name in CNI json for multus CNI json.
2019-08-21 10:54:36 -04:00
OpenShift Merge Robot
b20f528de1
Merge pull request from s1061123/fix/revert-nossl
Revert 'no_openssl' flag.
2019-07-17 16:41:15 +02:00
Tomofumi Hayashi
52ccadc1d4 Revert 'no_openssl' flag. 2019-07-17 21:59:24 +09:00
OpenShift Merge Robot
13932b1cdd
Merge pull request from s1061123/fix/nossl
Add '-tags no_openssl' option in build script
2019-07-17 14:56:27 +02:00
Tomofumi Hayashi
124ff4ee70 Add '-tags no_openssl' option in build script 2019-07-17 15:37:43 +09:00
OpenShift Merge Robot
c8dc6316c9
Merge pull request from dougbtv/release-v3-may-30-resolved
Entrypoint options, SR-IOV functionality, CNI libs update
2019-06-03 19:09:05 +02:00
Zenghui Shi
9bf83c28d3 assign device id to pciBusID in delegated config
This allows host-device plugin to recognize
Device PCI address passed from Multus.
It is related to the change in host-device which
enables use of device pci address as a config option:
https://github.com/containernetworking/plugins/pull/300
2019-05-31 10:10:01 -04:00
Zenghui Shi
09fde13d79 assign device id to pciBusID in delegated config
This allows host-device plugin to recognize
Device PCI address passed from Multus.
It is related to the change in host-device which
enables use of device pci address as a config option:
https://github.com/containernetworking/plugins/pull/300
2019-05-31 09:53:38 -04:00
dougbtv
7e0637f6a9 Merge to resolve conflicts in vendored packages 2019-05-30 14:33:14 -04:00
Tomofumi Hayashi
a4dbe7b102 CNIVersion fix for conflist
This change introduces two fix about CNIVersion. One is to add CNIVersion into
delegated CNI conflist in case of DEL if CNIVersion is missing. ParseVersion() in libcni
checks cniVersion as mandatory field and sometimes user/daemonset miss it.
The latest flannel daemonset yaml (of flannel github) does not have CNIVersion, for example.
This change adds CNIVersion from multus config if cniVersion is missing (empty) in delegated
CNI config.

In addition, this fix also adds '--cni-version' in entrypoint.sh to add cniVersion in multus
config in case of '--multus-conf-file=auto'.
2019-05-30 14:24:38 -04:00
Tomofumi Hayashi
bb69ac5058 Continue to process cmdDel() in case of no network namespace
K8s with docker runtime, cmdDel() is invoked with empty network
namespace in case of restart node. Currently multus just returns
but CNI spec mention that we should invoke CNI plugin to cleanup.

This PR deletes "return nil" and proceeds to invoke CNI plugins
DEL. Fix .
2019-05-30 14:24:16 -04:00
Tomofumi Hayashi
0b153cd941 Update libcni to 0.7.0 (Spec ver 0.4.0) 2019-05-30 14:23:53 -04:00
Dan Williams
41160c62d6 entrypoint.sh: add timestamps to log messages; log autogenerated config file
Example:

2019-05-09T15:41:20-05:00 Generating Multus configuration file ...
2019-05-09T15:41:20-05:00 Attemping to find master plugin configuration, attempt 0
2019-05-09T15:41:20-05:00 Attemping to find master plugin configuration, attempt 1
2019-05-09T15:41:20-05:00 Attemping to find master plugin configuration, attempt 10
2019-05-09T15:41:20-05:00 Attemping to find master plugin configuration, attempt 15
2019-05-09T15:41:20-05:00 Attemping to find master plugin configuration, attempt 20
2019-05-09T15:41:20-05:00 Config file created @ /host/etc/cni/net.d/00-multus.conf
{ "name": "multus-cni-network", "type": "multus", "namespaceIsolation": true, "logLevel": "verbose", "kubeconfig": "/etc/kubernetes/cni/net.d/multus.d/multus.kubeconfig", "delegates": [ { "cniVersion": "0.3.1", "name": "openshift-sdn", "type": "openshift-sdn" } ] }
2019-05-09T15:41:20-05:00 Entering sleep... (success)
2019-05-30 14:23:34 -04:00
dougbtv
381db908a2 [entrypoint][docs] Adds --multus-autoconfig-dir and entrypoint documentation 2019-05-30 14:23:15 -04:00
Zenghui Shi
f5bb00a5b1 add test cases for device id assignment 2019-05-30 14:22:45 -04:00
Tomofumi Hayashi
f242f38a60 Add version into binary and fix .travis.yml to run forked repo.
This changes introduce goreleaser, which does cross-compile and
package, as well as add version into go code. This change also
changes .travis.yml to allow to other users' forked repo.
2019-05-30 14:19:31 -04:00
OpenShift Merge Robot
e13c535019
Merge pull request from dougbtv/release-v3-may03-02
[bugfix] Skip clearing the network status annotation if the pod sandbox is not found
2019-05-07 14:30:52 +02:00
dougbtv
5b6d4320a0 [bugfix] Skipped clearing the network status annotation if the pod sandbox is not found 2019-05-03 10:13:39 -04:00
dougbtv
31d87f615d [bugfix] Skipped clearing the network status annotation if the pod sandbox is not found 2019-05-03 09:51:52 -04:00
OpenShift Merge Robot
dd8e7c3abd
Merge pull request from openshift/fix-cgo
add CGO_ENABLED=0 to build
2019-04-17 11:49:42 -07:00
Doug Smith
0390c1720c adds -tags no_openssl 2019-04-17 12:23:11 -04:00
Doug Smith
e691b1fa46 add CGO_ENABLED=0 to build 2019-04-17 11:44:49 -04:00
OpenShift Merge Robot
a805f63a04
Merge pull request from dougbtv/release-v3-mar26
fix missing deviceID in NetConfList
2019-03-27 06:12:01 -07:00
Abdul Halim
b6c8dfbffb fix missing deviceID in NetConfList
This patch fixes the issue described in  where deviceID for
delegate plugin was not adding properly if the plugin conf inside
NetConfList.

Change-Id: I1d221f6b0e60a5b888b8e823611dfe12635e6897
Signed-off-by: Abdul Halim <abdul.halim@intel.com>
2019-03-26 15:04:54 -04:00
Abdul Halim
05ba224c08 fix missing deviceID in NetConfList
This patch fixes the issue described in  where deviceID for
delegate plugin was not adding properly if the plugin conf inside
NetConfList.

Change-Id: I1d221f6b0e60a5b888b8e823611dfe12635e6897
Signed-off-by: Abdul Halim <abdul.halim@intel.com>
2019-03-26 14:54:36 -04:00
OpenShift Merge Robot
93fe981913
Merge pull request from zshi-redhat/kubelet-client
add kubelet client for Pod resource info
2019-03-26 06:39:19 -07:00
Abdul Halim
d2a02a6e5d add kubelet client for Pod resource info
This change introduces kubelet client to get allocated device
information of a Pod from newly added Kubelet grpc service.
For more information please see:
[](https://github.com/kubernetes/kubernetes/pull/70508)

Change-Id: I11e58ccdd52662601f445fa24c7d55c225441efc
Signed-off-by: Abdul Halim <abdul.halim@intel.com>
(cherry picked from commit d3c92b4aa2)
2019-03-24 12:33:04 +08:00
Abdul Halim
877f00be30 update vendor dependencies for kubelet client
adding new imported package dependencies in vendor which is required
for Kubelet Pod Resource api client.

Change-Id: If6c74598e12af5f8659df69371e72dd064823f49
(cherry picked from commit 8ee7eb335e)
2019-03-24 12:32:30 +08:00
Abdul Halim
942ad4749e add kubelet client for Pod resource info
This change introduces kubelet client to get allocated device
information of a Pod from newly added Kubelet grpc service.
For more information please see:
[](https://github.com/kubernetes/kubernetes/pull/70508)

Change-Id: I11e58ccdd52662601f445fa24c7d55c225441efc
Signed-off-by: Abdul Halim <abdul.halim@intel.com>
(cherry picked from commit d3c92b4aa2)
2019-03-23 23:26:40 +09:00
Abdul Halim
310c8e914c update vendor dependencies for kubelet client
adding new imported package dependencies in vendor which is required
for Kubelet Pod Resource api client.

Change-Id: If6c74598e12af5f8659df69371e72dd064823f49
(cherry picked from commit 8ee7eb335e)
2019-03-23 23:26:40 +09:00
OpenShift Merge Robot
02913fb96f
Merge pull request from dougbtv/release-v3-mar-20
Release v3 updates - includes caching PR
2019-03-20 19:40:31 -07:00
maximshd
aff54bb1f7 Properly initialize kubeClient in SetNetworkStatus method ()
* Properly initialize kubeClient in SetNetworkStatus method

* Fix typo

* Update error message

* Extend logging for setNetworkStatus function
2019-03-20 16:20:46 -04:00
Tomofumi Hayashi
57f1d7afc7 Caches all pod delegates json for pods deletion without k8s info
This fixes  with following changes:
 + Optimize to fetch Pod from k8s client
 + Change to use cache always in DEL.
 + If failed to fetch the pod info from k8s clinet in deletion,
  use cached delegates as emergency bailout
 + Add test cases for cache

fixed conflicts in multus/multus.go & multus/multus_test.go
2019-03-20 16:20:10 -04:00
knight
363b7fbc06 refactor k8sclient: rename some val 2019-03-20 16:11:38 -04:00
OpenShift Merge Robot
7094c9675f
Merge pull request from dougbtv/release-v3-mar7
Release v3 updates
2019-03-11 08:33:13 -07:00
Doug Smith
4aecbd2133 [entrypoint] Add options to specify logfile & loglevel in entrypoint () 2019-03-07 11:08:20 -05:00
Tomofumi Hayashi
1e4fe4f837 Add 'verbose' option to logging minimum information ()
This change address  to add 'verbose option which outputs
minimum information (for usual runs with a bit information than
'error').
2019-03-07 11:06:49 -05:00
Peng Liu
11fe266139 Generate Multus config file regardless 2019-03-07 11:06:18 -05:00
Tomofumi Hayashi
68c5d0dd70 Change ClusterNetwork/DefaultNetwork namespace to MultusNamespace
Fix .
2019-03-07 11:06:13 -05:00
OpenShift Merge Robot
f47677d637
Merge pull request from openshift/release-v3
Release v3
2019-02-28 09:23:05 -08:00
OpenShift Merge Robot
8b806df35c
Merge pull request from dougbtv/owners-file
Add OWNERS file
2019-02-28 00:39:35 +01:00
dougbtv
faae208bd7 Add OWNERS file 2019-02-27 14:02:25 -05:00
dougbtv
61f9e08863 Allows cmdDel to finish if netns doesn't exist, omits deferred netns.Close() in such a case 2019-02-22 09:07:35 -05:00
Doug Smith
3a489263ea Changes configuration for kube api to use gRPC 2019-02-22 09:07:16 -05:00
Dan Williams
3a9cf4a9a2 multus: simplify teardown on add error and clarify error message
Signed-off-by: Dan Williams <dcbw@redhat.com>
2019-02-22 09:07:03 -05:00
Doug Smith
b622593956
Merge pull request from openshift/noop
[noop]
2019-02-19 09:57:08 -05:00
dougbtv
2ecb1a01a5 [noop] 2019-02-19 09:55:52 -05:00
Tomofumi Hayashi
73e10fb999 Add multusNamespace/systemNamespaces config
This change provides new configuration parameters, multusNamespace
and systemNamespaces for flexible namespace management.
The change addresses issue  and issue .
2019-02-19 09:53:17 -05:00
dougbtv
972a7d18c6 [entrypoint] Updates entrypoint for atomic move of binary (for cleaner upgrade) 2019-01-22 10:46:24 -05:00
dougbtv
f276c023f8 [entrypoint] Adds option for namespaceIsolation in entrypoint 2019-01-15 13:48:17 -05:00
dougbtv
7174b9ac47 [dockerfile] Updates Dockerfile for OpenShift-style build 2019-01-07 11:39:34 -05:00
Mathieu Rohon
a51309c532 [dependency] Add portmap capability support (Other changes were dependent on changes made in this commit)
Signed-off-by: Mathieu Rohon <mathieu.rohon@orange.com>
2018-12-20 13:07:32 -05:00
dougbtv
8d2445bb4c [feature] Adds a namespace isolation security feature 2018-12-20 10:50:15 -05:00
Michal Rostecki
0a59253609 k8sclient: Add missing error check
Before this change, error returned by `libcni.ConfFiles` was
silently ignored.

Signed-off-by: Michal Rostecki <mrostecki@suse.de>
2018-12-19 16:04:54 -05:00
dougbtv
570a81d641 [dockerfile] Adds Dockerfile.rhel for OpenShift build 2018-12-19 16:04:54 -05:00
Tomofumi Hayashi
4ad64094b3 Fix go vet issue. 2018-12-19 16:04:54 -05:00
Tomofumi Hayashi
3e2c4b1528 Change the namespace to 'kube-system' 2018-12-19 16:04:54 -05:00
Tomofumi Hayashi
2e070bd4a5 clusterNetwork/defaultNetworks and namespace spec fixed
This fix is to add declaration about clusterNetwork/defaultNetwork
net-attach-def is in 'default' namesspace. In addition, this code
changes to skip defaultNetwork in case of 'kube-system' namespace
as well ().
2018-12-19 16:04:54 -05:00
Tomofumi Hayashi
dd8492c44c Fix Docker build issue around golang. 2018-12-19 16:04:53 -05:00
Michal Rostecki
09503c3dd3 Add .gitignore file
Prevent tracking of binary outputs, GOPATH and test outputs.

Signed-off-by: Michal Rostecki <mrostecki@suse.de>
2018-12-19 16:04:53 -05:00
dougbtv
1ee63551fa [bugfix] Delete all delegates instead of breaking out during deletion loop 2018-12-19 16:04:53 -05:00
Peng Liu
0e6cc2292a Change pod annotation name to 'v1.multus-cni.io/default-network' 2018-12-19 16:04:53 -05:00
Peng Liu
8c021bd46f Add more debug message 2018-12-19 16:04:53 -05:00
Peng Liu
50dfdb7036 Specify Pod default network in Annotations
Signed-off-by: Peng Liu <pliu@redhat.com>
2018-12-19 16:04:53 -05:00
Tomofumi Hayashi
5b5e5e5386 Add description how to use CRD in non-default namespaces 2018-12-19 16:04:53 -05:00
dougbtv
510c803d2c [docs] Updates to fix typos and extend information about CNI configurations generally per review 2018-12-19 16:04:53 -05:00
dougbtv
99dbdd9f57 [docs] Adds additional quickstart.md specific guide, some updates to usage guide 2018-12-19 16:04:53 -05:00
Tomofumi Hayashi
61801e07d4 Add comments in case of daemonset. 2018-12-19 16:04:53 -05:00
Tomofumi Hayashi
52f6892ba1 Add more paragraph. 2018-12-19 16:04:53 -05:00
Tomofumi Hayashi
62b9dc51c1 Add "NOTE:" and change "NOTE" from "Note" 2018-12-19 16:04:53 -05:00
Tomofumi Hayashi
80009b398c s/folloiwng/following/ 2018-12-19 16:04:53 -05:00
Tomofumi Hayashi
67380dbf9e Add 'skip in case of daemonset' at "SA, ClusterRole..." 2018-12-19 16:04:53 -05:00
Tomofumi Hayashi
7d551d100d Indent the paragraph at "install multus" 2018-12-19 16:04:53 -05:00
Tomofumi Hayashi
9d20b37904 Update README.md and split into several child documents
Fix  and . Thank you @dougbtv for reviewing the docs!
2018-12-19 16:04:49 -05:00
Alona Kaplan
c4feebc6c9 [divergence] Support IPRequest to specify IP address for interface 2018-12-19 16:03:08 -05:00
Tomofumi Hayashi
787586aca8 Fix the log message. 2018-12-19 16:02:36 -05:00
dougbtv
8c2798a32a [travis] Updates Travis to build ':snapshot' tagged image on each merge into master 2018-12-19 16:02:36 -05:00
Przemyslaw Lal
d87b7beeca fix indentation
Signed-off-by: Przemyslaw Lal <przemyslawx.lal@intel.com>
2018-12-19 16:02:36 -05:00
Przemyslaw Lal
ecb3260866 webhook documentation updates
Signed-off-by: Przemyslaw Lal <przemyslawx.lal@intel.com>
2018-12-19 16:02:36 -05:00
Przemyslaw Lal
20ae7c25e5 add more webhook tests
Signed-off-by: Przemyslaw Lal <przemyslawx.lal@intel.com>
2018-12-19 16:02:36 -05:00
Przemyslaw Lal
e748969163 improve error handling in webhook
Signed-off-by: Przemyslaw Lal <przemyslawx.lal@intel.com>
2018-12-19 16:02:36 -05:00
Przemyslaw Lal
63ff3f1992 run webhook as a deployment
Signed-off-by: Przemyslaw Lal <przemyslawx.lal@intel.com>
2018-12-19 16:02:36 -05:00
Przemyslaw Lal
862eb5e8e2 Add proxy env variables to docker build script
Signed-off-by: Przemyslaw Lal <przemyslawx.lal@intel.com>
2018-12-19 16:02:36 -05:00
Przemyslaw Lal
abcee9ecda Add documentation for validating admission webhook
Signed-off-by: Przemyslaw Lal <przemyslawx.lal@intel.com>
2018-12-19 16:02:36 -05:00
Przemyslaw Lal
8b18175fc9 Add deployment files for validating admission webhook
* Add script for automated certtificates and secret generation
* Add pod, service and webhook configuration specification files

Signed-off-by: Przemyslaw Lal <przemyslawx.lal@intel.com>
2018-12-19 16:02:36 -05:00
Przemyslaw Lal
5892b36b7a Add validating admission webhook
* Add validating admission webhook HTTP server application
* Handle incoming AdmissionReview requests and validate their correctness, handle errors if any
* Validate Network Attachment Definition objects
* Send AdmissionReview response with allowed/denied decision and its reason
* In case of any other errors (malformed HTTP request, empty body, etc.) send proper HTTP error code
* Use TLS encryption
* Add some basic unit tests for Network Attachment Definition objects validation
* Build Docker image with webhook application

Signed-off-by: Przemyslaw Lal <przemyslawx.lal@intel.com>
2018-12-19 16:02:36 -05:00
Alona Kaplan
fd5ac6801c Setting the MAC in CNI_ARGS shouldn't override the already existing CNI_ARGS 2018-12-19 16:02:36 -05:00
Dan Williams
d773dcfaad CRD: interfaceRequest -> interface (v1 spec conformance)
Change the Network Attachment Selection Annotation long-form
interface name request JSON key from 'interfaceRequest' to
'interface' to conform with the V1 NPWG spec.
2018-12-19 16:02:36 -05:00
Tomofumi Hayashi
bcc6e21587 Fix rebase conflicts. 2018-12-19 16:02:35 -05:00
Tomofumi Hayashi
818c8d7c99 [divergence] Fix multus_test. 2018-12-19 16:02:30 -05:00
Tomofumi Hayashi
dd9fedee2a Remove unnecessary else clause 2018-12-19 15:55:45 -05:00
Tomofumi Hayashi
87b7dbc378 Incorporate @dcbw's comment. 2018-12-19 15:55:45 -05:00
Tomofumi Hayashi
481c21b1e1 Add mac/interfaceRequest section in README.md 2018-12-19 15:55:45 -05:00
Tomofumi Hayashi
91a0e74a0d Change json field name to align with NPWG spec v1. 2018-12-19 15:55:45 -05:00
Tomofumi Hayashi
ac3731380c Fix the way to set MAC. 2018-12-19 15:55:44 -05:00
Tomofumi Hayashi
0a0c8d042a Add debug messasge for MAC. 2018-12-19 15:55:44 -05:00
Tomofumi Hayashi
dae85d7781 Support MacRequest to specify MAC address for interface 2018-12-19 15:55:44 -05:00
Tomofumi Hayashi
6b573e8c4b Add issue template for {bug,enhance,support} 2018-12-19 15:55:44 -05:00
Tomofumi Hayashi
606efe443f Fix example files ( and ) 2018-12-19 15:55:44 -05:00
Tomofumi Hayashi
3458934e12 Fix typo in README.md 2018-12-19 15:55:44 -05:00
Tomofumi Hayashi
6b0266148b Add clusterNetwork/defaultNetwork into multus
To support CRD/file/directory, add clusterNetwork/defaultNetwork
in multus.conf file.
2018-12-19 15:55:44 -05:00
Tomofumi Hayashi
55792b94d5 Add unit tests for clusterNetwork/defaultNetworks 2018-12-19 15:55:44 -05:00
Tomofumi Hayashi
061f4f913e Add clusterNetwork/defaultNetwork into multus
To support CRD/file/directory, add clusterNetwork/defaultNetwork
in multus.conf file.
2018-12-19 15:55:44 -05:00
Tomofumi Hayashi
bb89e538eb [divergence] Add clusterNetwork/defaultNetwork into multus
To support CRD/file/directory, add clusterNetwork/defaultNetwork
in multus.conf file.
2018-12-19 15:55:37 -05:00
Michael Cambria
7f389e7e7c Make conflistDel() behave like conflistAdd()
conflistAdd() finds binaries differently than conflistDel().
Make the two call find binaries the same way.

Fixes 

Signed-off-by: Michael Cambria <mcambria@redhat.com>
2018-12-19 15:51:36 -05:00
dougbtv
da20d1d875 [rbac] Tightens down RBAC for clusterrole 2018-12-19 15:51:36 -05:00
Michael Cambria
fa2e35c4d4 Fix logFile to match configuration json
The Logging Options section of README describes how to specify a file
to log to.  There is a typo, LogFile should be logFile to match the
json.

Fixes 

Signed-off-by: Michael Cambria <mcambria@redhat.com>
2018-12-19 15:51:36 -05:00
dougbtv
35e4ee50a4 Fixes flannel daemonset stuck in pod queue in Kubernetes 1.12.x per 2018-12-19 15:51:36 -05:00
dougbtv
ae626b4aa5 [bugfix][divergence] Diverges from master in requiring change to logging.Debugf value changed from string 2018-11-01 22:41:56 +09:00
Shahar Klein
25fd3ca8bc Seems like the ENTRYPOINT value must be quoted
Signed-off-by: Shahar Klein <shaharklein@gmail.com>
2018-11-01 22:41:56 +09:00
Tomofumi Hayashi
5380edbe6e Fix TravisCI for the failure of 'go get golint' 2018-11-01 22:41:56 +09:00
Tomofumi Hayashi
980edbdf6c TravisCI yaml parameterized
This change fixes , to make some specific TravisCI args parameter.
2018-11-01 22:41:56 +09:00
Doug Smith
ae9a1a5c10 Default network readiness [NOTE: Conflict due to omitted commit during cherry pick for stable] 2018-11-01 22:41:56 +09:00
Kuralamudhan Ramakrishnan
959ffaac3b Update README.md 2018-11-01 22:41:56 +09:00
Kuralamudhan Ramakrishnan
346bed2d9d Update README.md 2018-11-01 22:41:56 +09:00
Abdul Halim
66361bfbb2 fixed some typos in comments
Change-Id: Ieb650479b6b0fef1a4ecaeb2c3c1a7c15fff43d5
2018-11-01 22:41:56 +09:00
Abdul Halim
1ba90bad00 added checkpoint tests file
Change-Id: I53551660ffd017fe170de58abdf7a96e29178000
2018-11-01 22:41:56 +09:00
Abdul Halim
484385456b refactoring checkpoint.go code to be testable
this changes will allow mocking checkpoint instance for unit tests

Change-Id: I72fb25d15d5c9f28577a0fcbfcd385df523a5e57
2018-11-01 22:41:56 +09:00
Abdul Halim
5988b7a82b only create resourceMap on demand
making resourceMap a singleton object and only initialize it once
if one or more CRDs have a resourceName annotation in them.

Added copyright header for checkpoint/checkpoint.go.
Replaced fmt.Errorf with logging.

Change-Id: I54628d69324833e70a75dcf6533e6642dedde9b5
2018-11-01 22:41:56 +09:00
Abdul Halim
2cfaa19dda updated examples/README.md
Change-Id: I650fec86659b3690e1dc4b15bf84b6574cb0baba
2018-11-01 22:41:56 +09:00
Abdul Halim
539e92b8b0 parse kubelet checkpoint file for pod devices
Enabling kubelete checkpoint file  parsing to get Pod device info
so that these device information can be passed into CNI plugins
that need specific device information to work on.

Change-Id: I6630f56adc0a8307f575fc09ce9090c1ffca0337
2018-11-01 22:41:56 +09:00
Louis Woods
93237e6161 Improve grep in entrypoint.sh to only find .conf and .conflist files 2018-11-01 22:41:56 +09:00
Louis Woods
bae3adf158 Add the option to auto generate 00-multus.conf
When `--multus-conf-file=auto` is used, 00-multus.conf will be
automatically generated from the CNI configuration file of the master
plugin (the first file in lexicographical order in cni-conf-dir).
2018-11-01 22:41:56 +09:00
Tomofumi Hayashi
e9aa18319d Add bracket [] in Dockerfile's entrypoint to parse argument correctly. 2018-11-01 22:41:56 +09:00
4433 changed files with 222022 additions and 1548248 deletions

28
.github/ISSUE_TEMPLATE/bug-report.md vendored Normal file
View File

@ -0,0 +1,28 @@
---
name: Bug Report
about: Report a bug encountered
---
<!-- Please use this template while reporting a bug and provide as much info as possible. Not doing so may result in your bug not being addressed in a timely manner. Thanks!-->
**What happend**:
**What you expected to happen**:
**How to reproduce it (as minimally and precisely as possible)**:
**Anything else we need to know?**:
**Environment**:
- Multus version
image path and image ID (from 'docker images')
- Kubernetes version (use `kubectl version`):
- Primary CNI for Kubernetes cluster:
- OS (e.g. from /etc/os-release):
- File of '/etc/cni/net.d/'
- File of '/etc/cni/multus/net.d'
- NetworkAttachment info (use `kubectl get net-attach-def -o yaml`)
- Target pod yaml info (with annotation, use `kubectl get pod <podname> -o yaml`)
- Other log outputs (if you use multus logging)

10
.github/ISSUE_TEMPLATE/enhancement.md vendored Normal file
View File

@ -0,0 +1,10 @@
---
name: Enhancement Request
about: Suggest an enhancement to multus
---
<!-- Please only use this template for submitting enhancement requests -->
**What would you like to be added**:
**Why is this needed**:

5
.github/ISSUE_TEMPLATE/support.md vendored Normal file
View File

@ -0,0 +1,5 @@
---
name: Support Request
about: Support request or question relating to multus-cni
---

9
.gitignore vendored Normal file
View File

@ -0,0 +1,9 @@
# Binary output dir
bin/
# GOPATH created by the build script
gopath/
# Test outputs
*.out
*.test

27
.goreleaser.yml Normal file
View File

@ -0,0 +1,27 @@
# This is an example goreleaser.yaml file with some sane defaults.
# Make sure to check the documentation at http://goreleaser.com
env:
- GO111MODULE=on
before:
hooks:
- go mod download
builds:
-
env:
- CGO_ENABLED=0
main: ./multus/
goos:
- linux
goarch:
- 386
- amd64
- arm
- arm64
archive:
wrap_in_directory: true
checksum:
name_template: 'checksums.txt'
snapshot:
name_template: "{{ .Tag }}-snapshot"
release:
draft: true

View File

@ -3,65 +3,97 @@ language: go
# for the detail
# sudo: requried
dist: trusty
go:
- 1.12.x
env:
global:
- REGISTRY_USER=nfvperobot
- secure: "LnQV09sy5nfrJd0PKAbxYPdKJ5QtLECofsunYfVk7tFp+ivKyZBXHwi4V4aGFuB2SqCnpauXBRTLet8hrfm5kN9ZZQRqy0WNs/fJHdFC6YKOKwyCQwczFb1by/iTX68dxWc2nK9+Opi6s/81Bh5yb3Oquqzdk+OEgaQHz2KP7BwI4yDrobinBR5laJ4KdxZJYgYx4mP6uUPxj7UZww+HaWqyiGy8cAeK3L81sGjxXJIYTRRfG1J4pifI5A3c3IOJRID0pvifgUIsQXp5MHpx+nxmhRJ7KMBLeNkUKruLTEsufgGCvhY5eWpdBhVN2YefGTqlKBCtKEqRUPlLbP5eJGUdY1PlUMUnQsr+FRWAZz90A1TESOZXZqDs4xR1ox1wX7mBUeelViXvUfLQB9sOD8G86FkXqNTqx/thp3x0Dqgy44pL+12Y3k5xVZmIsWDSpGmmIe1jOCsoL26Fdic+dTO/l3mx3KP1+gPNqbScuJsccLyPsr96uFCBCPJ2mSy7nCqb01KZTbbkIvv6oOCQ+Mfq8MT9lkxf6FJ+K+7vVbcgshOGhqA/l1UO3rKxnGt8Rkj/5XoHkcjXjM6YzT5LvljVWszJGXeTQxGjcsPrK2AscyX7JvNp/AMElII/Hxm6P0NESfV0whrZHyVOaqIRrbhUsK9j4YP8IMFoI4qYp4g="
- GO111MODULE=on
- REGISTRY_USER=${REGISTRY_USER}
- REGISTRY_PASS=${REGISTRY_PASS}
- secure: "${REGISTRY_SECURE}"
matrix:
- TARGET=amd64
- TARGET=ppc64le
before_install:
- sudo apt-get update -qq
- go get github.com/mattn/goveralls
install:
# workaround golint install error in https://github.com/golang/lint/issues/288
- mkdir -p $GOPATH/src/golang.org/x
- pushd $GOPATH/src/golang.org/x
- git clone https://github.com/golang/lint.git
- go get github.com/golang/lint/golint
- popd
- go get -u golang.org/x/lint/golint
before_script:
- golint ./multus/... | grep -v ALL_CAPS | xargs -r false
- go fmt ./multus/...
- go vet ./multus/...
# Make gopath... to run golint/go fmt/go vet
# Suppress golint for fixing lint later.
- golint ./... | grep -v vendor | grep -v ALL_CAPS | xargs -r false
- go fmt ./...
- go vet ./...
# - gocyclo -over 15 ./multus
script:
- ./build
- sudo ./test.sh
- $GOPATH/bin/goveralls -coverprofile=coverage.out -service=travis-ci
- mkdir -p ${TRAVIS_BUILD_DIR}/dist
- tar cvfz ${TRAVIS_BUILD_DIR}/dist/multus-cni_amd64.tar.gz --warning=no-file-changed --exclude="dist" --exclude="vendor" .
- docker build -t nfvpe/multus .
before_deploy:
- go get -u github.com/laher/goxc
- mkdir -p $TRAVIS_BUILD_DIR/dist
- goxc -d=$TRAVIS_BUILD_DIR/dist -pv=$TRAVIS_TAG -bc=linux -tasks=clean-destination,xc,archive,rmbin
- GOARCH="${TARGET}" ./build
- |
if [ "${TARGET}" == "amd64" ]; then
sudo env PATH=${PATH} ./test.sh
goveralls -coverprofile=coverage.out -service=travis-ci
mkdir -p ${TRAVIS_BUILD_DIR}/dist
tar cvfz ${TRAVIS_BUILD_DIR}/dist/multus-cni_amd64.tar.gz --warning=no-file-changed --exclude="dist" .
docker build -t nfvpe/multus:latest-amd64 .
docker build -t nfvpe/multus:latest-ppc64le -f Dockerfile.ppc64le .
docker build -t nfvpe/multus-origin:latest -f Dockerfile.openshift .
fi
deploy:
- provider: releases
api_key:
secure: "iy7eqzXNvb/juc+5eVPQ/pFYDTCqDt8Zjt63n+zEK856Qzr2aEZwwOguMWs78XFDMFXagCs5PRTvtvZz8apoTfHX7Wkss3kRyEziAkuldQbH5yGDvpGyHsGBw78N95hauMoogefE7NuuLG3qRSWPeVz8RAKGhP7ADwEVyyfQKKYdum3Bqrz0D89HqKbCQqs3eZae7ppDIler3lab9WAQGuKNJ2HL6mqREVe48kb8sdsuSr+yV4qwVrBDNhXxQDxAT6LYuMXbknE7qTde2vViP13ZHpptbuZqiZG2ytzReIIs/iC9AWoIQXr3XTXl9z8fqlC3VljPCikBWVcmxDFA2aANYzx3M/7fMOO/DniwNhlZc9+pYfAkUrpoQPfPOWNqf45Qz0jP3wk49xy5hxEqe/rfmo5lipSsqeUsk+j3pT8kjVIAnDLrQpxSx7xwnijPLgtm34UwROVowfwLlOhE/7mUOFCbYlzEo3CKvjDN3Kmn35yHEueuu//Gv5jesVYvgcNPBHqaTKb5AXVTqymNBtA43PchLJ8gCC1mNukzSZifQP996vzbV5c9AxzBLjWbiDJ3lOFIpNhF8Sed0m0C0RylrTXHTX5TSrlMdXXffzYwbjJ96J+cFPBTpJNfSn+3N7hiart1r1k1bSXoPqYW4+94M8E1eZ5LjszoeiZbRrI="
file_glob: true
file: "$TRAVIS_BUILD_DIR/dist/*/*.gz"
skip_cleanup: true
# Release on versioned tag (e.g. v1.0)
- provider: script
#skip_cleanup: true
script: curl -sL https://git.io/goreleaser | bash
on:
tags: true
all_branches: true
condition: "$TRAVIS_TAG =~ ^v[0-9].*$"
# Push images to Dockerhub
# Push images to Dockerhub on tag
- provider: script
skip_cleanup: true
script: >
bash -c '
docker tag nfvpe/multus nfvpe/multus:$TRAVIS_TAG;
docker tag nfvpe/multus:latest-amd64 nfvpe/multus:latest;
docker tag nfvpe/multus:latest-amd64 nfvpe/multus:stable;
docker tag nfvpe/multus:latest-amd64 nfvpe/multus:stable-amd64;
docker tag nfvpe/multus:latest-amd64 nfvpe/multus:$TRAVIS_TAG;
docker tag nfvpe/multus:latest-ppc64le nfvpe/multus:stable-ppc64le;
docker login -u "$REGISTRY_USER" -p "$REGISTRY_PASS";
docker push nfvpe/multus;
docker push nfvpe/multus:$TRAVIS_TAG'
docker push nfvpe/multus:latest;
docker push nfvpe/multus:latest-amd64;
docker push nfvpe/multus:latest-ppc64le;
docker push nfvpe/multus:stable;
docker push nfvpe/multus:stable-amd64;
docker push nfvpe/multus:stable-ppc64le;
docker push nfvpe/multus:$TRAVIS_TAG;
echo done'
on:
tags: true
all_branches: true
condition: "$TRAVIS_TAG =~ ^v[0-9].*$"
# Push images to Dockerhub on merge to master
- provider: script
on:
branch: master
script: >
bash -c '
docker tag nfvpe/multus:latest-amd64 nfvpe/multus:snapshot;
docker tag nfvpe/multus:latest-amd64 nfvpe/multus:snapshot-amd64;
docker tag nfvpe/multus:latest-ppc64le nfvpe/multus:snapshot-ppc64le;
docker login -u "$REGISTRY_USER" -p "$REGISTRY_PASS";
docker push nfvpe/multus:snapshot;
docker push nfvpe/multus:snapshot-amd64;
docker push nfvpe/multus:snapshot-ppc64le;
docker push nfvpe/multus:latest;
docker push nfvpe/multus:latest-amd64;
docker push nfvpe/multus:latest-ppc64le;
echo done'
after_success:
# put build tgz to bintray

View File

@ -1,10 +1,13 @@
# This Dockerfile is used to build the image available on DockerHub
FROM centos:centos7
# Add everything
ADD . /usr/src/multus-cni
ENV INSTALL_PKGS "git golang"
RUN yum install -y $INSTALL_PKGS && \
RUN rpm --import https://mirror.go-repo.io/centos/RPM-GPG-KEY-GO-REPO && \
curl -s https://mirror.go-repo.io/centos/go-repo.repo | tee /etc/yum.repos.d/go-repo.repo && \
yum install -y $INSTALL_PKGS && \
rpm -V $INSTALL_PKGS && \
cd /usr/src/multus-cni && \
./build && \
@ -14,14 +17,6 @@ RUN yum install -y $INSTALL_PKGS && \
WORKDIR /
LABEL io.k8s.display-name="Multus CNI" \
io.k8s.description="This is a component of OpenShift Container Platform and provides a meta CNI plugin." \
io.openshift.tags="openshift" \
maintainer="Doug Smith <dosmith@redhat.com>"
ADD ./images/entrypoint.sh /
# does it require a root user?
# USER 1001
ENTRYPOINT /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]

20
Dockerfile.openshift Normal file
View File

@ -0,0 +1,20 @@
# This dockerfile is specific to building Multus for OpenShift
FROM openshift/origin-release:golang-1.10 as builder
ADD . /usr/src/multus-cni
WORKDIR /usr/src/multus-cni
ENV GO111MODULE=off
RUN ./build
FROM openshift/origin-base
RUN mkdir -p /usr/src/multus-cni/images && mkdir -p /usr/src/multus-cni/bin
COPY --from=builder /usr/src/multus-cni/bin/multus /usr/src/multus-cni/bin
ADD ./images/entrypoint.sh /
LABEL io.k8s.display-name="Multus CNI" \
io.k8s.description="This is a component of OpenShift Container Platform and provides a meta CNI plugin." \
io.openshift.tags="openshift" \
maintainer="Doug Smith <dosmith@redhat.com>"
ENTRYPOINT ["/entrypoint.sh"]

15
OWNERS Normal file
View File

@ -0,0 +1,15 @@
reviewers:
- dougbtv
- dcbw
- squeed
- zshi
- fepan
- s1061123
approvers:
- dougbtv
- dcbw
- squeed
- zshi
- fepan
- s1061123

615
README.md
View File

@ -1,44 +1,28 @@
# Multus-CNI
![multus-cni Logo](https://github.com/intel/multus-cni/blob/master/doc/images/Multus.png)
[![Travis CI](https://travis-ci.org/intel/multus-cni.svg?branch=master)](https://travis-ci.org/intel/multus-cni/builds)[![Go Report Card](https://goreportcard.com/badge/github.com/intel/multus-cni)](https://goreportcard.com/report/github.com/intel/multus-cni)
* [MULTUS CNI plugin](#multus-cni-plugin)
* [Quickstart Guide](#quickstart-guide)
* [Multi-Homed pod](#multi-homed-pod)
* [Building from source](#building-from-source)
* [Work flow](#work-flow)
* [Usage with Kubernetes CRD based network objects](#usage-with-kubernetes-crd-based-network-objects)
* [Creating "Network" resources in Kubernetes](#creating-network-resources-in-kubernetes)
* [<strong>CRD based Network objects</strong>](#crd-based-network-objects)
* [Configuring Multus to use the kubeconfig](#configuring-multus-to-use-the-kubeconfig)
* [Configuring Multus to use kubeconfig and a default network](#configuring-multus-to-use-kubeconfig-and-a-default-network)
* [Configuring Pod to use the CRD network objects](#configuring-pod-to-use-the-crd-network-objects)
* [Verifying Pod network interfaces](#verifying-pod-network-interfaces)
* [Using with Multus conf file](#using-with-multus-conf-file)
* [Logging Options](#logging-options)
* [Testing Multus CNI](#testing-multus-cni)
* [Multiple flannel networks](#multiple-flannel-networks)
* [Configure Kubernetes with CNI](#configure-kubernetes-with-cni)
* [Launching workloads in Kubernetes](#launching-workloads-in-kubernetes)
* [Multus additional plugins](#multus-additional-plugins)
* [NFV based networking in Kubernetes](#nfv-based-networking-in-kubernetes)
* [Need help](#need-help)
* [Contacts](#contacts)
Multus CNI enables attaching multiple network interfaces to pods in Kubernetes.
# MULTUS CNI plugin
- _Multus_ is a latin word for &quot;Multi&quot;
- As the name suggests, it acts as a Multi plugin in Kubernetes and provides the multiple network interface support in a pod
- Multus supports all [reference plugins](https://github.com/containernetworking/plugins) (eg. [Flannel](https://github.com/containernetworking/plugins/tree/master/plugins/meta/flannel), [DHCP](https://github.com/containernetworking/plugins/tree/master/plugins/ipam/dhcp), [Macvlan](https://github.com/containernetworking/plugins/tree/master/plugins/main/macvlan)) that implement the CNI specification and all 3rd party plugins (eg. [Calico](https://github.com/projectcalico/cni-plugin), [Weave](https://github.com/weaveworks/weave), [Cilium](https://github.com/cilium/cilium), [Contiv](https://github.com/contiv/netplugin)). In addition to it, Multus supports [SRIOV](https://github.com/hustcat/sriov-cni), [SRIOV-DPDK](https://github.com/Intel-Corp/sriov-cni), [OVS-DPDK &amp; VPP](https://github.com/intel/vhost-user-net-plugin) workloads in Kubernetes with both cloud native and NFV based applications in Kubernetes
- It is a contact between the container runtime and other plugins, and it doesn&#39;t have any of its own net configuration, it calls other plugins like flannel/calico to do the real net conf job.
- Multus reuses the concept of invoking delegates as used in flannel by grouping multiple plugins into delegates and invoking them in the sequential order of the CNI configuration file provided in json format
- The default network gets "eth0" and additional network Pod interface name as “net0”, “net1”,… “netX and so on. Multus also support interface names from the user.
- Multus is one of the projects in the [Baremetal Container Experience kit](https://networkbuilders.intel.com/network-technologies/container-experience-kits).
## How it works
Please check the [CNI](https://github.com/containernetworking/cni) documentation for more information on container networking.
Multus CNI is a container network interface (CNI) plugin for Kubernetes that enables attaching multiple network interfaces to pods. Typically, in Kubernetes each pod only has one network interface (apart from a loopback) -- with Multus you can create a multi-homed pod that has multiple interfaces. This is accomplished by Multus acting as a "meta-plugin", a CNI plugin that can call multiple other CNI plugins.
# Quickstart Guide
Multus CNI follows the [Kubernetes Network Custom Resource Definition De-facto Standard](https://docs.google.com/document/d/1Ny03h6IDVy_e_vmElOqR7UdTPAG_RNydhVE1Kx54kFQ/edit) to provide a standardized method by which to specify the configurations for additional network interfaces. This standard is put forward by the Kubernetes [Network Plumbing Working Group](https://docs.google.com/document/d/1oE93V3SgOGWJ4O1zeD1UmpeToa0ZiiO6LqRAmZBPFWM/edit).
Multus may be deployed as a Daemonset, and is provided in this guide along with Flannel. Flannel is deployed as a pod-to-pod network that is used as our "default network". Each network attachment is made in addition to this default network.
Multus is one of the projects in the [Baremetal Container Experience kit](https://networkbuilders.intel.com/network-technologies/container-experience-kits)
### Multi-Homed pod
Here's an illustration of the network interfaces attached to a pod, as provisioned by Multus CNI. The diagram shows the pod with three interfaces: `eth0`, `net0` and `net1`. `eth0` connects kubernetes cluster network to connect with kubernetes server/services (e.g. kubernetes api-server, kubelet and so on). `net0` and `net1` are additional network attachments and connect to other networks by using [other CNI plugins](https://kubernetes.io/docs/concepts/extend-kubernetes/compute-storage-net/network-plugins/) (e.g. vlan/vxlan/ptp).
![multus-pod-image](doc/images/multus-pod-image.svg)
## Quickstart Installation Guide
Multus may be deployed as a Daemonset, and is provided in this guide along with Flannel. Flannel is deployed as a pod-to-pod network that is used as our "default network" (a network interface that every pod will be created with). Each network attachment is made in addition to this default network.
Firstly, clone this GitHub repository. We'll apply files to `kubectl` from this repo.
@ -48,561 +32,22 @@ We apply these files as such:
$ cat ./images/{multus-daemonset.yml,flannel-daemonset.yml} | kubectl apply -f -
```
Create a CNI configuration loaded as a CRD object, in this case a macvlan CNI configuration is defined. You may replace the `config` field with any valid CNI configuration where the CNI binary is available on the nodes.
This will configure your systems to be ready to use Multus CNI, but, to get started with adding additional interfaces to your pods, refer to our complete [quick-start guide](doc/quickstart.md)
```
cat <<EOF | kubectl create -f -
apiVersion: "k8s.cni.cncf.io/v1"
kind: NetworkAttachmentDefinition
metadata:
name: macvlan-conf
spec:
config: '{
"cniVersion": "0.3.0",
"type": "macvlan",
"master": "eth0",
"mode": "bridge",
"ipam": {
"type": "host-local",
"subnet": "192.168.1.0/24",
"rangeStart": "192.168.1.200",
"rangeEnd": "192.168.1.216",
"routes": [
{ "dst": "0.0.0.0/0" }
],
"gateway": "192.168.1.1"
}
}'
EOF
```
## Additional installation Options
You may then create a pod which attached this additional interface, where the annotation correlates to the `name` in the `NetworkAttachmentDefinition` above.
- Install via daemonset using the quick-start guide, above.
- Download binaries from [release page](https://github.com/intel/multus-cni/releases)
- By Docker image from [Docker Hub](https://hub.docker.com/r/nfvpe/multus/tags/)
- Or, roll-you-own and build from source
- See [Development](doc/development.md)
```
cat <<EOF | kubectl create -f -
apiVersion: v1
kind: Pod
metadata:
name: samplepod
annotations:
k8s.v1.cni.cncf.io/networks: macvlan-conf
spec:
containers:
- name: samplepod
command: ["/bin/bash", "-c", "sleep 2000000000000"]
image: dougbtv/centos-network
EOF
```
## Comprehensive Documentation
You may now inspect the pod and see that there is an additional interface configured, like so:
- [How to use](doc/how-to-use.md)
- [Configuration](doc/configuration.md)
- [Development](doc/development.md)
```
$ kubectl exec -it samplepod -- ip a
```
# Kubernetes Network Custom Resource Definition De-facto Standard - Reference implementation
* This project is a reference implementation for Kubernetes Network Custom Resource Definition De-facto Standard. For more information refer [Network Plumbing Working Group Agenda](https://docs.google.com/document/d/1oE93V3SgOGWJ4O1zeD1UmpeToa0ZiiO6LqRAmZBPFWM/edit)
* Kubernetes Network Custom Resource Definition De-facto Standard [documentation link](https://docs.google.com/document/d/1Ny03h6IDVy_e_vmElOqR7UdTPAG_RNydhVE1Kx54kFQ/edit)
* Reference implementation support following modes
* CNI config JSON in network object
* Not using CNI config (“thick” plugin usecase)
* CNI configuration stored in on-disk file
> refer the section 3.2 Network Object Definition for more details in Kubernetes Network Custom Resource Definition De-facto Standard
* Refer the reference implementation presentation and demo details - [link](https://docs.google.com/presentation/d/1dbCin6MnhK-BjjcVun5YiPTL99VA2uSiyWAtWAPNlIc/edit?usp=sharing)
* Release version from v2.0 is not compatible with v1.1 and v1.2 network CRD
* [MULTUS CNI plugin](#multus-cni-plugin)specifications.
## Multi-Homed pod
<p align="center">
<img src="doc/images/multus_cni_pod.png" width="1008" />
</p>
## Building from source
**This plugin requires Go 1.8 (or later) to build.**
```
#./build
```
## Work flow
<p align="center">
<img src="doc/images/workflow.png" width="1008" />
</p>
## Network configuration reference
- name (string, required): the name of the network
- type (string, required): &quot;multus&quot;
- kubeconfig (string, optional): kubeconfig file for the out of cluster communication with kube-apiserver. See the example [kubeconfig](https://github.com/intel/multus-cni/blob/master/doc/node-kubeconfig.yaml)
- delegates (([]map,required): number of delegate details in the Multus
## Usage with Kubernetes CRD based network objects
Kubelet is responsible for establishing network interfaces for pods; it does this by invoking its configured CNI plugin. When Multus is invoked it retrieves network references from Pod annotation. Multus then uses these network references to get network configurations. Network configurations are defined as Kubernetes Custom Resource Object (CRD). These configurations describe which CNI plugins to invoke and what their configurations are. The order of plugin invocation is important as it identifies the primary plugin. This order is taken from network object references given in a Pod spec.
<p align="center">
<img src="doc/images/multus_crd_usage_diagram.JPG" width="1008" />
</p>
### Creating &quot;Network&quot; resources in Kubernetes
You may wish to create the `network-attachment-definition` manually if you haven't installed using the daemonset technique, which includes the CRD, and you can verify if it's loaded with `kubectl get crd` and look for the presence of `network-attachment-definition`.
1. Create a Custom Resource Definition (CRD) `crdnetwork.yaml`; for the network object using the YAML from the examples directory.
```
$ kubectl create -f ./examples/crd.yml
customresourcedefinition.apiextensions.k8s.io/network-attachment-definitions.k8s.cni.cncf.io created
```
3. Run kubectl get command to check the Network CRD creation
```
$ kubectl get crd
NAME KIND
network-attachment-definitions.k8s.cni.cncf.io CustomResourceDefinition.v1beta1.apiextensions.k8s.io
```
### Creating CRD network resources in Kubernetes
1. After creating CRD network object you can create network resources in Kubernetes. These network resources may contain additional underlying CNI plugin parameters given in JSON format. In the following example shown below the args field contains parameters that will be passed into Flannel plugin.
2. Save the following YAML to flannel-network.yaml
```
apiVersion: "k8s.cni.cncf.io/v1"
kind: NetworkAttachmentDefinition
metadata:
name: flannel-networkobj
spec:
config: '{
"cniVersion": "0.3.0",
"type": "flannel",
"delegate": {
"isDefaultGateway": true
}
}'
```
3. Create the custom resource definition
```
$ kubectl create -f ./flannel-network.yaml
network "flannel-networkobj" created
$ kubectl get net-attach-def
NAME AGE
flannel-networkobj 26s
```
4. Get the custom network object details
```
apiVersion: k8s.cni.cncf.io/v1
kind: NetworkAttachmentDefinition
metadata:
clusterName: ""
creationTimestamp: 2018-05-17T09:13:20Z
deletionGracePeriodSeconds: null
deletionTimestamp: null
initializers: null
name: flannel-networkobj
namespace: default
resourceVersion: "21176114"
selfLink: /apis/k8s.cni.cncf.io/v1/namespaces/default/networks/flannel-networkobj
uid: 8ac8f873-59b2-11e8-8308-a4bf01024e6f
spec:
config: '{ "cniVersion": "0.3.0", "type": "flannel", "delegate": { "isDefaultGateway":
true } }'
```
5. Save the following YAML to sriov-network.yaml to creating sriov network object. ( Refer to [Intel - SR-IOV CNI](https://github.com/Intel-Corp/sriov-cni) or contact @kural in [Intel-Corp Slack](https://intel-corp.herokuapp.com/) for running the DPDK based workloads in Kubernetes)
```
apiVersion: "k8s.cni.cncf.io/v1"
kind: NetworkAttachmentDefinition
metadata:
name: sriov-conf
spec:
config: '{
"type": "sriov",
"if0": "enp12s0f1",
"ipam": {
"type": "host-local",
"subnet": "10.56.217.0/24",
"rangeStart": "10.56.217.171",
"rangeEnd": "10.56.217.181",
"routes": [
{ "dst": "0.0.0.0/0" }
],
"gateway": "10.56.217.1"
}
}'
```
6. Likewise save the following YAML to sriov-vlanid-l2enable-network.yaml to create another sriov based network object:
```
apiVersion: "k8s.cni.cncf.io/v1"
kind: NetworkAttachmentDefinition
metadata:
name: sriov-vlanid-l2enable-conf
spec:
config: '{
"type": "sriov",
"if0": "enp2s0",
"vlan": 210,
"l2enable": true
}'
```
7. Follow step 3 above to create &quot;sriov-vlanid-l2enable-conf&quot; and &quot;sriov-conf&quot; network objects
8. View network objects using kubectl
```
# kubectl get net-attach-def
NAME AGE
flannel-networkobj 29m
sriov-conf 6m
sriov-vlanid-l2enable-conf 2m
```
### Configuring Multus to use the kubeconfig
1. Create a Mutlus CNI configuration file on each Kubernetes node. This file should be created in: /etc/cni/net.d/multus-cni.conf with the content shown below. Use only the absolute path to point to the kubeconfig file (as it may change depending upon your cluster env). We are assuming all CNI plugin binaries are default location (`/opt/cni/bin dir`)
```
{
"name": "node-cni-network",
"type": "multus",
"kubeconfig": "/etc/kubernetes/node-kubeconfig.yaml"
}
```
### Configuring Multus to use kubeconfig and a default network
1. Many users want Kubernetes default networking feature along with network objects. Refer to issues [#14](https://github.com/intel/multus-cni/issues/14) &amp; [#17](https://github.com/intel/multus-cni/issues/17) for more information. In the following Multus configuration, Weave act as the default network in the absence of network field in the pod metadata annotation.
```
{
"name": "node-cni-network",
"type": "multus",
"kubeconfig": "/etc/kubernetes/node-kubeconfig.yaml",
"delegates": [{
"type": "weave-net",
"hairpinMode": true
}]
}
```
Configurations referenced in annotations are created in addition to the default network.
### Configuring Pod to use the CRD network objects
1. Save the following YAML to pod-multi-network.yaml. In this case flannel-conf network object acts as the primary network.
```
# cat pod-multi-network.yaml
apiVersion: v1
kind: Pod
metadata:
name: multus-multi-net-poc
annotations:
k8s.v1.cni.cncf.io/networks: '[
{ "name": "flannel-conf" },
{ "name": "sriov-conf" },
{ "name": "sriov-vlanid-l2enable-conf",
"interfaceRequest": "north" }
]'
spec: # specification of the pod's contents
containers:
- name: multus-multi-net-poc
image: "busybox"
command: ["top"]
stdin: true
tty: true
```
2. Create Multiple network based pod from the master node
```
# kubectl create -f ./pod-multi-network.yaml
pod "multus-multi-net-poc" created
```
3. Get the details of the running pod from the master
```
# kubectl get pods
NAME READY STATUS RESTARTS AGE
multus-multi-net-poc 1/1 Running 0 30s
```
### Verifying Pod network interfaces
1. Run `ifconfig` command in Pod:
```
# kubectl exec -it multus-multi-net-poc -- ifconfig
eth0 Link encap:Ethernet HWaddr C6:43:7C:09:B4:9C
inet addr:10.128.0.4 Bcast:0.0.0.0 Mask:255.255.255.0
UP BROADCAST RUNNING MULTICAST MTU:1450 Metric:1
RX packets:8 errors:0 dropped:0 overruns:0 frame:0
TX packets:1 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:648 (648.0 B) TX bytes:42 (42.0 B)
lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
inet6 addr: ::1/128 Scope:Host
UP LOOPBACK RUNNING MTU:65536 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
net0 Link encap:Ethernet HWaddr 06:21:91:2D:74:B9
inet addr:192.168.42.3 Bcast:0.0.0.0 Mask:255.255.255.0
inet6 addr: fe80::421:91ff:fe2d:74b9/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1450 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:8 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:0 (0.0 B) TX bytes:648 (648.0 B)
net1 Link encap:Ethernet HWaddr D2:94:98:82:00:00
inet addr:10.56.217.171 Bcast:0.0.0.0 Mask:255.255.255.0
inet6 addr: fe80::d094:98ff:fe82:0/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:2 errors:0 dropped:0 overruns:0 frame:0
TX packets:8 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:120 (120.0 B) TX bytes:648 (648.0 B)
north Link encap:Ethernet HWaddr BE:F2:48:42:83:12
inet6 addr: fe80::bcf2:48ff:fe42:8312/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:1420 errors:0 dropped:0 overruns:0 frame:0
TX packets:1276 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:95956 (93.7 KiB) TX bytes:82200 (80.2 KiB)
```
| Interface name | Description |
| --- | --- |
| lo | loopback |
| eth0 | weave network interface |
| net0 | Flannel network tap interface |
| net1 | VF0 of NIC 1 assigned to the container by [Intel - SR-IOV CNI](https://github.com/intel/sriov-cni) plugin |
| north | VF0 of NIC 2 assigned with VLAN ID 210 to the container by SR-IOV CNI plugin |
2. Check the vlan ID of the NIC 2 VFs
```
# ip link show enp2s0
20: enp2s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP mode DEFAULT group default qlen 1000
link/ether 24:8a:07:e8:7d:40 brd ff:ff:ff:ff:ff:ff
vf 0 MAC 00:00:00:00:00:00, vlan 210, spoof checking off, link-state auto
vf 1 MAC 00:00:00:00:00:00, vlan 4095, spoof checking off, link-state auto
vf 2 MAC 00:00:00:00:00:00, vlan 4095, spoof checking off, link-state auto
vf 3 MAC 00:00:00:00:00:00, vlan 4095, spoof checking off, link-state auto
```
## Using with Multus conf file
Given the following network configuration:
```
# tee /etc/cni/net.d/multus-cni.conf <<-'EOF'
{
"name": "multus-demo-network",
"type": "multus",
"delegates": [
{
"type": "sriov",
#part of sriov plugin conf
"if0": "enp12s0f0",
"ipam": {
"type": "host-local",
"subnet": "10.56.217.0/24",
"rangeStart": "10.56.217.131",
"rangeEnd": "10.56.217.190",
"routes": [
{ "dst": "0.0.0.0/0" }
],
"gateway": "10.56.217.1"
}
},
{
"type": "ptp",
"ipam": {
"type": "host-local",
"subnet": "10.168.1.0/24",
"rangeStart": "10.168.1.11",
"rangeEnd": "10.168.1.20",
"routes": [
{ "dst": "0.0.0.0/0" }
],
"gateway": "10.168.1.1"
}
},
{
"type": "flannel",
"delegate": {
"isDefaultGateway": true
}
}
]
}
EOF
```
## Logging Options
You may wish to enable some enhanced logging for Multus, especially during the process where you're configuring Multus and need to understand what is or isn't working with your particular configuration.
Multus will always log via `STDERR`, which is the standard method by which CNI plugins communicate errors, and these errors are logged by the Kubelet. This method is always enabled.
### Writing to a Log File
Optionally, you may have Multus log to a file on the filesystem. This file will be written locally on each node where Multus is executed. You may configure this via the `LogFile` option in the CNI configuration. By default this additional logging to a flat file is disabled.
For example in your CNI configuration, you may set:
```
"LogFile": "/var/log/multus.log",
```
### Logging Level
The default logging level is set as `panic` -- this will log only the most critical errors, and is the least verbose logging level.
The available logging level values, in decreasing order of verbosity are:
* `debug`
* `error`
* `panic`
You may configure the logging level by using the `LogLevel` option in your CNI configuration. For example:
```
"LogLevel": "debug",
```
## Testing Multus CNI
### Multiple flannel networks
Github user [YYGCui](https://github.com/YYGCui) has used multiple flannel network to work with Multus CNI plugin. Please refer to this [closed issue](https://github.com/intel/multus-cni/issues/7) for ,multiple overlay network support with Multus CNI.
Make sure that the multus, [sriov](https://github.com/Intel-Corp/sriov-cni), [flannel](https://github.com/containernetworking/cni/blob/master/Documentation/flannel.md), and [ptp](https://github.com/containernetworking/cni/blob/master/Documentation/ptp.md) binaries are in the /opt/cni/bin directories and follow the steps as mentioned in the [CNI](https://github.com/containernetworking/cni/#running-a-docker-container-with-network-namespace-set-up-by-cni-plugins)
#### Configure Kubernetes with CNI
Kubelet must be configured to run with the CNI network plugin. Edit `/etc/kubernetes/kubelet` file and add `--network-plugin=cni` flags in `KUBELET\_OPTS `as shown below:
```
KUBELET_OPTS="...
--network-plugin-dir=/etc/cni/net.d
--network-plugin=cni
"
```
Refer to the Kubernetes User Guide and network plugin for more information.
- [Single Node](https://kubernetes.io/docs/getting-started-guides/fedora/fedora_manual_config/)
- [Multi Node](https://kubernetes.io/docs/getting-started-guides/fedora/flannel_multi_node_cluster/)
- [Network plugin](https://kubernetes.io/docs/admin/network-plugins/)
#### Launching workloads in Kubernetes
With Multus CNI configured as described in sections above each workload launched via a Kubernetes Pod will have multiple network interfacesLaunch the workload using yaml file in the kubernetes master, with above configuration in the multus CNI, each pod should have multiple interfaces.
Note: To verify whether Multus CNI plugin is working correctly, create a pod containing one `busybox` container and execute `ip link` command to check if interfaces management follows configuration.
1. Create `multus-test.yaml` file containing below configuration. Created pod will consist of one `busybox` container running `top` command.
```
apiVersion: v1
kind: Pod
metadata:
name: multus-test
spec: # specification of the pod's contents
restartPolicy: Never
containers:
- name: test1
image: "busybox"
command: ["top"]
stdin: true
tty: true
```
2. Create pod using command:
```
# kubectl create -f multus-test.yaml
pod "multus-test" created
```
3. Run &quot;ip link&quot; command inside the container:
```
# 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
3: eth0@if41: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue
link/ether 26:52:6b:d8:44:2d brd ff:ff:ff:ff:ff:ff
20: net0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq qlen 1000
link/ether f6:fb:21:4f:1d:63 brd ff:ff:ff:ff:ff:ff
21: net1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq qlen 1000
link/ether 76:13:b1:60:00:00 brd ff:ff:ff:ff:ff:ff
```
| Interface name | Description |
| --- | --- |
| lo | loopback |
| eth0@if41 | Flannel network tap interface |
| net0 | VF assigned to the container by [SR-IOV CNI](https://github.com/Intel-Corp/sriov-cni) plugin |
| net1 | ptp localhost interface |
## Multus additional plugins
- [DPDK-SRIOV CNI](https://github.com/Intel-Corp/sriov-cni)
- [Vhostuser CNI](https://github.com/intel/vhost-user-net-plugin) - a Dataplane network plugin - Supports OVS-DPDK &amp; VPP
- [Bond CNI](https://github.com/Intel-Corp/bond-cni) - For fail-over and high availability of networking
## NFV based networking in Kubernetes
- KubeCon workshop on [&quot;Enabling NFV features in Kubernetes&quot;](https://kccncna17.sched.com/event/Cvnw/enabling-nfv-features-in-kubernetes-hosted-by-kuralamudhan-ramakrishnan-ivan-coughlan-intel) presentation [slide deck](https://www.slideshare.net/KuralamudhanRamakris/enabling-nfv-features-in-kubernetes-83923352)
- Feature brief
- [Multiple Network Interface Support in Kubernetes](https://builders.intel.com/docs/networkbuilders/multiple-network-interfaces-support-in-kubernetes-feature-brief.pdf)
- [Enhanced Platform Awareness in Kubernetes](https://builders.intel.com/docs/networkbuilders/enhanced-platform-awareness-feature-brief.pdf)
- Application note
- [Multiple Network Interfaces in Kubernetes and Container Bare Metal](https://builders.intel.com/docs/networkbuilders/multiple-network-interfaces-in-kubernetes-application-note.pdf)
- [Enhanced Platform Awareness Features in Kubernetes](https://builders.intel.com/docs/networkbuilders/enhanced-platform-awareness-in-kubernetes-application-note.pdf)
- White paper
- [Enabling New Features with Kubernetes for NFV](https://builders.intel.com/docs/networkbuilders/enabling_new_features_in_kubernetes_for_NFV.pdf)
- Multus&#39;s related project github pages
- [Multus](https://github.com/Intel-Corp/multus-cni)
- [SRIOV - DPDK CNI](https://github.com/Intel-Corp/sriov-cni)
- [Vhostuser - VPP &amp; OVS - DPDK CNI](https://github.com/intel/vhost-user-net-plugin)
- [Bond CNI](https://github.com/Intel-Corp/bond-cni)
- [Node Feature Discovery](https://github.com/kubernetes-incubator/node-feature-discovery)
- [CPU Manager for Kubernetes](https://github.com/Intel-Corp/CPU-Manager-for-Kubernetes)
## Need help
- Read [Containers Experience Kits](https://networkbuilders.intel.com/network-technologies/container-experience-kits)
- Try our container exp kit demo - KubeCon workshop on [Enabling NFV Features in Kubernetes](https://github.com/intel/container-experience-kits-demo-area/)
- Join us on [#intel-sddsg-slack](https://intel-corp.herokuapp.com/) slack channel and ask question in [#general-discussion](https://intel-corp-team.slack.com/messages/C4C5RSEER)
- You can also [email](mailto:kuralamudhan.ramakrishnan@intel.com) us
- Feel free to [submit](https://github.com/Intel-Corp/multus-cni/issues/new) an issue
Please fill in the Questions/feedback - [google-form](https://goo.gl/forms/upBWyGs8Wmq69IEi2)!
## Contacts
For any questions about Multus CNI, please reach out on github issue or feel free to contact the developer @kural in our [Intel-Corp Slack](https://intel-corp.herokuapp.com/)
## Contact Us
For any questions about Multus CNI, feel free to ask a question in #general in the [Intel-Corp Slack](https://intel-corp.herokuapp.com/), or open up a GitHub issue.

51
build
View File

@ -1,17 +1,48 @@
#!/usr/bin/env bash
set -e
ORG_PATH="github.com/intel"
REPO_PATH="${ORG_PATH}/multus-cni"
DEST_DIR="bin"
if [ ! -h gopath/src/${REPO_PATH} ]; then
mkdir -p gopath/src/${ORG_PATH}
ln -s ../../../.. gopath/src/${REPO_PATH} || exit 255
if [ ! -d ${DEST_DIR} ]; then
mkdir ${DEST_DIR}
fi
export GO15VENDOREXPERIMENT=1
export GOBIN=${PWD}/bin
export GOPATH=${PWD}/gopath
# Add version/commit/date into binary
# In case of TravisCI, need to check error code of 'git describe'.
set +e
git describe --tags --abbrev=0 > /dev/null 2>&1
if [ "$?" != "0" ]; then
VERSION="master"
else
VERSION=$(git describe --tags --abbrev=0)
fi
set -e
DATE=$(date --iso-8601=seconds)
COMMIT=$(git rev-parse --verify HEAD)
LDFLAGS="-X main.version=${VERSION:-master} -X main.commit=${COMMIT} -X main.date=${DATE}"
export CGO_ENABLED=0
echo "Building plugins"
go install "$@" ${REPO_PATH}/multus
# this if... will be removed when gomodules goes default
if [ "$GO111MODULE" == "off" ]; then
echo "Building plugin without go module"
echo "Warning: this will be deprecated in near future so please use go modules!"
ORG_PATH="github.com/intel"
REPO_PATH="${ORG_PATH}/multus-cni"
if [ ! -h gopath/src/${REPO_PATH} ]; then
mkdir -p gopath/src/${ORG_PATH}
ln -s ../../../.. gopath/src/${REPO_PATH} || exit 255
fi
export GO15VENDOREXPERIMENT=1
export GOBIN=${PWD}/bin
export GOPATH=${PWD}/gopath
go install -tags no_openssl -ldflags "${LDFLAGS}" "$@" ${REPO_PATH}/multus
else
# build with go modules
export GO111MODULE=on
echo "Building plugins"
go build -o ${DEST_DIR}/multus -tags no_openssl -ldflags "${LDFLAGS}" "$@" ./multus
fi

110
checkpoint/checkpoint.go Normal file
View File

@ -0,0 +1,110 @@
// Copyright (c) 2018 Intel Corporation
//
// 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 checkpoint
import (
"encoding/json"
"io/ioutil"
"github.com/intel/multus-cni/logging"
"github.com/intel/multus-cni/types"
v1 "k8s.io/api/core/v1"
)
const (
checkPointfile = "/var/lib/kubelet/device-plugins/kubelet_internal_checkpoint"
)
type PodDevicesEntry struct {
PodUID string
ContainerName string
ResourceName string
DeviceIDs []string
AllocResp []byte
}
type checkpointData struct {
PodDeviceEntries []PodDevicesEntry
RegisteredDevices map[string][]string
}
type Data struct {
Data checkpointData
Checksum uint64
}
type checkpoint struct {
fileName string
podEntires []PodDevicesEntry
}
// GetCheckpoint returns an instance of Checkpoint
func GetCheckpoint() (types.ResourceClient, error) {
logging.Debugf("GetCheckpoint(): invoked")
return getCheckpoint(checkPointfile)
}
func getCheckpoint(filePath string) (types.ResourceClient, error) {
cp := &checkpoint{fileName: filePath}
err := cp.getPodEntries()
if err != nil {
return nil, err
}
logging.Debugf("getCheckpoint(): created checkpoint instance with file: %s", filePath)
return cp, nil
}
// getPodEntries gets all Pod device allocation entries from checkpoint file
func (cp *checkpoint) getPodEntries() error {
cpd := &Data{}
rawBytes, err := ioutil.ReadFile(cp.fileName)
if err != nil {
return logging.Errorf("getPodEntries(): error reading file %s\n%v\n", checkPointfile, err)
}
if err = json.Unmarshal(rawBytes, cpd); err != nil {
return logging.Errorf("getPodEntries(): error unmarshalling raw bytes %v", err)
}
cp.podEntires = cpd.Data.PodDeviceEntries
logging.Debugf("getPodEntries(): podEntires %+v", cp.podEntires)
return nil
}
// GetComputeDeviceMap returns an instance of a map of ResourceInfo
func (cp *checkpoint) GetPodResourceMap(pod *v1.Pod) (map[string]*types.ResourceInfo, error) {
podID := string(pod.UID)
resourceMap := make(map[string]*types.ResourceInfo)
if podID == "" {
return nil, logging.Errorf("GetPodResourceMap(): invalid Pod cannot be empty")
}
for _, pod := range cp.podEntires {
if pod.PodUID == podID {
entry, ok := resourceMap[pod.ResourceName]
if ok {
// already exists; append to it
entry.DeviceIDs = append(entry.DeviceIDs, pod.DeviceIDs...)
} else {
// new entry
resourceMap[pod.ResourceName] = &types.ResourceInfo{DeviceIDs: pod.DeviceIDs}
}
}
}
return resourceMap, nil
}

View File

@ -0,0 +1,131 @@
package checkpoint
import (
"os"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"io/ioutil"
"testing"
"github.com/intel/multus-cni/types"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
k8sTypes "k8s.io/apimachinery/pkg/types"
)
const (
fakeTempFile = "/tmp/kubelet_internal_checkpoint"
)
type fakeCheckpoint struct {
fileName string
}
func (fc *fakeCheckpoint) WriteToFile(inBytes []byte) error {
return ioutil.WriteFile(fc.fileName, inBytes, 0600)
}
func (fc *fakeCheckpoint) DeleteFile() error {
return os.Remove(fc.fileName)
}
func TestCheckpoint(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Checkpoint")
}
var _ = BeforeSuite(func() {
sampleData := `{
"Data": {
"PodDeviceEntries": [
{
"PodUID": "970a395d-bb3b-11e8-89df-408d5c537d23",
"ContainerName": "appcntr1",
"ResourceName": "intel.com/sriov_net_A",
"DeviceIDs": [
"0000:03:02.3",
"0000:03:02.0"
],
"AllocResp": "CikKC3NyaW92X25ldF9BEhogMDAwMDowMzowMi4zIDAwMDA6MDM6MDIuMA=="
}
],
"RegisteredDevices": {
"intel.com/sriov_net_A": [
"0000:03:02.1",
"0000:03:02.2",
"0000:03:02.3",
"0000:03:02.0"
],
"intel.com/sriov_net_B": [
"0000:03:06.3",
"0000:03:06.0",
"0000:03:06.1",
"0000:03:06.2"
]
}
},
"Checksum": 229855270
}`
fakeCheckpoint := &fakeCheckpoint{fileName: fakeTempFile}
err := fakeCheckpoint.WriteToFile([]byte(sampleData))
Expect(err).NotTo(HaveOccurred())
})
var _ = Describe("Kubelet checkpoint data read operations", func() {
Context("Using /tmp/kubelet_internal_checkpoint file", func() {
var (
cp types.ResourceClient
err error
resourceMap map[string]*types.ResourceInfo
resourceInfo *types.ResourceInfo
resourceAnnot = "intel.com/sriov_net_A"
)
It("should get a Checkpoint instance from file", func() {
cp, err = getCheckpoint(fakeTempFile)
Expect(err).NotTo(HaveOccurred())
})
It("should return a ResourceMap instance", func() {
podUID := k8sTypes.UID("970a395d-bb3b-11e8-89df-408d5c537d23")
fakePod := &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "fakePod",
Namespace: "podNamespace",
UID: podUID,
},
}
rmap, err := cp.GetPodResourceMap(fakePod)
Expect(err).NotTo(HaveOccurred())
Expect(rmap).NotTo(BeEmpty())
resourceMap = rmap
})
It("resourceMap should have value for \"intel.com/sriov_net_A\"", func() {
rInfo, ok := resourceMap[resourceAnnot]
Expect(ok).To(BeTrue())
resourceInfo = rInfo
})
It("should have 2 deviceIDs", func() {
Expect(len(resourceInfo.DeviceIDs)).To(BeEquivalentTo(2))
})
It("should have \"0000:03:02.3\" in deviceIDs[0]", func() {
Expect(resourceInfo.DeviceIDs[0]).To(BeEquivalentTo("0000:03:02.3"))
})
It("should have \"0000:03:02.0\" in deviceIDs[1]", func() {
Expect(resourceInfo.DeviceIDs[1]).To(BeEquivalentTo("0000:03:02.0"))
})
})
})
var _ = AfterSuite(func() {
fakeCheckpoint := &fakeCheckpoint{fileName: fakeTempFile}
err := fakeCheckpoint.DeleteFile()
Expect(err).NotTo(HaveOccurred())
})

95
deployment/webhook/certs.sh Executable file
View File

@ -0,0 +1,95 @@
#!/bin/bash
# Copyright (c) 2018 Intel Corporation
#
# 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.
# create temp dir to store intermediate files
tmp=$(mktemp -d)
# generate private key
echo "Generating private RSA key..."
openssl genrsa -out ${tmp}/webhook-key.pem 2048 >/dev/null 2>&1
# generate CSR
echo "Generating CSR configuration file..."
cat <<EOF >> ${tmp}/webhook.conf
[req]
req_extensions = v3_req
distinguished_name = req_distinguished_name
[req_distinguished_name]
[ v3_req ]
basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
subjectAltName = @alt_names
[alt_names]
DNS.1 = multus-webhook-service
DNS.2 = multus-webhook-service.default
DNS.3 = multus-webhook-service.default.svc
EOF
openssl req -new -key ${tmp}/webhook-key.pem -subj "/CN=multus-webhook-service.default.svc" -out ${tmp}/server.csr -config ${tmp}/webhook.conf
# push CSR to Kubernetes API server
echo "Sending CSR to Kubernetes..."
csr_name="multus-webhook-service.default"
kubectl delete csr ${csr_name} >/dev/null 2>&1
cat <<EOF | kubectl create -f -
apiVersion: certificates.k8s.io/v1beta1
kind: CertificateSigningRequest
metadata:
name: ${csr_name}
spec:
request: $(cat ${tmp}/server.csr | base64 -w0)
groups:
- system:authenticated
usages:
- digital signature
- key encipherment
- server auth
EOF
# approve certificate
echo "Approving CSR..."
kubectl certificate approve ${csr_name}
# wait for the cert to be issued
echo -n "Waiting for the certificate to be issued..."
cert=""
for sec in $(seq 15); do
cert=$(kubectl get csr ${csr_name} -o jsonpath='{.status.certificate}')
if [[ $cert != "" ]]; then
echo -e "\nCertificate issued succesfully."
echo $cert | base64 --decode > ${tmp}/webhook-cert.pem
break
fi
echo -n "."; sleep 1
done
if [[ $cert == "" ]]; then
echo -e "\nError: certificate not issued. Verify that the API for signing certificates is enabled."
exit
fi
# create secret
echo "Creating secret..."
kubectl delete secret "multus-webhook-secret"
kubectl create secret generic --from-file=key.pem=${tmp}/webhook-key.pem --from-file=cert.pem=${tmp}/webhook-cert.pem "multus-webhook-secret"
# set cert in webhook configuration
echo "Patching configuration file with certificate..."
if [[ -f configuration-template.yaml ]]; then
sed "s/__CERT__/${cert}/" configuration-template.yaml > configuration.yaml
echo "File configuration.yaml patched."
else
echo -e "Error: validating configuration template file 'configuration-template.yaml' is missing. Please update it with cert.pem value from the secret manually."
fi

View File

@ -0,0 +1,38 @@
# Copyright (c) 2018 Intel Corporation
#
# 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.
apiVersion: admissionregistration.k8s.io/v1beta1
kind: ValidatingWebhookConfiguration
metadata:
labels:
app: multus-webhook
name: multus-webhook-config
webhooks:
- clientConfig:
caBundle: __CERT__
service:
name: multus-webhook-service
namespace: default
path: /validate
failurePolicy: Fail
name: multus-webhook.k8s.cni.cncf.io
rules:
- apiGroups:
- k8s.cni.cncf.io
apiVersions:
- v1
resources:
- network-attachment-definitions
operations:
- CREATE

View File

@ -0,0 +1,50 @@
# Copyright (c) 2018 Intel Corporation
#
# 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.
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: multus-webhook
name: multus-webhook-deployment
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: multus-webhook
template:
metadata:
labels:
app: multus-webhook
spec:
containers:
- name: multus-webhook
image: multus-webhook
command:
- /webhook/webhook
args:
- --bind-address=0.0.0.0
- --port=443
- --tls-private-key-file=/webhook/tls/key.pem
- --tls-cert-file=/webhook/tls/cert.pem
volumeMounts:
- mountPath: /webhook/tls
name: multus-webhook-secret
readOnly: True
imagePullPolicy: IfNotPresent
volumes:
- name: multus-webhook-secret
secret:
secretName: multus-webhook-secret

View File

@ -0,0 +1,27 @@
# Copyright (c) 2018 Intel Corporation
#
# 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.
apiVersion: v1
kind: Service
metadata:
name: multus-webhook-service
labels:
app: multus-webhook
namespace: default
spec:
ports:
- port: 443
targetPort: 443
selector:
app: multus-webhook

251
doc/configuration.md Normal file
View File

@ -0,0 +1,251 @@
## Multus-cni Configuration Reference
Following is the example of multus config file, in `/etc/cni/net.d/`.
(`"Note1"` and `"Note2"` are just comments, so you can remove them at your configuration)
```
{
"name": "node-cni-network",
"type": "multus",
"kubeconfig": "/etc/kubernetes/node-kubeconfig.yaml",
"confDir": "/etc/cni/multus/net.d",
"cniDir": "/var/lib/cni/multus",
"binDir": "/opt/cni/bin",
"logFile": "/var/log/multus.log",
"logLevel": "debug",
"capabilities": {
"portMappings": true
},
"readinessindicatorfile": "",
"namespaceIsolation": false,
"Note1":"NOTE: you can set clusterNetwork+defaultNetworks OR delegates!!",
"clusterNetwork": "defaultCRD",
"defaultNetworks": ["sidecarCRD", "flannel"],
"systemNamespaces": ["kube-system", "admin"],
"multusNamespace": "kube-system",
"Note2":"NOTE: If you use clusterNetwork/defaultNetworks, delegates is ignored",
"delegates": [{
"type": "weave-net",
"hairpinMode": true
}, {
"type": "macvlan",
... (snip)
}]
}
```
* `name` (string, required): the name of the network
* `type` (string, required): &quot;multus&quot;
* `confDir` (string, optional): directory for CNI config file that multus reads. default `/etc/cni/multus/net.d`
* `cniDir` (string, optional): Multus CNI data directory, default `/var/lib/cni/multus`
* `binDir` (string, optional): additional directory for CNI plugins which multus calls, in addition to the default (the default is typically set to `/opt/cni/bin`)
* `kubeconfig` (string, optional): kubeconfig file for the out of cluster communication with kube-apiserver. See the example [kubeconfig](https://github.com/intel/multus-cni/blob/master/doc/node-kubeconfig.yaml). If you would like to use CRD (i.e. network attachment definition), this is required
* `logFile` (string, optional): file path for log file. multus puts log in given file
* `logLevel` (string, optional): logging level ("debug", "error" or "panic")
* `namespaceIsolation` (boolean, optional): Enables a security feature where pods are only allowed to access `NetworkAttachmentDefinitions` in the namespace where the pod resides. Defaults to false.
* `capabilities` ({}list, optional): [capabilities](https://github.com/containernetworking/cni/blob/master/CONVENTIONS.md#dynamic-plugin-specific-fields-capabilities--runtime-configuration) supported by at least one of the delegates. (NOTE: Multus only supports portMappings capability for now). See the [example](https://github.com/intel/multus-cni/blob/master/examples/multus-ptp-portmap.conf).
* `readinessindicatorfile`: The path to a file whose existance denotes that the default network is ready
User should chose following parameters combination (`clusterNetwork`+`defaultNetworks` or `delegates`):
* `clusterNetwork` (string, required): default CNI network for pods, used in kubernetes cluster (Pod IP and so on): name of network-attachment-definition, CNI json file name (without extention, .conf/.conflist) or directory for CNI config file
* `defaultNetworks` ([]string, required): default CNI network attachment: name of network-attachment-definition, CNI json file name (without extention, .conf/.conflist) or directory for CNI config file
* `systemNamespaces` ([]string, optional): list of namespaces for Kubernetes system (namespaces listed here will not have `defaultNetworks` added)
* `multusNamespace` (string, optional): namespace for `clusterNetwork`/`defaultNetworks`
* `delegates` ([]map,required): number of delegate details in the Multus
### Network selection flow of clusterNetwork/defaultNetworks
Multus will find network for clusterNetwork/defaultNetworks as following sequences:
1. CRD object for given network name, in 'kube-system' namespace
1. CNI json config file in `confDir`. Given name should be without extention, like .conf/.conflist. (e.g. "test" for "test.conf")
1. Directory for CNI json config file. Multus will find alphabetically first file for the network
1. Multus failed to find network. Multus raise error message
## Miscellaneous config
### Default Network Readiness Indicator
You may wish for your "default network" (that is, the CNI plugin & its configuration you specify as your default delegate) to become ready before you attach networks with Multus. This is disabled by default and not used unless you add the readiness check option(s) to your CNI configuration file.
For example, if you use Flannel as a default network, the recommended method for Flannel to be installed is via a daemonset that also drops a configuration file in `/etc/cni/net.d/`. This may apply to other plugins that place that configuration file upon their readiness, hence, Multus uses their configuration filename as a semaphore and optionally waits to attach networks to pods until that file exists.
In this manner, you may prevent pods from crash looping, and instead wait for that default network to be ready.
Only one option is necessary to configure this functionality:
* `readinessindicatorfile`: The path to a file whose existance denotes that the default network is ready.
*NOTE*: If `readinessindicatorfile` is unset, or is an empty string, this functionality will be disabled, and is disabled by default.
### Logging
You may wish to enable some enhanced logging for Multus, especially during the process where you're configuring Multus and need to understand what is or isn't working with your particular configuration.
Multus will always log via `STDERR`, which is the standard method by which CNI plugins communicate errors, and these errors are logged by the Kubelet. This method is always enabled.
#### Writing to a Log File
Optionally, you may have Multus log to a file on the filesystem. This file will be written locally on each node where Multus is executed. You may configure this via the `LogFile` option in the CNI configuration. By default this additional logging to a flat file is disabled.
For example in your CNI configuration, you may set:
```
"LogFile": "/var/log/multus.log",
```
#### Logging Level
The default logging level is set as `panic` -- this will log only the most critical errors, and is the least verbose logging level.
The available logging level values, in decreasing order of verbosity are:
* `debug`
* `error`
* `panic`
You may configure the logging level by using the `LogLevel` option in your CNI configuration. For example:
```
"LogLevel": "debug",
```
### Namespace Isolation
The functionality provided by the `namespaceIsolation` configuration option enables a mode where Multus only allows pods to access custom resources (the `NetworkAttachmentDefinitions`) within the namespace where that pod resides. In other words, the `NetworkAttachmentDefinitions` are isolated to usage within the namespace in which they're created.
For example, if a pod is created in the namespace called `development`, Multus will not allow networks to be attached when defined by custom resources created in a different namespace, say in the `default` network.
Consider the situation where you have a system that has users of different privilege levels -- as an example, a platform which has two administrators: a Senior Administrator and a Junior Administrator. The Senior Administrator may have access to all namespaces, and some network configurations as used by Multus are considered to be privileged in that they allow access to some protected resources available on the network. However, the Junior Administrator has access to only a subset of namespaces, and therefore it should be assumed that the Junior Administrator cannot create pods in their limited subset of namespaces. The `namespaceIsolation` feature provides for this isolation, allowing pods created in given namespaces to only access custom resources in the same namespace as the pod.
Namespace Isolation is disabled by default.
#### Configuration example
```
"namespaceIsolation": true,
```
#### Usage example
Let's setup an example where we:
* Create a custom resource in a namespace called `privileged`
* Create a pod in a namespace called `development`, and have annotations that reference a custom resource in the `privileged` namespace. The creation of this pod should be disallowed by Multus (as we'll have the use of the custom resources limited only to those custom resources created within the same namespace as the pod).
Given the above scenario with a Junior & Senior Administrator. You may assume that the Senior Administrator has access to all namespaces, whereas the Junior Administrator has access only to the `development` namespace.
Firstly, we show that we have a number of namespaces available:
```
# List the available namespaces
[user@kube-master ~]$ kubectl get namespaces
NAME STATUS AGE
default Active 7h27m
development Active 3h
kube-public Active 7h27m
kube-system Active 7h27m
privileged Active 4s
```
We'll create a `NetworkAttachmentDefinition` in the `privileged` namespace.
```
# Show the network attachment definition we're creating.
[user@kube-master ~]$ cat cr.yml
apiVersion: "k8s.cni.cncf.io/v1"
kind: NetworkAttachmentDefinition
metadata:
name: macvlan-conf
spec:
config: '{
"cniVersion": "0.3.0",
"type": "macvlan",
"master": "eth0",
"mode": "bridge",
"ipam": {
"type": "host-local",
"subnet": "192.168.1.0/24",
"rangeStart": "192.168.1.200",
"rangeEnd": "192.168.1.216",
"routes": [
{ "dst": "0.0.0.0/0" }
],
"gateway": "192.168.1.1"
}
}'
# Create that network attachment definition in the privileged namespace
[user@kube-master ~]$ kubectl create -f cr.yml -n privileged
networkattachmentdefinition.k8s.cni.cncf.io/macvlan-conf created
# List the available network attachment definitions in the privileged namespace.
[user@kube-master ~]$ kubectl get networkattachmentdefinition.k8s.cni.cncf.io -n privileged
NAME AGE
macvlan-conf 11s
```
Next, we'll create a pod with an annotation that references the privileged namespace. Pay particular attention to the annotation that reads `k8s.v1.cni.cncf.io/networks: privileged/macvlan-conf` -- where it contains a reference to a `namespace/configuration-name` formatted network attachment name. In this case referring to the `macvlan-conf` in the namespace called `privileged`.
```
# Show the yaml for a pod.
[user@kube-master ~]$ cat example.pod.yml
apiVersion: v1
kind: Pod
metadata:
name: samplepod
annotations:
k8s.v1.cni.cncf.io/networks: privileged/macvlan-conf
spec:
containers:
- name: samplepod
command: ["/bin/bash", "-c", "sleep 2000000000000"]
image: dougbtv/centos-network
# Create that pod.
[user@kube-master ~]$ kubectl create -f example.pod.yml -n development
pod/samplepod created
```
You'll note that pod fails to spawn successfully. If you check the Multus logs, you'll see an entry such as:
```
2018-12-18T21:41:32Z [error] GetPodNetwork: namespace isolation violation: podnamespace: development / target namespace: privileged
```
This error expresses that the pod resides in the namespace named `development` but refers to a `NetworkAttachmentDefinition` outside of that namespace, in this case, the namespace named `privileged`.
In a positive example, you'd instead create the `NetworkAttachmentDefinition` in the `development` namespace, and you'd have an annotation that either A. does not reference a namespace, or B. refers to the same annotation.
A positive example may be:
```
# Create the same NetworkAttachmentDefinition as above, however in the development namespace
[user@kube-master ~]$ kubectl create -f cr.yml -n development
networkattachmentdefinition.k8s.cni.cncf.io/macvlan-conf created
# Show the yaml for a sample pod which references macvlan-conf without a namspace/ format
[user@kube-master ~]$ cat positive.example.pod
apiVersion: v1
kind: Pod
metadata:
name: samplepod
annotations:
k8s.v1.cni.cncf.io/networks: macvlan-conf
spec:
containers:
- name: samplepod
command: ["/bin/bash", "-c", "sleep 2000000000000"]
image: dougbtv/centos-network
# Create that pod.
[user@kube-master ~]$ kubectl create -f positive.example.pod -n development
pod/samplepod created
# We can see that this pod has been launched successfully.
[user@kube-master ~]$ kubectl get pods -n development
NAME READY STATUS RESTARTS AGE
samplepod 1/1 Running 0 31s
```

30
doc/development.md Normal file
View File

@ -0,0 +1,30 @@
## Development Information
## How to build the multus-cni?
```
git clone https://github.com/intel/multus-cni.git
cd multus-cni
./build
```
## How to run CI tests?
Multus has go unit tests (based on ginkgo framework). Following commands drive CI tests manually in your environment:
```
sudo ./test.sh
```
## Logging Best Practices
Followings are multus logging best practices:
* Add `logging.Debugf()` at the begining of function
* In case of error handling, use `logging.Errorf()` with given error info
* `logging.Panicf()` only be used at very critical error (it should NOT used usually)
## CI Introduction
TBD

551
doc/how-to-use.md Normal file
View File

@ -0,0 +1,551 @@
## Multus CNI usage guide
### Prerequisites
* Kubelet configured to use CNI
* Kubernetes version with CRD support (generally )
Your Kubelet(s) must be configured to run with the CNI network plugin. Please see [Kubernetes document for CNI](https://kubernetes.io/docs/concepts/extend-kubernetes/compute-storage-net/network-plugins/#cni) for more details.
### Install Multus
Generally we recommend two options: Manually place a Multus binary in your `/opt/cni/bin`, or use our [quick-start method](quickstart.md) -- which creates a daemonset that has an opinionated way of how to install & configure Multus CNI (recommended).
*Copy Multus Binary into place*
You may acquire the Multus binary via compilation (see the [developer guide](development.md)) or download the a binary from the [GitHub releases](https://github.com/intel/multus-cni/releases) page. Copy multus binary into CNI binary directory, usually `/opt/cni/bin`. Perform this on all nodes in your cluster (master and nodes).
$ cp multus /opt/cni/bin
*Via Daemonset method*
As a [quickstart](quickstart.md), you may apply these YAML files (included in the clone of this repository). Run this command (typically you would run this on the master, or wherever you have access to the `kubectl` command to manage yoru cluster).
$ cat ./images/{multus-daemonset.yml,flannel-daemonset.yml} | kubectl apply -f -
If you need more comprehensive detail, continue along with this guide, otherwise, you may wish to either [follow the quickstart guide]() or skip to the ['Create network attachment definition'](https://github.com/s1061123/multus-cni/blob/dev/update-readme/doc/how-to-use.md#create-network-attachment-definition) section.
### Set up conf file in /etc/cni/net.d/ (Installed automatically by Daemonset)
**If you use daemonset to install multus, skip this section and go to "Create network attachment"**
You put CNI config file in `/etc/cni/net.d`. Kubernetes CNI runtime uses the alphabetically first file in the directory. (`"NOTE1"`, `"NOTE2"` are just comments, you can remove them at your configuration)
Execute following commands at all Kubernetes nodes (i.e. master and minions)
```
$ mkdir -p /etc/cni/net.d
$ cat >/etc/cni/net.d/30-multus.conf <<EOF
{
"name": "multus-cni-network",
"type": "multus",
"readinessindicatorfile": "/var/run/flannel/subnet.env",
"delegates": [
{
"NOTE1": "This is example, wrote your CNI config in delegates",
"NOTE2": "If you use flannel, you also need to run flannel daemonset before!",
"type": "flannel",
"name": "flannel.1",
"delegate": {
"isDefaultGateway": true
}
}
],
"kubeconfig": "/etc/cni/net.d/multus.d/multus.kubeconfig"
}
EOF
```
For the detail, please take a look into [Configuration Reference](configuration.md)
**NOTE: You can use "clusterNetwork"/"defaultNetworks" instead of "delegates", see []() for the detail**
As above config, you need to set `"kubeconfig"` in the config file for NetworkAttachmentDefinition(CRD).
##### Which network will be used for "Pod IP"?
In case of "delegates", the first delegates network will be used for "Pod IP". Otherwise, "clusterNetwork" will be used for "Pod IP".
#### Create ServiceAccount, ClusterRole and its binding
Create resources for multus to access CRD objects as following command:
```
# Execute following commands at Kubernetes master
$ cat <<EOF | kubectl create -f -
apiVersion: v1
kind: ServiceAccount
metadata:
name: multus
namespace: kube-system
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: multus
rules:
- apiGroups: ["k8s.cni.cncf.io"]
resources:
- '*'
verbs:
- '*'
- apiGroups:
- ""
resources:
- pods
- pods/status
verbs:
- get
- update
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: multus
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: multus
subjects:
- kind: ServiceAccount
name: multus
namespace: kube-system
EOF
```
#### Set up kubeconfig file
Create kubeconfig at master node as following commands:
```
# Execute following command at Kubernetes master
$ mkdir -p /etc/cni/multus.d
$ SERVICEACCOUNT_CA=$(kubectl get secrets -n=kube-system -o json | jq -r '.items[]|select(.metadata.annotations."kubernetes.io/service-account.name"=="multus")| .data."ca.crt"')
$ SERVICEACCOUNT_TOKEN=$(kubectl get secrets -n=kube-system -o json | jq -r '.items[]|select(.metadata.annotations."kubernetes.io/service-account.name"=="multus")| .data.token' | base64 -d )
$ KUBERNETES_SERVICE_PROTO=$(kubectl get all -o json | jq -r .items[0].spec.ports[0].name)
$ KUBERNETES_SERVICE_HOST=$(kubectl get all -o json | jq -r .items[0].spec.clusterIP)
$ KUBERNETES_SERVICE_PORT=$(kubectl get all -o json | jq -r .items[0].spec.ports[0].port)
$ cat > /etc/cni/net.d/multus.d/multus.kubeconfig <<EOF
# Kubeconfig file for Multus CNI plugin.
apiVersion: v1
kind: Config
clusters:
- name: local
cluster:
server: ${KUBERNETES_SERVICE_PROTOCOL:-https}://${KUBERNETES_SERVICE_HOST}:${KUBERNETES_SERVICE_PORT}
certificate-authority-data: ${SERVICEACCOUNT_CA}
users:
- name: multus
user:
token: "${SERVICEACCOUNT_TOKEN}"
contexts:
- name: multus-context
context:
cluster: local
user: multus
current-context: multus-context
EOF
```
Copy `/etc/cni/net.d/multus.d/multus.kubeconfig` into other Kubernetes nodes
**NOTE: Recommend to exec 'chmod 600 /etc/cni/net.d/multus.d/multus.kubeconfig' to keep secure**
```
$ scp /etc/cni/net.d/multus.d/multus.kubeconfig ...
```
### Setup CRDs (daemonset automatically does)
**If you use daemonset to install multus, skip this section and go to "Create network attachment"**
Create CRD definition in Kubernetes as following command at master node:
```
# Execute following command at Kubernetes master
$ cat <<EOF | kubectl create -f -
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: network-attachment-definitions.k8s.cni.cncf.io
spec:
group: k8s.cni.cncf.io
version: v1
scope: Namespaced
names:
plural: network-attachment-definitions
singular: network-attachment-definition
kind: NetworkAttachmentDefinition
shortNames:
- net-attach-def
validation:
openAPIV3Schema:
properties:
spec:
properties:
config:
type: string
EOF
```
### Create network attachment definition
The 'NetworkAttachmentDefinition' is used to setup the network attachment, i.e. secondary interface for the pod, There are two ways to configure the 'NetworkAttachmentDefinition' as following:
- NetworkAttachmentDefinition with json CNI config
- NetworkAttachmentDefinition with CNI config file
#### NetworkAttachmentDefinition with json CNI config:
Following command creates NetworkAttachmentDefinition. CNI config is in `config:` field.
```
# Execute following command at Kubernetes master
$ cat <<EOF | kubectl create -f -
apiVersion: "k8s.cni.cncf.io/v1"
kind: NetworkAttachmentDefinition
metadata:
name: macvlan-conf-1
spec:
config: '{
"cniVersion": "0.3.0",
"type": "macvlan",
"master": "eth1",
"mode": "bridge",
"ipam": {
"type": "host-local",
"ranges": [
[ {
"subnet": "10.10.0.0/16",
"rangeStart": "10.10.1.20",
"rangeEnd": "10.10.3.50",
"gateway": "10.10.0.254"
} ]
]
}
}'
EOF
```
#### NetworkAttachmentDefinition with CNI config file:
If NetworkAttachmentDefinition has no spec, multus find a file in defaultConfDir ('/etc/cni/multus/net.d', with same name in the 'name' field of CNI config.
```
# Execute following command at Kubernetes master
$ cat <<EOF | kubectl create -f -
apiVersion: "k8s.cni.cncf.io/v1"
kind: NetworkAttachmentDefinition
metadata:
name: macvlan-conf-2
EOF
```
```
# Execute following commands at all Kubernetes nodes (i.e. master and minions)
$ cat <<EOF > /etc/cni/multus/net.d/macvlan2.conf
{
"cniVersion": "0.3.0",
"type": "macvlan",
"name": "macvlan-conf-2",
"master": "eth1",
"mode": "bridge",
"ipam": {
"type": "host-local",
"ranges": [
[ {
"subnet": "11.10.0.0/16",
"rangeStart": "11.10.1.20",
"rangeEnd": "11.10.3.50"
} ]
]
}
}
```
### Run pod with network annotation
#### Lauch pod with text annotation
```
# Execute following command at Kubernetes master
$ cat <<EOF | kubectl create -f -
apiVersion: v1
kind: Pod
metadata:
name: pod-case-01
annotations:
k8s.v1.cni.cncf.io/networks: macvlan-conf-1, macvlan-conf-2
spec:
containers:
- name: pod-case-01
image: docker.io/centos/tools:latest
command:
- /sbin/init
EOF
```
#### Lauch pod with text annotation for NetworkAttachmentDefinition in different namespace
You can also specify NetworkAttachmentDefinition with its namespace as adding `/<namespace>`
```
# Execute following command at Kubernetes master
$ cat <<EOF | kubectl create -f -
apiVersion: "k8s.cni.cncf.io/v1"
kind: NetworkAttachmentDefinition
metadata:
name: macvlan-conf-3
namespace: testns1
spec:
config: '{
"cniVersion": "0.3.0",
"type": "macvlan",
"master": "eth1",
"mode": "bridge",
"ipam": {
"type": "host-local",
"ranges": [
[ {
"subnet": "12.10.0.0/16",
"rangeStart": "12.10.1.20",
"rangeEnd": "12.10.3.50"
} ]
]
}
}'
EOF
$ cat <<EOF | kubectl create -f -
apiVersion: v1
kind: Pod
metadata:
name: pod-case-02
annotations:
k8s.v1.cni.cncf.io/networks: macvlan-conf-3/testns1
spec:
containers:
- name: pod-case-02
image: docker.io/centos/tools:latest
command:
- /sbin/init
EOF
```
#### Lauch pod with text annotation with interface name
You can also specify interface name as adding `@<ifname>`.
```
# Execute following command at Kubernetes master
$ cat <<EOF | kubectl create -f -
apiVersion: v1
kind: Pod
metadata:
name: pod-case-03
annotations:
k8s.v1.cni.cncf.io/networks: macvlan-conf-1@macvlan1
spec:
containers:
- name: pod-case-03
image: docker.io/centos/tools:latest
command:
- /sbin/init
EOF
```
#### Lauch pod with json annotation
```
# Execute following command at Kubernetes master
$ cat <<EOF | kubectl create -f -
apiVersion: v1
kind: Pod
metadata:
name: pod-case-04
annotations:
k8s.v1.cni.cncf.io/networks: '[
{ "name" : "macvlan-conf-1" },
{ "name" : "macvlan-conf-2" }
]'
spec:
containers:
- name: pod-case-04
image: docker.io/centos/tools:latest
command:
- /sbin/init
EOF
```
#### Lauch pod with json annotation for NetworkAttachmentDefinition in different namespace
You can also specify NetworkAttachmentDefinition with its namespace as adding `"namespace": "<namespace>"`.
```
# Execute following command at Kubernetes master
$ cat <<EOF | kubectl create -f -
apiVersion: v1
kind: Pod
metadata:
name: pod-case-05
annotations:
k8s.v1.cni.cncf.io/networks: '[
{ "name" : "macvlan-conf-1",
"namespace": "testns1" }
]'
spec:
containers:
- name: pod-case-05
image: docker.io/centos/tools:latest
command:
- /sbin/init
EOF
```
#### Lauch pod with json annotation with interface
You can also specify interface name as adding `"interfaceRequest": "<ifname>"`.
```
# Execute following command at Kubernetes master
$ cat <<EOF | kubectl create -f -
apiVersion: v1
kind: Pod
metadata:
name: pod-case-06
annotations:
k8s.v1.cni.cncf.io/networks: '[
{ "name" : "macvlan-conf-1",
"interfaceRequest": "macvlan1" },
{ "name" : "macvlan-conf-2" }
]'
spec:
containers:
- name: pod-case-06
image: docker.io/centos/tools:latest
command:
- /sbin/init
EOF
```
### Verifying pod network
Following the example of `ip -d address` output of above pod, "pod-case-06":
```
# Execute following command at Kubernetes master
$ kubectl exec -it pod-case-06 -- ip -d address
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 promiscuity 0 numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
3: eth0@if11: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP group default
link/ether 0a:58:0a:f4:02:06 brd ff:ff:ff:ff:ff:ff link-netnsid 0 promiscuity 0
veth numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535
inet 10.244.2.6/24 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::ac66:45ff:fe7c:3a19/64 scope link
valid_lft forever preferred_lft forever
4: macvlan1@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN group default
link/ether 4e:6d:7a:4e:14:87 brd ff:ff:ff:ff:ff:ff link-netnsid 0 promiscuity 0
macvlan mode bridge numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535
inet 10.10.1.22/16 scope global macvlan1
valid_lft forever preferred_lft forever
inet6 fe80::4c6d:7aff:fe4e:1487/64 scope link
valid_lft forever preferred_lft forever
5: net2@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN group default
link/ether 6e:e3:71:7f:86:f7 brd ff:ff:ff:ff:ff:ff link-netnsid 0 promiscuity 0
macvlan mode bridge numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535
inet 11.10.1.22/16 scope global net2
valid_lft forever preferred_lft forever
inet6 fe80::6ce3:71ff:fe7f:86f7/64 scope link
valid_lft forever preferred_lft forever
```
| Interface name | Description |
| --- | --- |
| lo | loopback |
| eth0 | Default network interface (flannel) |
| macvlan1 | macvlan interface (macvlan-conf-1) |
| net2 | macvlan interface (macvlan-conf-2) |
## Entrypoint Parameters
Multus CNI, when installed using the daemonset-style installation uses an entrypoint script which copies the Multus binary into place, places CNI configurations. This entrypoint takes a variety of parameters for customization.
Typically, you'd modified the daemonset YAML itself to specify these parameters.
For example, the `command` and `args` parameters in the `containers` section of the DaemonSet may look something like:
```
command: ["/entrypoint.sh"]
args:
- "--multus-conf-file=auto"
- "--namespace-isolation=true"
- "--multus-log-level=verbose"
```
Note that some of the defaults have directories inside the root directory named `/host/`, this is because it is deployed as a container and we have host file system locations mapped into this directory inside the container. If you use other directories, you may have to change the mounted volumes.
### Entrypoint script parameters
Each parameter is shown with the default as the value.
--cni-conf-dir=/host/etc/cni/net.d
This is the configuration directory where Multus will write its configuration file.
--cni-bin-dir=/host/opt/cni/bin
This the directory in which the Multus binary will be installed.
--namespace-isolation=false
Setting this option to true enables the Namespace isolation feature, which insists that custom resources must be created in the same namespace as the pods, otherwise it will refuse to attach those definitions as additional interfaces.
--multus-bin-file=/usr/src/multus-cni/bin/multus
This option lets you set which binary executable to copy from the container onto the host (into the directory specified by `--cni-bin-dir`), allowing one to copy an alternate version or build of Multus CNI.
--multus-conf-file=/usr/src/multus-cni/images/70-multus.conf
The `--multus-conf-file` is one of two options; it can be set to a source file to be copied into the location specified by `--cni-conf-dir`. Or, to a value of `auto`, that is: `--multus-conf-file=auto`.
The automatic configuration option is used to automatically generate Multus configurations given existing on-disk CNI configurations for your default network.
In the case that `--multus-conf-file=auto` -- The entrypoint script will look at the `--multus-autoconfig-dir` (by default, the same as the `--cni-conf-dir`). Multus will wait (600 seconds) until there's a CNI configuration file there, and it will take the alphabetically first configuration there, and it will wrap that configuration into a Multus configuration.
--multus-autoconfig-dir=/host/etc/cni/net.d
Used only with `--multus-conf-file=auto`. This option allows one to set which directory will be used to generate configuration files.
This can be used if you have your CNI configuration stored in an alternate location, or, you have constraints on race conditions where you'd like to generate your default network configuration first, and then only have Multus write its configuration when it finds that configuration -- allowing only Multus to write the CNI configuration in the `--cni-conf-dir`, therefore notifying the Kubelet that the node is in a ready state.
--multus-kubeconfig-file-host=/etc/cni/net.d/multus.d/multus.kubeconfig
Used only with `--multus-conf-file=auto`. Allows you to specify an alternate path to the Kubeconfig.
--multus-log-level=
--multus-log-file=
Used only with `--multus-conf-file=auto`. See the documentation for logging for which values are permitted.
Used only with `--multus-conf-file=auto`. Allows you to specify CNI spec version. Please set if you need to speicfy CNI spec version.
--cni-version=
In some cases, the original CNI configuration that the Multus configuration was generated from (using `--multus-conf-file=auto`) may be used as a sort of semaphor for network readiness -- as this model is used by the Kubelet itself. If you need to disable Multus' availablity, you may wish to clean out the generated configuration file when the source file for autogeneration of the config file is no longer present. You can use this functionality by setting:
--cleanup-config-on-exit=true
When using CRIO, you may need to restart CRIO to get the Multus configuration file to take -- this is rarely necessary.
--restart-crio=false
Additionally when using CRIO, you may wish to have the CNI config file that's used as the source for `--multus-conf-file=auto` renamed. This boolean option when set to true automatically renames the file with a `.old` suffix to the original filename.
--rename-conf-file=true
When using `--multus-conf-file=auto` you may also care to specify a `binDir` in the configuration, this can be accomplished using the `--additional-bin-dir` option.
--additional-bin-dir=/opt/multus/bin

File diff suppressed because one or more lines are too long

After

(image error) Size: 190 KiB

176
doc/quickstart.md Normal file
View File

@ -0,0 +1,176 @@
# Quickstart Guide
This guide is intended as a way to get you off the ground, and using Multus CNI to create Kubernetes pods with multiple interfaces. If you're already using Multus and need more detail, see the [comprehensive usage guide](how-to-use.md). This document a quickstart and a getting started guide in one, intended for your first run-through of Multus CNI.
We'll first install Multus CNI, and then we'll setup some configurations so that you can see how multiple interfaces are created for pods.
## Key Concepts
Two things we'll refer to a number of times through this document are:
* "Default network" -- This is your pod-to-pod network. This is how pods communicate among one another in your cluster, how they have connectivity. Generally speaking, this is presented as the interface named `eth0`. This interface is always attached to your pods, so that they can have connectivity among themselves. We'll add interfaces in addition to this.
* "CRDs" -- Custom Resource Definitions. Custom Resources are a way that the Kubernetes API is extended. We use these here to store some information that Multus can read. Primarily, we use these to store the configurations for each of the additional interfaces that are attached to your pods.
## Installation
Our recommended quickstart method to deploy Multus is to deploy using a Daemonset. This method is provided in this guide along with [Flannel](https://github.com/coreos/flannel). Flannel is deployed as a pod-to-pod network that is used as our "default network" -- this provides connectivity between pods in your cluster. Each additional network attachment (i.e. for multiple interfaces in pods) is made in addition to this default network. This guide generally assumes a new Kubernetes cluster that hasn't yet had any networking configured. If it's your first time using Multus, you might consider using a fresh cluster to learn with, and then later configure it to work with an existing cluster.
Firstly, clone this GitHub repository.
```
git clone https://github.com/intel/multus-cni.git && cd multus-cni
```
We'll apply files to `kubectl` from this repo. The files we're applying here specify a "Daemonset" (pods that run on each node in the cluster), this Daemonset handles installing the Multus CNI binary, dropping a default configuration on each node in the cluster -- and then also installs Flannel to use as a default network.
```
$ cat ./images/{multus-daemonset.yml,flannel-daemonset.yml} | kubectl apply -f -
```
### Validating your installation
Generally, the first step in validating your installation is to look at the `STATUS` field of your nodes, you can check it out by looking at:
```
$ kubectl get nodes
```
This will show each of the nodes in your cluster, take a look at the `STATUS` field, and look for `Ready` to appear for each of your nodes. This readiness is determined by the presence of a CNI configuration file on each of the nodes, and when that file appears.
You may also wish to start any pod in your cluster (without any further configuration), and validate that it works as you'd otherwise expect -- especially that it can communicate over the default network.
## Creating additional interfaces
The first thing we'll do is create configurations for each of the additional interfaces that we attach to pods. We'll do this by creating Custom Resources. Part of the quickstart installation creates a "CRD" -- a custom resource definition that is the home where we keep these custom resources -- we'll store our configurations for each interface in these.
### CNI Configurations
Each configuration we'll add is a CNI configuration. If you're not familiar with them, let's break them down quickly. Here's an example CNI configuration:
```
{
"cniVersion": "0.3.0",
"type": "loopback",
"additional": "information"
}
```
CNI configurations are JSON, and we have a structure here that has a few things we're interested in:
1. `cniVersion`: Tells each CNI plugin which version is being used and can give the plugin information if it's using a too late (or too early) version.
2. `type`: This tells CNI which binary to call on disk. Each CNI plugin is a binary that's called. Typically, these binaries are stored in `/opt/cni/bin` on each node, and CNI executes this binary. In this case we've specified the `loopback` binary (which create a loopback-type network interface). If this is your first time installing Multus, you might want to verify that the plugins that are in the "type" field are actually on disk in the `/opt/cni/bin` directory.
3. `additional`: This field is put here as an example, each CNI plugin can specify whatever configuration parameters they'd like in JSON. These are specific to the binary you're calling in the `type` field.
For an even further example -- take a look at the [bridge CNI plugin README](https://github.com/containernetworking/plugins/tree/master/plugins/main/bridge) which shows additional
If you'd like more information about CNI configuration, you can read [the entire CNI specification](https://github.com/containernetworking/cni/blob/master/SPEC.md). It might also be useful to look at the [CNI reference plugins](https://github.com/containernetworking/plugins) and see how they're configured.
You do not need to reload or refresh the Kubelets when CNI configurations change. These are read on each creation & deletion of pods. So if you change a configuration, it'll apply the next time a pod is created. Existing pods may need to be restarted if they need the new configuration.
### Storing a configuration as a Custom Resource
So, we want to create an additional interface. Let's create a macvlan interface for pods to use. We'll create a custom resource that defines the CNI configuration for interfaces.
Note in the following command that there's a `kind: NetworkAttachmentDefinition`. This is our fancy name for our configuration -- it's a custom extension of Kubernetes that defines how we attach networks to our pods.
Secondarily, note the `config` field. You'll see that this is a CNI configuration just like we explained earlier.
Lastly but *very* importantly, note under `metadata` the `name` field -- here's where we give this configuration a name, and it's how we tell pods to use this configuration. The name here is `macvlan-conf` -- as we're creating a configuration for macvlan.
Here's the command to create this example configuration:
```
cat <<EOF | kubectl create -f -
apiVersion: "k8s.cni.cncf.io/v1"
kind: NetworkAttachmentDefinition
metadata:
name: macvlan-conf
spec:
config: '{
"cniVersion": "0.3.0",
"type": "macvlan",
"master": "eth0",
"mode": "bridge",
"ipam": {
"type": "host-local",
"subnet": "192.168.1.0/24",
"rangeStart": "192.168.1.200",
"rangeEnd": "192.168.1.216",
"routes": [
{ "dst": "0.0.0.0/0" }
],
"gateway": "192.168.1.1"
}
}'
EOF
```
You can see which configurations you've created using `kubectl` here's how you can do that:
```
kubectl get network-attachment-definitions
```
You can get more detail by describing them:
```
kubectl describe network-attachment-definitions macvlan-conf
```
### Creating a pod that attaches an additional interface
We're going to create a pod. This will look familiar as any pod you might have created before, but, we'll have a special `annotations` field -- in this case we'll have an annotation called `k8s.v1.cni.cncf.io/networks`. This field takes a comma delimited list of the names of your `NetworkAttachmentDefinition`s as we created above. Note in the comand below that we have the annotation of `k8s.v1.cni.cncf.io/networks: macvlan-conf` where `macvlan-conf` is the name we used above when we created our configuration.
Let's go ahead and create a pod (that just sleeps for a really long time) with this command:
```
cat <<EOF | kubectl create -f -
apiVersion: v1
kind: Pod
metadata:
name: samplepod
annotations:
k8s.v1.cni.cncf.io/networks: macvlan-conf
spec:
containers:
- name: samplepod
command: ["/bin/bash", "-c", "sleep 2000000000000"]
image: dougbtv/centos-network
EOF
```
You may now inspect the pod and see what interfaces interfaces are attached, like so:
```
$ kubectl exec -it samplepod -- ip a
```
You should note that there's 3 interfaces:
* `lo` a loopback interface
* `eth0` our default network
* `net1` the new interface we created with the macvlan configuration.
### What if I want more interfaces?
You can add more interfaces to a pod by creating more custom resources and then referring to them in pod's annotation. You can also reuse configurations, so for example, to attach two macvlan interfaces to a pod, you could create a pod like so:
```
cat <<EOF | kubectl create -f -
apiVersion: v1
kind: Pod
metadata:
name: samplepod
annotations:
k8s.v1.cni.cncf.io/networks: macvlan-conf,macvlan-conf
spec:
containers:
- name: samplepod
command: ["/bin/bash", "-c", "sleep 2000000000000"]
image: dougbtv/centos-network
EOF
```
Note that the annotation now reads `k8s.v1.cni.cncf.io/networks: macvlan-conf,macvlan-conf`. Where we have the same configuration used twice, separated by a comma.
If you were to create another custom resource with the name `foo` you could use that such as: `k8s.v1.cni.cncf.io/networks: foo,macvlan-conf`, and use any number of attachments.

112
doc/webhook/webhook.md Normal file
View File

@ -0,0 +1,112 @@
# Validating admission webhook
## Building Docker image
From the root directory of Multus execute:
```
cd webhook
./build
```
## Deploying webhook application
Change working directory. From the root directory of Multus execute:
```
cd deployment/webhook
```
Create key and certificate pair and patch configuration-template.yaml file with base64-encoded certificate file. Run:
```
./certs.sh
```
*Note: Verify that Kubernetes controller manager has --cluster-signing-cert-file and --cluster-signing-key-file parameters set to paths to your CA keypair,
to make sure that Certificates API is enabled in order to generate certificate signed by cluster CA.
Script generates private key and certificate signing request, which is then pushed to the Kubernetes API server.
Then script approves that CSR and API server issues the certificate. Certificate is obtained from the API server and used to create a secret.
Script also patches `configuration-template.yaml` file with base64-encoded certificate and creates `configuration.yaml` file containing
Validating Webhook Configuration specification, which is deployed in one of the next steps.
More details about TLS certificates management in a cluster available [here](https://kubernetes.io/docs/tasks/tls/managing-tls-in-a-cluster/).*
Create service:
```
kubectl create -f service.yaml
```
Run deployment:
```
kubectl create -f deployment.yaml
```
Create Validating Webhook Configuration:
```
kubectl create -f configuration.yaml
```
## Verifying installation
Try to create invalid Network Attachment Definition resource:
```
cat <<EOF | kubectl create -f -
apiVersion: "k8s.cni.cncf.io/v1"
kind: NetworkAttachmentDefinition
metadata:
name: invalid-net-attach-def
spec:
config: '{
"invalid": "config"
}'
EOF
```
Webhook should deny the request:
```
Error from server: error when creating "STDIN": admission webhook "multus-webhook.k8s.cni.cncf.io" denied the request: Invalid network config spec
```
Now, try to create correctly defined one:
```
cat <<EOF | kubectl create -f -
apiVersion: "k8s.cni.cncf.io/v1"
kind: NetworkAttachmentDefinition
metadata:
name: correct-net-attach-def
spec:
config: '{
"cniVersion": "0.3.0",
"name": "a-bridge-network",
"type": "bridge",
"bridge": "br0",
"isGateway": true,
"ipam": {
"type": "host-local",
"subnet": "192.168.5.0/24",
"dataDir": "/mnt/cluster-ipam"
}
}'
EOF
```
Resource should be allowed and created:
```
networkattachmentdefinition.k8s.cni.cncf.io/correct-net-attach-def created
```
## Troubleshooting
Webhook server prints a lot of debug messages that could help to find the root cause of an issue.
To display logs run:
```
kubectl logs -l app=multus-webhook
```
Example output showing logs for handling requests generated in the "Verifying installation section":
```
# kubectl logs multus-webhook-pod
2018-08-22T13:33:09Z [debug] Starting Multus webhook server
2018-08-22T13:33:32Z [debug] Validating network config spec: { "invalid": "config" }
2018-08-22T13:33:32Z [debug] Spec is not a valid network config: error parsing configuration list: no name. Trying to parse into config list
2018-08-22T13:33:32Z [debug] Spec is not a valid network config list: error parsing configuration: missing 'type'
2018-08-22T13:33:32Z [error] Invalid config: error parsing configuration: missing 'type'
2018-08-22T13:33:32Z [debug] Sending response to the API server
2018-08-22T13:35:29Z [debug] Validating network config spec: { "cniVersion": "0.3.0", "name": "a-bridge-network", "type": "bridge", "bridge": "br0", "isGateway": true, "ipam": { "type": "host-local", "subnet": "192.168.5.0/24", "dataDir": "/mnt/cluster-ipam" } }
2018-08-22T13:35:29Z [debug] Spec is not a valid network config: error parsing configuration list: no 'plugins' key. Trying to parse into config list
2018-08-22T13:35:29Z [debug] Network Attachment Defintion is valid. Admission Review request allowed
2018-08-22T13:35:29Z [debug] Sending response to the API server
```

View File

@ -60,3 +60,35 @@ A sample `cni-configuration.conf` is provided, typically this file is placed in
## Other considerations
Primarily in this setup one thing that one should consider are the aspects of the `macvlan-conf.yml`, which is likely specific to the configuration of the node on which this resides.
## Passing down device information
Some CNI plugins require specific device information which maybe pre-allocated by K8s device plugin. This could be indicated by providing `k8s.v1.cni.cncf.io/resourceName` annotaton in its network attachment definition CRD. The file [`examples/sriov-net.yaml`](./sriov-net.yaml) shows an example on how to define a Network attachment definition with specific device allocation information. Multus will get allocated device information and make them available for CNI plugin to work on.
In this exmaple (shown below), it is expected that an [SRIOV Device Plugin](https://github.com/intel/sriov-network-device-plugin/) making a pool of SRIOV VFs available to the K8s with `intel.com/sriov` as their resourceName. Any device allocated from this resource pool will be passed down by Multus to the [sriov-cni](https://github.com/intel/sriov-cni/tree/dev/k8s-deviceid-model) plugin in `deviceID` field. This is up to the sriov-cni plugin to capture this information and work with this specific device information.
```yaml
apiVersion: "k8s.cni.cncf.io/v1"
kind: NetworkAttachmentDefinition
metadata:
name: sriov-net-a
annotations:
k8s.v1.cni.cncf.io/resourceName: intel.com/sriov
spec:
config: '{
"type": "sriov",
"vlan": 1000,
"ipam": {
"type": "host-local",
"subnet": "10.56.217.0/24",
"rangeStart": "10.56.217.171",
"rangeEnd": "10.56.217.181",
"routes": [{
"dst": "0.0.0.0/0"
}],
"gateway": "10.56.217.1"
}
}'
```
The [net-resource-sample-pod.yaml](./net-resource-sample-pod.yaml) is an exmaple Pod manifest file that requesting a SRIOV device from a host which is then configured using the above network attachement definition.
>For further information on how to configure SRIOV Device Plugin and SRIOV-CNI please refer to the links given above.

View File

@ -1,16 +1,19 @@
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: multus-crd-overpowered
rules:
- apiGroups:
- '*'
resources:
- '*'
verbs:
- '*'
- nonResourceURLs:
- '*'
verbs:
- '*'
- apiGroups: ["k8s.cni.cncf.io"]
resources:
- '*'
verbs:
- '*'
- apiGroups:
- ""
resources:
- pods
- pods/status
verbs:
- get
- update

View File

@ -0,0 +1,36 @@
{
"name": "multus-cni-network",
"type": "multus"
"capabilities": {
"portMappings": true
},
"delegates": [
{
"cniVersion": "0.3.1",
"name": "ptp-tuning-conflist",
"plugins": [
{
"dns": {
"nameservers": [
"172.16.1.1"
]
},
"ipMasq": true,
"ipam": {
"subnet": "172.16.0.0/24",
"type": "host-local"
},
"mtu": 512,
"type": "ptp"
},
{
"capabilities": {
"portMappings": true
},
"externalSetMarkChain": "KUBE-MARK-MASQ",
"type": "portmap"
}
]
}
],
}

View File

@ -101,8 +101,7 @@ spec:
nodeSelector:
beta.kubernetes.io/arch: amd64
tolerations:
- key: node-role.kubernetes.io/master
operator: Exists
- operator: Exists
effect: NoSchedule
serviceAccountName: flannel
initContainers:

View File

@ -0,0 +1,21 @@
apiVersion: v1
kind: Pod
metadata:
name: testpod1
labels:
env: test
annotations:
k8s.v1.cni.cncf.io/networks: sriov-net-a
spec:
containers:
- name: appcntr1
image: centos/tools
imagePullPolicy: IfNotPresent
command: [ "/bin/bash", "-c", "--" ]
args: [ "while true; do sleep 300000; done;" ]
resources:
requests:
intel.com/sriov: '1'
limits:
intel.com/sriov: '1'
restartPolicy: "Never"

View File

@ -8,7 +8,7 @@ metadata:
{ "name": "macvlan-conf-2" },
{ "name": "vlan-conf-1-1",
"namespace": "testns1",
"interfaceRequest": "vlan1-1" }
"interface": "vlan1-1" }
]'
spec:
containers:

21
examples/sriov-net.yaml Normal file
View File

@ -0,0 +1,21 @@
apiVersion: "k8s.cni.cncf.io/v1"
kind: NetworkAttachmentDefinition
metadata:
name: sriov-net-a
annotations:
k8s.v1.cni.cncf.io/resourceName: intel.com/sriov
spec:
config: '{
"type": "sriov",
"vlan": 1000,
"ipam": {
"type": "host-local",
"subnet": "10.56.217.0/24",
"rangeStart": "10.56.217.171",
"rangeEnd": "10.56.217.181",
"routes": [{
"dst": "0.0.0.0/0"
}],
"gateway": "10.56.217.1"
}
}'

68
glide.lock generated
View File

@ -1,21 +1,21 @@
hash: 0c4ea2a342364d2ff3b43242730cb3b1db3b7e8456f6cf43da3c51dbb67e18da
updated: 2018-07-27T03:29:02.093332104+01:00
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
- pkg/ip
- pkg/ipam
- pkg/skel
- pkg/types
- pkg/types/020
- pkg/types/current
- pkg/version
- name: github.com/containernetworking/plugins
version: 2b8b1ac0af4568e928d96ccc5f47b075416eeabd
version: 0950a3607bf5e8a57c6a655c7e573e6aab0dc650
subpackages:
- pkg/ip
- pkg/ipam
- pkg/ns
- pkg/testutils
- name: github.com/ghodss/yaml
@ -23,7 +23,9 @@ imports:
- name: github.com/gogo/protobuf
version: c0656edd0d9eab7c66d1eb0c568f9039345796f7
subpackages:
- gogoproto
- proto
- protoc-gen-gogo/descriptor
- sortkeys
- name: github.com/golang/glog
version: 44145f04b68cf362d9c4df2182967c2275eaefed
@ -53,6 +55,8 @@ imports:
version: 6633656539c1639d9d78127b7d47c622b5d7b6dc
- name: github.com/json-iterator/go
version: f2b4162afba35581b6d4a50d3b8f34e33c144682
- name: github.com/Microsoft/go-winio
version: 78439966b38d69bf38227fbf57ac8a6fee70f69a
- name: github.com/modern-go/concurrent
version: bacd9c7ef1dd9b15be4a9909b8ac7a4e313eec94
- name: github.com/modern-go/reflect2
@ -61,6 +65,7 @@ imports:
version: 7f8ab55aaf3b86885aa55b762e803744d1674700
subpackages:
- config
- extensions/table
- internal/codelocation
- internal/containernode
- internal/failer
@ -91,15 +96,15 @@ imports:
- name: github.com/peterbourgon/diskv
version: 5f041e8faa004a95c88a202771f4cc3e991971e6
- name: github.com/pkg/errors
version: 816c9085562cd7ee03e7f8188a1cfd942858cded
version: 645ef00459ed84a119197bfb8d8205042c6df63d
- name: github.com/spf13/pflag
version: 583c0c0531f06d5278b7d917446061adc344b5cd
- name: github.com/vishvananda/netlink
version: 6e453822d85ef5721799774b654d4d02fed62afb
version: b2de5d10e38ecce8607e6b438b6d174f389a004e
subpackages:
- nl
- name: github.com/vishvananda/netns
version: 54f0e4339ce73702a0607f49922aaa1e749b418d
version: be1fbeda19366dea804f00efff2dd73a1642fdcc
- name: golang.org/x/crypto
version: 49796115aa4b964c318aad4f3084fdb41e9aa067
subpackages:
@ -111,7 +116,9 @@ imports:
- http2
- http2/hpack
- idna
- internal/timeseries
- lex/httplex
- trace
- name: golang.org/x/sys
version: 95c6576299259db960f6c5b9b69ea52422860fce
subpackages:
@ -128,6 +135,40 @@ imports:
version: f51c12702a4d776e4c1fa9b0fabab841babae631
subpackages:
- rate
- name: google.golang.org/genproto
version: 09f6ed296fc66555a25fe4ce95173148778dfa85
subpackages:
- googleapis/rpc/status
- name: google.golang.org/grpc
version: 168a6198bcb0ef175f7dacec0b8691fc141dc9b8
subpackages:
- balancer
- balancer/base
- balancer/roundrobin
- codes
- connectivity
- credentials
- encoding
- encoding/proto
- grpclb/grpc_lb_v1/messages
- grpclog
- health
- health/grpc_health_v1
- internal
- internal/backoff
- internal/channelz
- internal/grpcrand
- keepalive
- metadata
- naming
- peer
- resolver
- resolver/dns
- resolver/passthrough
- stats
- status
- tap
- transport
- name: gopkg.in/inf.v0
version: 3887ee99ecf07df5b447e9b00d9c0b2adaa9f3e4
- name: gopkg.in/yaml.v2
@ -135,6 +176,7 @@ imports:
- name: k8s.io/api
version: 2d6f90ab1293a1fb871cf149423ebb72aa7423aa
subpackages:
- admission/v1beta1
- admissionregistration/v1alpha1
- admissionregistration/v1beta1
- apps/v1
@ -258,4 +300,12 @@ imports:
- util/homedir
- util/integer
- util/retry
- name: k8s.io/klog
version: 8139d8cb77af419532b33dfa7dd09fbc5f1d344f
- name: k8s.io/kubernetes
version: ddf47ac13c1a9483ea035a79cd7c10005ff21a6d
subpackages:
- pkg/kubelet/apis/podresources
- pkg/kubelet/apis/podresources/v1alpha1
- pkg/kubelet/util
testImports: []

View File

@ -1,36 +0,0 @@
package: github.com/intel/multus-cni
ignore:
- bytes
import:
- package: github.com/containernetworking/cni
version: 07c1a6da47b7fbf8b357f4949ecce2113e598491
subpackages:
- pkg/skel
- pkg/types
- pkg/version
- package: github.com/containernetworking/plugins
version: 2b8b1ac0af4568e928d96ccc5f47b075416eeabd
subpackages:
- pkg/ip
- pkg/ipam
- pkg/ns
- package: github.com/onsi/ginkgo
version: 7f8ab55aaf3b86885aa55b762e803744d1674700
- package: github.com/onsi/gomega
version: 2152b45fa28a361beba9aab0885972323a444e28
- package: github.com/golang/glog
- package: github.com/vishvananda/netlink
- package: k8s.io/apimachinery
version: kubernetes-1.11.1
- package: k8s.io/api
version: kubernetes-1.11.1
subpackages:
- core/v1
- package: k8s.io/client-go
version: kubernetes-1.11.1
subpackages:
- kubernetes
- tools/clientcmd
- util/retry
- package: github.com/vishvananda/netns
- package: github.com/pkg/errors

42
go.mod Normal file
View File

@ -0,0 +1,42 @@
module github.com/intel/multus-cni
go 1.12
require (
github.com/Microsoft/go-winio v0.4.14 // indirect
github.com/containernetworking/cni v0.7.1
github.com/containernetworking/plugins v0.8.2
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680 // indirect
github.com/gogo/protobuf v0.0.0-20170330071051-c0656edd0d9e // indirect
github.com/golang/protobuf v1.3.2 // indirect
github.com/google/btree v0.0.0-20160524151835-7d79101e329e // indirect
github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367 // indirect
github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d // indirect
github.com/gregjones/httpcache v0.0.0-20170728041850-787624de3eb7 // indirect
github.com/imdario/mergo v0.0.0-20141206190957-6633656539c1 // indirect
github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da // indirect
github.com/onsi/ginkgo v1.9.0
github.com/onsi/gomega v1.6.0
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
github.com/pkg/errors v0.8.1
github.com/spf13/pflag v1.0.1 // indirect
github.com/stretchr/objx v0.2.0 // indirect
github.com/stretchr/testify v1.4.0 // indirect
github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf
github.com/vishvananda/netns v0.0.0-20190625233234-7109fa855b0f // indirect
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586 // indirect
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7
golang.org/x/sys v0.0.0-20190825160603-fb81701db80f // indirect
golang.org/x/text v0.3.2 // indirect
golang.org/x/time v0.0.0-20161028155119-f51c12702a4d // indirect
google.golang.org/grpc v1.23.0
gopkg.in/inf.v0 v0.9.0 // indirect
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0 // indirect
k8s.io/api v0.0.0-20180712090710-2d6f90ab1293
k8s.io/apimachinery v0.0.0-20180621070125-103fd098999d
k8s.io/client-go v0.0.0-20180718001006-59698c7d9724
k8s.io/klog v0.0.0-20181108234604-8139d8cb77af // indirect
k8s.io/kubernetes v1.13.0
)

169
go.sum Normal file
View File

@ -0,0 +1,169 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+qxleU=
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg=
github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae/go.mod h1:CgnQgUtFrFz9mxFNtED3jI5tLDjKlOM+oUF/sTk6ps0=
github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/containernetworking/cni v0.7.0 h1:1Qy7EwdC08mx5wUB0DpjCuBrk6e/uXg9yI9TvAvgox8=
github.com/containernetworking/cni v0.7.0/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY=
github.com/containernetworking/cni v0.7.1 h1:fE3r16wpSEyaqY4Z4oFrLMmIGfBYIKpPrHK31EJ9FzE=
github.com/containernetworking/cni v0.7.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY=
github.com/containernetworking/plugins v0.8.2 h1:5lnwfsAYO+V7yXhysJKy3E1A2Gy9oVut031zfdOzI9w=
github.com/containernetworking/plugins v0.8.2/go.mod h1:TxALKWZpWL79BC3GOYKJzzXr7U8R23PdhwaLp6F3adc=
github.com/coreos/go-iptables v0.4.2/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU=
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1SMSibvLzxjeJLnrYEVLULFNiHY9YfQ=
github.com/d2g/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW34z5W5s=
github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5/go.mod h1:Eo87+Kg/IX2hfWJfwxMzLyuSZyxSoAug2nGa1G2QAi8=
github.com/d2g/hardwareaddr v0.0.0-20190221164911-e7d9fbe030e4/go.mod h1:bMl4RjIciD2oAxI7DmWRx6gbeqrkoLqv3MV0vzNad+I=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680 h1:ZktWZesgun21uEDrwW7iEV1zPCGQldM2atlJZ3TdvVM=
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
github.com/gogo/protobuf v0.0.0-20170330071051-c0656edd0d9e h1:ago6fNuQ6IhszPsXkeU7qRCyfsIX7L67WDybsAPkLl8=
github.com/gogo/protobuf v0.0.0-20170330071051-c0656edd0d9e/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/btree v0.0.0-20160524151835-7d79101e329e h1:JHB7F/4TJCrYBW8+GZO8VkWDj1jxcWuCl6uxKODiyi4=
github.com/google/btree v0.0.0-20160524151835-7d79101e329e/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367 h1:ScAXWS+TR6MZKex+7Z8rneuSJH+FSDqd6ocQyl+ZHo4=
github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d h1:7XGaL1e6bYS1yIonGp9761ExpPPV1ui0SAC59Yube9k=
github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
github.com/gregjones/httpcache v0.0.0-20170728041850-787624de3eb7 h1:6TSoaYExHper8PYsJu23GWVNOyYRCSnIFyxKgLSZ54w=
github.com/gregjones/httpcache v0.0.0-20170728041850-787624de3eb7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/imdario/mergo v0.0.0-20141206190957-6633656539c1 h1:FeeCi0I2Fu8kA8IXrdVPtGzym+mW9bzfj9f26EaES9k=
github.com/imdario/mergo v0.0.0-20141206190957-6633656539c1/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6tORTn+6F6j+Jc8TOr5osrynvN6ivFWZ2GA=
github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3 h1:/UewZcckqhvnnS0C6r3Sher2hSEbVmM6Ogpcjen08+Y=
github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/juju/errors v0.0.0-20180806074554-22422dad46e1/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q=
github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U=
github.com/juju/testing v0.0.0-20190613124551-e81189438503/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da h1:ZQGIPjr1iTtUPXZFk8WShqb5G+Qg65VHFLtSvmHh+Mw=
github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/onsi/ginkgo v0.0.0-20151202141238-7f8ab55aaf3b h1:Ey6yH0acn50T/v6CB75bGP4EMJqnv9WvnjN7oZaj+xE=
github.com/onsi/ginkgo v0.0.0-20151202141238-7f8ab55aaf3b/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.9.0 h1:SZjF721BByVj8QH636/8S2DnX4n0Re3SteMmw3N+tzc=
github.com/onsi/ginkgo v1.9.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a h1:KfNOeFvoAssuZLT7IntKZElKwi/5LRuxY71k+t6rfaM=
github.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
github.com/onsi/gomega v1.6.0 h1:8XTW0fcJZEq9q+Upcyws4JSGua2MFysCL5xkaSgHc+M=
github.com/onsi/gomega v1.6.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI=
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4=
github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/spf13/pflag v1.0.1 h1:aCvUg6QPl3ibpQUxyLkrEkCHtPqYJL4x9AuhqVqFis4=
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf h1:3J37+NPjNyGW/dbfXtj3yWuF9OEepIdGOXRaJGbORV8=
github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk=
github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI=
github.com/vishvananda/netns v0.0.0-20190625233234-7109fa855b0f h1:nBX3nTcmxEtHSERBJaIo1Qa26VwRaopnZmfDQUXsF4I=
github.com/vishvananda/netns v0.0.0-20190625233234-7109fa855b0f/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI=
golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586 h1:7KByu05hhLed2MO29w7p1XfZvZ13m8mub3shuVftRs0=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 h1:fHDIZ2oxGnUZRN6WgWFCbYBjH9uqVPRCUVUDhs0wnbA=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190825160603-fb81701db80f h1:LCxigP8q3fPRGNVYndYsyHnF0zRrvcoVwZMfb8iQZe4=
golang.org/x/sys v0.0.0-20190825160603-fb81701db80f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/time v0.0.0-20161028155119-f51c12702a4d h1:TnM+PKb3ylGmZvyPXmo9m/wktg7Jn/a/fNmr33HSj8g=
golang.org/x/time v0.0.0-20161028155119-f51c12702a4d/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/grpc v1.23.0 h1:AzbTB6ux+okLTzP8Ru1Xs41C303zdcfEht7MQnYJt5A=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo=
gopkg.in/inf.v0 v0.9.0 h1:3zYtXIO92bvsdS3ggAdA8Gb4Azj0YU+TVY1uGYNFA8o=
gopkg.in/inf.v0 v0.9.0/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0 h1:POO/ycCATvegFmVuPpQzZFJ+pGZeX22Ufu6fibxDVjU=
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
k8s.io/api v0.0.0-20180712090710-2d6f90ab1293 h1:hROmpFC7JMobXFXMmD7ZKZLhDKvr1IKfFJoYS/45G/8=
k8s.io/api v0.0.0-20180712090710-2d6f90ab1293/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA=
k8s.io/apimachinery v0.0.0-20180621070125-103fd098999d h1:MZjlsu9igBoVPZkXpIGoxI6EonqNsXXZU7hhvfQLkd4=
k8s.io/apimachinery v0.0.0-20180621070125-103fd098999d/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0=
k8s.io/client-go v0.0.0-20180718001006-59698c7d9724 h1:6gXlQ4rPEmQ86ugMoxdryE8Pu/+2tvcN7ulE74xAWcw=
k8s.io/client-go v0.0.0-20180718001006-59698c7d9724/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s=
k8s.io/klog v0.0.0-20181108234604-8139d8cb77af h1:s6rm8OxBbyDNSRkpyAd5OL4icUdBICVw9+mFADa+t5E=
k8s.io/klog v0.0.0-20181108234604-8139d8cb77af/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
k8s.io/kubernetes v1.13.0 h1:qTfB+u5M92k2fCCCVP2iuhgwwSOv1EkAkvQY1tQODD8=
k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk=

View File

@ -31,10 +31,12 @@ You can get get help with the `--help` flag.
```
$ ./entrypoint.sh --help
This is an entrypoint script for Multus CNI to overlay its
binary and configuration into locations in a filesystem.
The configuration & binary file will be copied to the
corresponding configuration directory.
This is an entrypoint script for Multus CNI to overlay its binary and
configuration into locations in a filesystem. The configuration & binary file
will be copied to the corresponding configuration directory. When
`--multus-conf-file=auto` is used, 00-multus.conf will be automatically
generated from the CNI configuration file of the master plugin (the first file
in lexicographical order in cni-conf-dir).
./entrypoint.sh
-h --help
@ -42,6 +44,7 @@ corresponding configuration directory.
--cni-bin-dir=/host/opt/cni/bin
--multus-conf-file=/usr/src/multus-cni/images/70-multus.conf
--multus-bin-file=/usr/src/multus-cni/bin/multus
--multus-kubeconfig-file-host=/etc/cni/net.d/multus.d/multus.kubeconfig
```
You must use an `=` to delimit the parameter name and the value. For example you may set a custom `cni-conf-dir` like so:
@ -59,7 +62,7 @@ Note: You'll noticed that there's a `/host/...` directory from the root for the
Example docker run command:
```
$ docker run -it -v /opt/cni/bin/:/host/opt/cni/bin/ -v /etc/cni/net.d/:/host/etc/cni/net.d/ --entrypoint=/bin/bash dougbtv/multus
$ docker run -it -v /opt/cni/bin/:/host/opt/cni/bin/ -v /etc/cni/net.d/:/host/etc/cni/net.d/ --entrypoint=/bin/bash dougbtv/multus
```
Originally inspired by and is a portmanteau of the [Flannel daemonset](https://github.com/coreos/flannel/blob/master/Documentation/kube-flannel.yml), the [Calico Daemonset](https://github.com/projectcalico/calico/blob/master/v2.0/getting-started/kubernetes/installation/hosted/k8s-backend-addon-manager/calico-daemonset.yaml), and the [Calico CNI install bash script](https://github.com/projectcalico/cni-plugin/blob/be4df4db2e47aa7378b1bdf6933724bac1f348d0/k8s-install/scripts/install-cni.sh#L104-L153).
Originally inspired by and is a portmanteau of the [Flannel daemonset](https://github.com/coreos/flannel/blob/master/Documentation/kube-flannel.yml), the [Calico Daemonset](https://github.com/projectcalico/calico/blob/master/v2.0/getting-started/kubernetes/installation/hosted/k8s-backend-addon-manager/calico-daemonset.yaml), and the [Calico CNI install bash script](https://github.com/projectcalico/cni-plugin/blob/be4df4db2e47aa7378b1bdf6933724bac1f348d0/k8s-install/scripts/install-cni.sh#L104-L153).

View File

@ -3,19 +3,46 @@
# Always exit on errors.
set -e
# Run a clean up when we exit if configured to do so.
trap cleanup TERM
function cleanup {
if [ "$MULTUS_CLEANUP_CONFIG_ON_EXIT" == "true" ]; then
CONF=$(cat <<-EOF
{Multus configuration intentionally invalidated to prevent pods from being scheduled.}
EOF
)
echo $CONF > $CNI_CONF_DIR/00-multus.conf
log "Multus configuration intentionally invalidated to prevent pods from being scheduled."
fi
}
# Set our known directories.
CNI_CONF_DIR="/host/etc/cni/net.d"
CNI_BIN_DIR="/host/opt/cni/bin"
ADDITIONAL_BIN_DIR=""
MULTUS_CONF_FILE="/usr/src/multus-cni/images/70-multus.conf"
MULTUS_AUTOCONF_DIR="/host/etc/cni/net.d"
MULTUS_BIN_FILE="/usr/src/multus-cni/bin/multus"
MULTUS_KUBECONFIG_FILE_HOST="/etc/cni/net.d/multus.d/multus.kubeconfig"
MULTUS_NAMESPACE_ISOLATION=false
MULTUS_LOG_LEVEL=""
MULTUS_LOG_FILE=""
OVERRIDE_NETWORK_NAME=false
MULTUS_CLEANUP_CONFIG_ON_EXIT=false
RESTART_CRIO=false
CRIO_RESTARTED_ONCE=false
RENAME_SOURCE_CONFIG_FILE=false
# Give help text for parameters.
function usage()
{
echo -e "This is an entrypoint script for Multus CNI to overlay its"
echo -e "binary and configuration into locations in a filesystem."
echo -e "The configuration & binary file will be copied to the "
echo -e "corresponding configuration directory."
echo -e "This is an entrypoint script for Multus CNI to overlay its binary and "
echo -e "configuration into locations in a filesystem. The configuration & binary file "
echo -e "will be copied to the corresponding configuration directory. When "
echo -e "'--multus-conf-file=auto' is used, 00-multus.conf will be automatically "
echo -e "generated from the CNI configuration file of the master plugin (the first file "
echo -e "in lexicographical order in cni-conf-dir)."
echo -e ""
echo -e "./entrypoint.sh"
echo -e "\t-h --help"
@ -23,6 +50,31 @@ function usage()
echo -e "\t--cni-bin-dir=$CNI_BIN_DIR"
echo -e "\t--multus-conf-file=$MULTUS_CONF_FILE"
echo -e "\t--multus-bin-file=$MULTUS_BIN_FILE"
echo -e "\t--multus-kubeconfig-file-host=$MULTUS_KUBECONFIG_FILE_HOST"
echo -e "\t--namespace-isolation=$MULTUS_NAMESPACE_ISOLATION"
echo -e "\t--multus-autoconfig-dir=$MULTUS_AUTOCONF_DIR (used only with --multus-conf-file=auto)"
echo -e "\t--multus-log-level=$MULTUS_LOG_LEVEL (empty by default, used only with --multus-conf-file=auto)"
echo -e "\t--multus-log-file=$MULTUS_LOG_FILE (empty by default, used only with --multus-conf-file=auto)"
echo -e "\t--override-network-name=false (used only with --multus-conf-file=auto)"
echo -e "\t--cleanup-config-on-exit=false (used only with --multus-conf-file=auto)"
echo -e "\t--rename-conf-file=false (used only with --multus-conf-file=auto)"
echo -e "\t--additional-bin-dir=$ADDITIONAL_BIN_DIR (adds binDir option to configuration, used only with --multus-conf-file=auto)"
echo -e "\t--restart-crio=false (restarts CRIO after config file is generated)"
}
function log()
{
echo "$(date --iso-8601=seconds) ${1}"
}
function error()
{
log "ERR: {$1}"
}
function warn()
{
log "WARN: {$1}"
}
# Parse parameters given as arguments to this script.
@ -34,6 +86,9 @@ while [ "$1" != "" ]; do
usage
exit
;;
--cni-version)
CNI_VERSION=$VALUE
;;
--cni-conf-dir)
CNI_CONF_DIR=$VALUE
;;
@ -46,10 +101,38 @@ while [ "$1" != "" ]; do
--multus-bin-file)
MULTUS_BIN_FILE=$VALUE
;;
--multus-kubeconfig-file-host)
MULTUS_KUBECONFIG_FILE_HOST=$VALUE
;;
--namespace-isolation)
MULTUS_NAMESPACE_ISOLATION=$VALUE
;;
--multus-log-level)
MULTUS_LOG_LEVEL=$VALUE
;;
--multus-log-file)
MULTUS_LOG_FILE=$VALUE
;;
--multus-autoconfig-dir)
MULTUS_AUTOCONF_DIR=$VALUE
;;
--override-network-name)
OVERRIDE_NETWORK_NAME=$VALUE
;;
--cleanup-config-on-exit)
MULTUS_CLEANUP_CONFIG_ON_EXIT=$VALUE
;;
--restart-crio)
RESTART_CRIO=$VALUE
;;
--rename-conf-file)
RENAME_SOURCE_CONFIG_FILE=$VALUE
;;
--additional-bin-dir)
ADDITIONAL_BIN_DIR=$VALUE
;;
*)
echo "ERROR: unknown parameter \"$PARAM\""
usage
exit 1
warn "unknown parameter \"$PARAM\""
;;
esac
shift
@ -57,20 +140,27 @@ done
# Create array of known locations
declare -a arr=($CNI_CONF_DIR $CNI_BIN_DIR $MULTUS_CONF_FILE $MULTUS_BIN_FILE)
declare -a arr=($CNI_CONF_DIR $CNI_BIN_DIR $MULTUS_BIN_FILE)
if [ "$MULTUS_CONF_FILE" != "auto" ]; then
arr+=($MULTUS_CONF_FILE)
fi
# Loop through and verify each location each.
for i in "${arr[@]}"
do
if [ ! -e "$i" ]; then
echo "Location $i does not exist"
warn "Location $i does not exist"
exit 1;
fi
done
# Copy files into proper places.
cp -f $MULTUS_CONF_FILE $CNI_CONF_DIR
cp -f $MULTUS_BIN_FILE $CNI_BIN_DIR
# Copy files into place and atomically move into final binary name
cp -f $MULTUS_BIN_FILE $CNI_BIN_DIR/_multus
mv -f $CNI_BIN_DIR/_multus $CNI_BIN_DIR/multus
if [ "$MULTUS_CONF_FILE" != "auto" ]; then
cp -f $MULTUS_CONF_FILE $CNI_CONF_DIR
fi
# Make a multus.d directory (for our kubeconfig)
@ -89,10 +179,10 @@ SKIP_TLS_VERIFY=${SKIP_TLS_VERIFY:-false}
if [ -f "$SERVICE_ACCOUNT_PATH/token" ]; then
# We're running as a k8d pod - expect some variables.
if [ -z ${KUBERNETES_SERVICE_HOST} ]; then
echo "KUBERNETES_SERVICE_HOST not set"; exit 1;
error "KUBERNETES_SERVICE_HOST not set"; exit 1;
fi
if [ -z ${KUBERNETES_SERVICE_PORT} ]; then
echo "KUBERNETES_SERVICE_PORT not set"; exit 1;
error "KUBERNETES_SERVICE_PORT not set"; exit 1;
fi
if [ "$SKIP_TLS_VERIFY" == "true" ]; then
@ -129,12 +219,147 @@ current-context: multus-context
EOF
else
echo "WARNING: Doesn't look like we're running in a kubernetes environment (no serviceaccount token)"
warn "Doesn't look like we're running in a kubernetes environment (no serviceaccount token)"
fi
# ---------------------- end Generate a "kube-config".
echo "Entering sleep... (success)"
# ------------------------------- Generate "00-multus.conf"
# Sleep forever.
sleep infinity
function generateMultusConf {
if [ "$MULTUS_CONF_FILE" == "auto" ]; then
log "Generating Multus configuration file using files in $MULTUS_AUTOCONF_DIR..."
found_master=false
tries=0
while [ $found_master == false ]; do
MASTER_PLUGIN="$(ls $MULTUS_AUTOCONF_DIR | grep -E '\.conf(list)?$' | grep -Ev '00-multus\.conf' | head -1)"
if [ "$MASTER_PLUGIN" == "" ]; then
if [ $tries -lt 600 ]; then
if ! (($tries % 5)); then
log "Attemping to find master plugin configuration, attempt $tries"
fi
let "tries+=1"
# See if the Multus configuration file exists, if it does then clean it up.
if [ "$MULTUS_CLEANUP_CONFIG_ON_EXIT" == true ] && [ -f "$CNI_CONF_DIR/00-multus.conf" ]; then
# But first, check if it has the invalidated configuration in it (otherwise we keep doing this over and over.)
if ! grep -q "invalidated" $CNI_CONF_DIR/00-multus.conf; then
cleanup
fi
fi
sleep 1;
else
error "Multus could not be configured: no master plugin was found."
exit 1;
fi
else
found_master=true
ISOLATION_STRING=""
if [ "$MULTUS_NAMESPACE_ISOLATION" == true ]; then
ISOLATION_STRING="\"namespaceIsolation\": true,"
fi
LOG_LEVEL_STRING=""
if [ ! -z "${MULTUS_LOG_LEVEL// }" ]; then
case "$MULTUS_LOG_LEVEL" in
debug)
;;
error)
;;
panic)
;;
verbose)
;;
*)
error "Log levels should be one of: debug/verbose/error/panic, did not understand $MULTUS_LOG_LEVEL"
usage
exit 1
esac
LOG_LEVEL_STRING="\"logLevel\": \"$MULTUS_LOG_LEVEL\","
fi
LOG_FILE_STRING=""
if [ ! -z "${MULTUS_LOG_FILE// }" ]; then
LOG_FILE_STRING="\"logFile\": \"$MULTUS_LOG_FILE\","
fi
CNI_VERSION_STRING=""
if [ ! -z "${CNI_VERSION// }" ]; then
CNI_VERSION_STRING="\"cniVersion\": \"$CNI_VERSION\","
fi
ADDITIONAL_BIN_DIR_STRING=""
if [ ! -z "${ADDITIONAL_BIN_DIR// }" ]; then
ADDITIONAL_BIN_DIR_STRING="\"binDir\": \"$ADDITIONAL_BIN_DIR\","
fi
if [ "$OVERRIDE_NETWORK_NAME" == "true" ]; then
MASTER_PLUGIN_NET_NAME="$(cat $MULTUS_AUTOCONF_DIR/$MASTER_PLUGIN | \
python -c 'import json,sys;print json.load(sys.stdin)["name"]')"
else
MASTER_PLUGIN_NET_NAME="multus-cni-network"
fi
MASTER_PLUGIN_LOCATION=$MULTUS_AUTOCONF_DIR/$MASTER_PLUGIN
MASTER_PLUGIN_JSON="$(cat $MASTER_PLUGIN_LOCATION)"
log "Using $MASTER_PLUGIN_LOCATION as a source to generate the Multus configuration"
CONF=$(cat <<-EOF
{
$CNI_VERSION_STRING
"name": "$MASTER_PLUGIN_NET_NAME",
"type": "multus",
$ISOLATION_STRING
$LOG_LEVEL_STRING
$LOG_FILE_STRING
$ADDITIONAL_BIN_DIR_STRING
"kubeconfig": "$MULTUS_KUBECONFIG_FILE_HOST",
"delegates": [
$MASTER_PLUGIN_JSON
]
}
EOF
)
echo $CONF > $CNI_CONF_DIR/00-multus.conf
log "Config file created @ $CNI_CONF_DIR/00-multus.conf"
echo $CONF
# If we're not performing the cleanup on exit, we can safely rename the config file.
if [ "$RENAME_SOURCE_CONFIG_FILE" == true ]; then
mv ${MULTUS_AUTOCONF_DIR}/${MASTER_PLUGIN} ${MULTUS_AUTOCONF_DIR}/${MASTER_PLUGIN}.old
log "Original master file moved to ${MULTUS_AUTOCONF_DIR}/${MASTER_PLUGIN}.old"
fi
if [ "$RESTART_CRIO" == true ]; then
# Restart CRIO only once.
if [ "$CRIO_RESTARTED_ONCE" == false ]; then
log "Restarting crio"
systemctl restart crio
CRIO_RESTARTED_ONCE=true
fi
fi
fi
done
fi
}
generateMultusConf
# ---------------------- end Generate "00-multus.conf".
# Enter either sleep loop, or watch loop...
if [ "$MULTUS_CLEANUP_CONFIG_ON_EXIT" == true ]; then
log "Entering watch loop..."
while true; do
# Check and see if the original master plugin configuration exists...
if [ ! -f "$MASTER_PLUGIN_LOCATION" ]; then
log "Master plugin @ $MASTER_PLUGIN_LOCATION has been deleted. Performing cleanup..."
cleanup
generateMultusConf
log "Continuing watch loop after configuration regeneration..."
fi
sleep 1
done
else
log "Entering sleep (success)..."
sleep infinity
fi

View File

@ -101,8 +101,7 @@ spec:
nodeSelector:
beta.kubernetes.io/arch: amd64
tolerations:
- key: node-role.kubernetes.io/master
operator: Exists
- operator: Exists
effect: NoSchedule
serviceAccountName: flannel
# ------------------------------- Intentionally removed, Multus daemonset configures /etc/cni/net.d
@ -181,8 +180,7 @@ spec:
nodeSelector:
beta.kubernetes.io/arch: arm64
tolerations:
- key: node-role.kubernetes.io/master
operator: Exists
- operator: Exists
effect: NoSchedule
serviceAccountName: flannel
initContainers:
@ -260,8 +258,7 @@ spec:
nodeSelector:
beta.kubernetes.io/arch: arm
tolerations:
- key: node-role.kubernetes.io/master
operator: Exists
- operator: Exists
effect: NoSchedule
serviceAccountName: flannel
initContainers:
@ -339,8 +336,7 @@ spec:
nodeSelector:
beta.kubernetes.io/arch: ppc64le
tolerations:
- key: node-role.kubernetes.io/master
operator: Exists
- operator: Exists
effect: NoSchedule
serviceAccountName: flannel
initContainers:
@ -418,8 +414,7 @@ spec:
nodeSelector:
beta.kubernetes.io/arch: s390x
tolerations:
- key: node-role.kubernetes.io/master
operator: Exists
- operator: Exists
effect: NoSchedule
serviceAccountName: flannel
initContainers:
@ -476,4 +471,4 @@ spec:
path: /etc/cni/net.d
- name: flannel-cfg
configMap:
name: kube-flannel-cfg
name: kube-flannel-cfg

View File

@ -0,0 +1,180 @@
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: network-attachment-definitions.k8s.cni.cncf.io
spec:
group: k8s.cni.cncf.io
version: v1
scope: Namespaced
names:
plural: network-attachment-definitions
singular: network-attachment-definition
kind: NetworkAttachmentDefinition
shortNames:
- net-attach-def
validation:
openAPIV3Schema:
properties:
spec:
properties:
config:
type: string
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: multus
rules:
- apiGroups: ["k8s.cni.cncf.io"]
resources:
- '*'
verbs:
- '*'
- apiGroups:
- ""
resources:
- pods
- pods/status
verbs:
- get
- update
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: multus
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: multus
subjects:
- kind: ServiceAccount
name: multus
namespace: kube-system
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: multus
namespace: kube-system
---
kind: ConfigMap
apiVersion: v1
metadata:
name: multus-cni-config
namespace: kube-system
labels:
tier: node
app: multus
data:
# NOTE: If you'd prefer to manually apply a configuration file, you may create one here.
# In the case you'd like to customize the Multus installation, you should change the arguments to the Multus pod
# change the "args" line below from
# - "--multus-conf-file=auto"
# to:
# "--multus-conf-file=/tmp/multus-conf/70-multus.conf"
# Additionally -- you should ensure that the name "70-multus.conf" is the alphabetically first name in the
# /etc/cni/net.d/ directory on each node, otherwise, it will not be used by the Kubelet.
cni-conf.json: |
{
"name": "multus-cni-network",
"type": "multus",
"capabilities": {
"portMappings": true
},
"delegates": [
{
"cniVersion": "0.3.1",
"name": "default-cni-network",
"plugins": [
{
"type": "flannel",
"name": "flannel.1",
"delegate": {
"isDefaultGateway": true,
"hairpinMode": true
}
},
{
"type": "portmap",
"capabilities": {
"portMappings": true
}
}
]
}
],
"kubeconfig": "/etc/cni/net.d/multus.d/multus.kubeconfig"
}
---
apiVersion: extensions/v1beta1
kind: DaemonSet
metadata:
name: kube-multus-ds-amd64
namespace: kube-system
labels:
tier: node
app: multus
spec:
updateStrategy:
type: RollingUpdate
template:
metadata:
labels:
tier: node
app: multus
spec:
hostNetwork: true
nodeSelector:
beta.kubernetes.io/arch: amd64
tolerations:
- operator: Exists
effect: NoSchedule
serviceAccountName: multus
containers:
- name: kube-multus
# crio support requires multus:latest for now. support 3.3 or later.
image: nfvpe/multus:latest
command: ["/entrypoint.sh"]
args:
- "--cni-bin-dir=/host/usr/libexec/cni"
- "--multus-conf-file=auto"
- "--override-network-name=true"
- "--rename-conf-file=true"
resources:
requests:
cpu: "100m"
memory: "50Mi"
limits:
cpu: "100m"
memory: "50Mi"
securityContext:
privileged: true
capabilities:
add: ["SYS_ADMIN"]
volumeMounts:
- name: run
mountPath: /run
- name: cni
mountPath: /host/etc/cni/net.d
- name: cnibin
mountPath: /host/usr/libexec/cni
- name: multus-cfg
mountPath: /tmp/multus-conf
volumes:
- name: run
hostPath:
path: /run
- name: cni
hostPath:
path: /etc/cni/net.d
- name: cnibin
hostPath:
path: /usr/libexec/cni
- name: multus-cfg
configMap:
name: multus-cni-config
items:
- key: cni-conf.json
path: 70-multus.conf

View File

@ -26,16 +26,19 @@ apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: multus
rules:
- apiGroups:
- '*'
resources:
- '*'
verbs:
- '*'
- nonResourceURLs:
- '*'
verbs:
- '*'
- apiGroups: ["k8s.cni.cncf.io"]
resources:
- '*'
verbs:
- '*'
- apiGroups:
- ""
resources:
- pods
- pods/status
verbs:
- get
- update
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
@ -111,8 +114,7 @@ spec:
nodeSelector:
beta.kubernetes.io/arch: amd64
tolerations:
- key: node-role.kubernetes.io/master
operator: Exists
- operator: Exists
effect: NoSchedule
serviceAccountName: multus
containers:

View File

@ -31,10 +31,17 @@ import (
"github.com/containernetworking/cni/libcni"
"github.com/containernetworking/cni/pkg/skel"
cnitypes "github.com/containernetworking/cni/pkg/types"
"github.com/intel/multus-cni/kubeletclient"
"github.com/intel/multus-cni/logging"
"github.com/intel/multus-cni/types"
)
const (
resourceNameAnnot = "k8s.v1.cni.cncf.io/resourceName"
defaultNetAnnot = "v1.multus-cni.io/default-network"
networkAttachmentAnnot = "k8s.v1.cni.cncf.io/networks"
)
// NoK8sNetworkError indicates error, no network in kubernetes
type NoK8sNetworkError struct {
message string
@ -74,30 +81,45 @@ func setKubeClientInfo(c *clientInfo, client KubeClient, k8sArgs *types.K8sArgs)
c.Podname = string(k8sArgs.K8S_POD_NAME)
}
func SetNetworkStatus(k *clientInfo, netStatus []*types.NetworkStatus) error {
func SetNetworkStatus(client KubeClient, k8sArgs *types.K8sArgs, netStatus []*types.NetworkStatus, conf *types.NetConf) error {
logging.Debugf("SetNetworkStatus: %v, %v, %v, %v", client, k8sArgs, netStatus, conf)
logging.Debugf("SetNetworkStatus: %v, %v", k, netStatus)
pod, err := k.Client.GetPod(k.Podnamespace, k.Podname)
client, err := GetK8sClient(conf.Kubeconfig, client)
if err != nil {
return logging.Errorf("SetNetworkStatus: failed to query the pod %v in out of cluster comm: %v", k.Podname, err)
return logging.Errorf("SetNetworkStatus: %v", err)
}
if client == nil {
if len(conf.Delegates) == 0 {
// No available kube client and no delegates, we can't do anything
return logging.Errorf("must have either Kubernetes config or delegates, refer to Multus documentation for usage instructions")
}
logging.Debugf("SetNetworkStatus: kube client info is not defined, skip network status setup")
return nil
}
var ns string
podName := string(k8sArgs.K8S_POD_NAME)
podNamespace := string(k8sArgs.K8S_POD_NAMESPACE)
pod, err := client.GetPod(podNamespace, podName)
if err != nil {
return logging.Errorf("SetNetworkStatus: failed to query the pod %v in out of cluster comm: %v", podName, err)
}
var networkStatuses string
if netStatus != nil {
var networkStatus []string
for _, nets := range netStatus {
data, err := json.MarshalIndent(nets, "", " ")
for _, status := range netStatus {
data, err := json.MarshalIndent(status, "", " ")
if err != nil {
return logging.Errorf("SetNetworkStatus: error with Marshal Indent: %v", err)
}
networkStatus = append(networkStatus, string(data))
}
ns = fmt.Sprintf("[%s]", strings.Join(networkStatus, ","))
networkStatuses = fmt.Sprintf("[%s]", strings.Join(networkStatus, ","))
}
_, err = setPodNetworkAnnotation(k.Client, k.Podnamespace, pod, ns)
_, err = setPodNetworkAnnotation(client, podNamespace, pod, networkStatuses)
if err != nil {
return logging.Errorf("SetNetworkStatus: failed to update the pod %v in out of cluster comm: %v", k.Podname, err)
return logging.Errorf("SetNetworkStatus: failed to update the pod %v in out of cluster comm: %v", podName, err)
}
return nil
@ -131,18 +153,6 @@ func setPodNetworkAnnotation(client KubeClient, namespace string, pod *v1.Pod, n
return pod, nil
}
func getPodNetworkAnnotation(client KubeClient, k8sArgs *types.K8sArgs) (string, string, error) {
var err error
logging.Debugf("getPodNetworkAnnotation: %v, %v", client, k8sArgs)
pod, err := client.GetPod(string(k8sArgs.K8S_POD_NAMESPACE), string(k8sArgs.K8S_POD_NAME))
if err != nil {
return "", "", logging.Errorf("getPodNetworkAnnotation: failed to query the pod %v in out of cluster comm: %v", string(k8sArgs.K8S_POD_NAME), err)
}
return pod.Annotations["k8s.v1.cni.cncf.io/networks"], pod.ObjectMeta.Namespace, nil
}
func parsePodNetworkObjectName(podnetwork string) (string, string, string, error) {
var netNsName string
var netIfName string
@ -250,7 +260,7 @@ func getCNIConfigFromFile(name string, confdir string) ([]byte, error) {
return nil, logging.Errorf("Error loading CNI conflist file %s: %v", confFile, err)
}
if confList.Name == name {
if confList.Name == name || name == "" {
return confList.Bytes, nil
}
@ -260,7 +270,7 @@ func getCNIConfigFromFile(name string, confdir string) ([]byte, error) {
return nil, logging.Errorf("Error loading CNI config file %s: %v", confFile, err)
}
if conf.Network.Name == name {
if conf.Network.Name == name || name == "" {
// Ensure the config has a "type" so we know what plugin to run.
// Also catches the case where somebody put a conflist into a conf file.
if conf.Network.Type == "" {
@ -326,30 +336,60 @@ func cniConfigFromNetworkResource(customResource *types.NetworkAttachmentDefinit
return config, nil
}
func getKubernetesDelegate(client KubeClient, net *types.NetworkSelectionElement, confdir string) (*types.DelegateNetConf, error) {
func getKubernetesDelegate(client KubeClient, net *types.NetworkSelectionElement, confdir string, pod *v1.Pod, resourceMap map[string]*types.ResourceInfo) (*types.DelegateNetConf, map[string]*types.ResourceInfo, error) {
logging.Debugf("getKubernetesDelegate: %v, %v, %s", client, net, confdir)
rawPath := fmt.Sprintf("/apis/k8s.cni.cncf.io/v1/namespaces/%s/network-attachment-definitions/%s", net.Namespace, net.Name)
netData, err := client.GetRawWithPath(rawPath)
if err != nil {
return nil, logging.Errorf("getKubernetesDelegate: failed to get network resource, refer Multus README.md for the usage guide: %v", err)
return nil, resourceMap, logging.Errorf("getKubernetesDelegate: failed to get network resource, refer Multus README.md for the usage guide: %v", err)
}
customResource := &types.NetworkAttachmentDefinition{}
if err := json.Unmarshal(netData, customResource); err != nil {
return nil, logging.Errorf("getKubernetesDelegate: failed to get the netplugin data: %v", err)
return nil, resourceMap, logging.Errorf("getKubernetesDelegate: failed to get the netplugin data: %v", err)
}
// Get resourceName annotation from NetworkAttachmentDefinition
deviceID := ""
resourceName, ok := customResource.Metadata.Annotations[resourceNameAnnot]
if ok && pod.Name != "" && pod.Namespace != "" {
// ResourceName annotation is found; try to get device info from resourceMap
logging.Debugf("getKubernetesDelegate: found resourceName annotation : %s", resourceName)
if resourceMap == nil {
ck, err := kubeletclient.GetResourceClient()
if err != nil {
return nil, resourceMap, logging.Errorf("getKubernetesDelegate: failed to get a ResourceClient instance: %v", err)
}
resourceMap, err = ck.GetPodResourceMap(pod)
if err != nil {
return nil, resourceMap, logging.Errorf("getKubernetesDelegate: failed to get resourceMap from ResourceClient: %v", err)
}
logging.Debugf("getKubernetesDelegate(): resourceMap instance: %+v", resourceMap)
}
entry, ok := resourceMap[resourceName]
if ok {
if idCount := len(entry.DeviceIDs); idCount > 0 && idCount > entry.Index {
deviceID = entry.DeviceIDs[entry.Index]
logging.Debugf("getKubernetesDelegate: podName: %s deviceID: %s", pod.Name, deviceID)
entry.Index++ // increment Index for next delegate
}
}
}
configBytes, err := cniConfigFromNetworkResource(customResource, confdir)
if err != nil {
return nil, err
return nil, resourceMap, err
}
delegate, err := types.LoadDelegateNetConf(configBytes, net.InterfaceRequest)
delegate, err := types.LoadDelegateNetConf(configBytes, net, deviceID)
if err != nil {
return nil, err
return nil, resourceMap, err
}
return delegate, nil
return delegate, resourceMap, nil
}
type KubeClient interface {
@ -361,7 +401,7 @@ type KubeClient interface {
func GetK8sArgs(args *skel.CmdArgs) (*types.K8sArgs, error) {
k8sArgs := &types.K8sArgs{}
logging.Debugf("GetK8sNetwork: %v", args)
logging.Debugf("GetK8sArgs: %v", args)
err := cnitypes.LoadArgs(args.Args, k8sArgs)
if err != nil {
return nil, err
@ -372,11 +412,11 @@ func GetK8sArgs(args *skel.CmdArgs) (*types.K8sArgs, error) {
// Attempts to load Kubernetes-defined delegates and add them to the Multus config.
// Returns the number of Kubernetes-defined delegates added or an error.
func TryLoadK8sDelegates(k8sArgs *types.K8sArgs, conf *types.NetConf, kubeClient KubeClient) (int, *clientInfo, error) {
func TryLoadPodDelegates(k8sArgs *types.K8sArgs, conf *types.NetConf, kubeClient KubeClient) (int, *clientInfo, error) {
var err error
clientInfo := &clientInfo{}
logging.Debugf("TryLoadK8sDelegates: %v, %v, %v", k8sArgs, conf, kubeClient)
logging.Debugf("TryLoadPodDelegates: %v, %v, %v", k8sArgs, conf, kubeClient)
kubeClient, err = GetK8sClient(conf.Kubeconfig, kubeClient)
if err != nil {
return 0, nil, err
@ -391,19 +431,40 @@ func TryLoadK8sDelegates(k8sArgs *types.K8sArgs, conf *types.NetConf, kubeClient
}
setKubeClientInfo(clientInfo, kubeClient, k8sArgs)
delegates, err := GetK8sNetwork(kubeClient, k8sArgs, conf.ConfDir)
// Get the pod info. If cannot get it, we use cached delegates
pod, err := kubeClient.GetPod(string(k8sArgs.K8S_POD_NAMESPACE), string(k8sArgs.K8S_POD_NAME))
if err != nil {
if _, ok := err.(*NoK8sNetworkError); ok {
return 0, clientInfo, nil
logging.Debugf("tryLoadK8sDelegates: Err in loading K8s cluster default network from pod annotation: %v, use cached delegates", err)
return 0, nil, nil
}
delegate, err := tryLoadK8sPodDefaultNetwork(kubeClient, pod, conf)
if err != nil {
return 0, nil, logging.Errorf("tryLoadK8sDelegates: Err in loading K8s cluster default network from pod annotation: %v", err)
}
if delegate != nil {
logging.Debugf("tryLoadK8sDelegates: Overwrite the cluster default network with %v from pod annotations", delegate)
conf.Delegates[0] = delegate
}
networks, err := GetPodNetwork(pod)
if networks != nil {
delegates, err := GetNetworkDelegates(kubeClient, pod, networks, conf.ConfDir, conf.NamespaceIsolation)
if err != nil {
if _, ok := err.(*NoK8sNetworkError); ok {
return 0, clientInfo, nil
}
return 0, nil, logging.Errorf("Multus: Err in getting k8s network from pod: %v", err)
}
return 0, nil, logging.Errorf("Multus: Err in getting k8s network from pod: %v", err)
}
if err = conf.AddDelegates(delegates); err != nil {
return 0, nil, err
if err = conf.AddDelegates(delegates); err != nil {
return 0, nil, err
}
return len(delegates), clientInfo, nil
}
return len(delegates), clientInfo, nil
return 0, clientInfo, nil
}
func GetK8sClient(kubeconfig string, kubeClient KubeClient) (KubeClient, error) {
@ -435,6 +496,10 @@ func GetK8sClient(kubeconfig string, kubeClient KubeClient) (KubeClient, error)
return nil, nil
}
// Specify that we use gRPC
config.AcceptContentTypes = "application/vnd.kubernetes.protobuf,application/json"
config.ContentType = "application/vnd.kubernetes.protobuf"
// creates the clientset
client, err := kubernetes.NewForConfig(config)
if err != nil {
@ -444,13 +509,11 @@ func GetK8sClient(kubeconfig string, kubeClient KubeClient) (KubeClient, error)
return &defaultKubeClient{client: client}, nil
}
func GetK8sNetwork(k8sclient KubeClient, k8sArgs *types.K8sArgs, confdir string) ([]*types.DelegateNetConf, error) {
logging.Debugf("GetK8sNetwork: %v, %v, %v", k8sclient, k8sArgs, confdir)
func GetPodNetwork(pod *v1.Pod) ([]*types.NetworkSelectionElement, error) {
logging.Debugf("GetPodNetwork: %v", pod)
netAnnot, defaultNamespace, err := getPodNetworkAnnotation(k8sclient, k8sArgs)
if err != nil {
return nil, err
}
netAnnot := pod.Annotations[networkAttachmentAnnot]
defaultNamespace := pod.ObjectMeta.Namespace
if len(netAnnot) == 0 {
return nil, &NoK8sNetworkError{"no kubernetes network found"}
@ -460,16 +523,177 @@ func GetK8sNetwork(k8sclient KubeClient, k8sArgs *types.K8sArgs, confdir string)
if err != nil {
return nil, err
}
return networks, nil
}
func GetNetworkDelegates(k8sclient KubeClient, pod *v1.Pod, networks []*types.NetworkSelectionElement, confdir string, confnamespaceIsolation bool) ([]*types.DelegateNetConf, error) {
logging.Debugf("GetNetworkDelegates: %v, %v, %v, %v, %v", k8sclient, pod, networks, confdir, confnamespaceIsolation)
// resourceMap holds Pod device allocation information; only initizized if CRD contains 'resourceName' annotation.
// This will only be initialized once and all delegate objects can reference this to look up device info.
var resourceMap map[string]*types.ResourceInfo
// Read all network objects referenced by 'networks'
var delegates []*types.DelegateNetConf
defaultNamespace := pod.ObjectMeta.Namespace
for _, net := range networks {
delegate, err := getKubernetesDelegate(k8sclient, net, confdir)
// The pods namespace (stored as defaultNamespace, does not equal the annotation's target namespace in net.Namespace)
// In the case that this is a mismatch when namespaceisolation is enabled, this should be an error.
if confnamespaceIsolation {
if defaultNamespace != net.Namespace {
return nil, logging.Errorf("GetPodNetwork: namespace isolation violation: podnamespace: %v / target namespace: %v", defaultNamespace, net.Namespace)
}
}
delegate, updatedResourceMap, err := getKubernetesDelegate(k8sclient, net, confdir, pod, resourceMap)
if err != nil {
return nil, logging.Errorf("GetK8sNetwork: failed getting the delegate: %v", err)
return nil, logging.Errorf("GetPodNetwork: failed getting the delegate: %v", err)
}
delegates = append(delegates, delegate)
resourceMap = updatedResourceMap
}
return delegates, nil
}
func getDefaultNetDelegateCRD(client KubeClient, net, confdir, namespace string) (*types.DelegateNetConf, error) {
logging.Debugf("getDefaultNetDelegateCRD: %v, %v, %s, %s", client, net, confdir, namespace)
rawPath := fmt.Sprintf("/apis/k8s.cni.cncf.io/v1/namespaces/%s/network-attachment-definitions/%s", namespace, net)
netData, err := client.GetRawWithPath(rawPath)
if err != nil {
return nil, logging.Errorf("getDefaultNetDelegateCRD: failed to get network resource, refer Multus README.md for the usage guide: %v", err)
}
customResource := &types.NetworkAttachmentDefinition{}
if err := json.Unmarshal(netData, customResource); err != nil {
return nil, logging.Errorf("getDefaultNetDelegateCRD: failed to get the netplugin data: %v", err)
}
configBytes, err := cniConfigFromNetworkResource(customResource, confdir)
if err != nil {
return nil, err
}
delegate, err := types.LoadDelegateNetConf(configBytes, nil, "")
if err != nil {
return nil, err
}
return delegate, nil
}
func getNetDelegate(client KubeClient, netname, confdir, namespace string) (*types.DelegateNetConf, error) {
logging.Debugf("getNetDelegate: %v, %v, %v, %s", client, netname, confdir, namespace)
// option1) search CRD object for the network
delegate, err := getDefaultNetDelegateCRD(client, netname, confdir, namespace)
if err == nil {
return delegate, nil
}
// option2) search CNI json config file
var configBytes []byte
configBytes, err = getCNIConfigFromFile(netname, confdir)
if err == nil {
delegate, err := types.LoadDelegateNetConf(configBytes, nil, "")
if err != nil {
return nil, err
}
return delegate, nil
}
// option3) search directry
fInfo, err := os.Stat(netname)
if err == nil {
if fInfo.IsDir() {
files, err := libcni.ConfFiles(netname, []string{".conf", ".conflist"})
if err != nil {
return nil, err
}
if len(files) > 0 {
var configBytes []byte
configBytes, err = getCNIConfigFromFile("", netname)
if err == nil {
delegate, err := types.LoadDelegateNetConf(configBytes, nil, "")
if err != nil {
return nil, err
}
return delegate, nil
}
return nil, err
}
}
}
return nil, logging.Errorf("getNetDelegate: cannot find network: %v", netname)
}
// GetDefaultNetwork parses 'defaultNetwork' config, gets network json and put it into netconf.Delegates.
func GetDefaultNetworks(k8sArgs *types.K8sArgs, conf *types.NetConf, kubeClient KubeClient) error {
logging.Debugf("GetDefaultNetworks: %v, %v, %v", k8sArgs, conf, kubeClient)
var delegates []*types.DelegateNetConf
kubeClient, err := GetK8sClient(conf.Kubeconfig, kubeClient)
if err != nil {
return err
}
if kubeClient == nil {
if len(conf.Delegates) == 0 {
// No available kube client and no delegates, we can't do anything
return logging.Errorf("must have either Kubernetes config or delegates, refer Multus README.md for the usage guide")
}
return nil
}
delegate, err := getNetDelegate(kubeClient, conf.ClusterNetwork, conf.ConfDir, conf.MultusNamespace)
if err != nil {
return err
}
delegate.MasterPlugin = true
delegates = append(delegates, delegate)
// Pod in kube-system namespace does not have default network for now.
if !types.CheckSystemNamespaces(string(k8sArgs.K8S_POD_NAMESPACE), conf.SystemNamespaces) {
for _, netname := range conf.DefaultNetworks {
delegate, err := getNetDelegate(kubeClient, netname, conf.ConfDir, conf.MultusNamespace)
if err != nil {
return err
}
delegates = append(delegates, delegate)
}
}
if err = conf.AddDelegates(delegates); err != nil {
return err
}
return nil
}
// tryLoadK8sPodDefaultNetwork get pod default network from annotations
func tryLoadK8sPodDefaultNetwork(kubeClient KubeClient, pod *v1.Pod, conf *types.NetConf) (*types.DelegateNetConf, error) {
var netAnnot string
logging.Debugf("tryLoadK8sPodDefaultNetwork: %v, %v, %v", kubeClient, pod, conf)
netAnnot, ok := pod.Annotations[defaultNetAnnot]
if !ok {
logging.Debugf("tryLoadK8sPodDefaultNetwork: Pod default network annotation is not defined")
return nil, nil
}
// The CRD object of default network should only be defined in multusNamespace
networks, err := parsePodNetworkAnnotation(netAnnot, conf.MultusNamespace)
if err != nil {
return nil, logging.Errorf("tryLoadK8sPodDefaultNetwork: failed to parse CRD object: %v", err)
}
if len(networks) > 1 {
return nil, logging.Errorf("tryLoadK8sPodDefaultNetwork: more than one default network is specified: %s", netAnnot)
}
delegate, _, err := getKubernetesDelegate(kubeClient, networks[0], conf.ConfDir, pod, nil)
if err != nil {
return nil, logging.Errorf("tryLoadK8sPodDefaultNetwork: failed getting the delegate: %v", err)
}
delegate.MasterPlugin = true
return delegate, nil
}

View File

@ -25,6 +25,7 @@ import (
testutils "github.com/intel/multus-cni/testing"
"github.com/containernetworking/cni/pkg/skel"
"github.com/intel/multus-cni/types"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
@ -50,7 +51,7 @@ var _ = Describe("k8sclient operations", func() {
})
It("retrieves delegates from kubernetes using simple format annotation", func() {
fakePod := testutils.NewFakePod("testpod", "net1,net2")
fakePod := testutils.NewFakePod("testpod", "net1,net2", "")
net1 := `{
"name": "net1",
"type": "mynet",
@ -81,7 +82,10 @@ var _ = Describe("k8sclient operations", func() {
Expect(err).NotTo(HaveOccurred())
k8sArgs, err := GetK8sArgs(args)
Expect(err).NotTo(HaveOccurred())
delegates, err := GetK8sNetwork(kubeClient, k8sArgs, tmpDir)
pod, err := kubeClient.GetPod(string(k8sArgs.K8S_POD_NAMESPACE), string(k8sArgs.K8S_POD_NAME))
networks, err := GetPodNetwork(pod)
Expect(err).NotTo(HaveOccurred())
delegates, err := GetNetworkDelegates(kubeClient, pod, networks, tmpDir, false)
Expect(err).NotTo(HaveOccurred())
Expect(fKubeClient.PodCount).To(Equal(1))
Expect(fKubeClient.NetCount).To(Equal(2))
@ -96,7 +100,7 @@ var _ = Describe("k8sclient operations", func() {
})
It("fails when the network does not exist", func() {
fakePod := testutils.NewFakePod("testpod", "net1,net2")
fakePod := testutils.NewFakePod("testpod", "net1,net2", "")
net3 := `{
"name": "net3",
"type": "mynet3",
@ -114,9 +118,12 @@ var _ = Describe("k8sclient operations", func() {
Expect(err).NotTo(HaveOccurred())
k8sArgs, err := GetK8sArgs(args)
Expect(err).NotTo(HaveOccurred())
delegates, err := GetK8sNetwork(kubeClient, k8sArgs, tmpDir)
pod, err := kubeClient.GetPod(string(k8sArgs.K8S_POD_NAMESPACE), string(k8sArgs.K8S_POD_NAME))
networks, err := GetPodNetwork(pod)
Expect(err).NotTo(HaveOccurred())
delegates, err := GetNetworkDelegates(kubeClient, pod, networks, tmpDir, false)
Expect(len(delegates)).To(Equal(0))
Expect(err).To(MatchError("GetK8sNetwork: failed getting the delegate: getKubernetesDelegate: failed to get network resource, refer Multus README.md for the usage guide: resource not found"))
Expect(err).To(MatchError("GetPodNetwork: failed getting the delegate: getKubernetesDelegate: failed to get network resource, refer Multus README.md for the usage guide: resource not found"))
})
It("retrieves delegates from kubernetes using JSON format annotation", func() {
@ -131,7 +138,7 @@ var _ = Describe("k8sclient operations", func() {
"name":"net3",
"namespace":"other-ns"
}
]`)
]`, "")
args := &skel.CmdArgs{
Args: fmt.Sprintf("K8S_POD_NAME=%s;K8S_POD_NAMESPACE=%s", fakePod.ObjectMeta.Name, fakePod.ObjectMeta.Namespace),
}
@ -158,7 +165,10 @@ var _ = Describe("k8sclient operations", func() {
Expect(err).NotTo(HaveOccurred())
k8sArgs, err := GetK8sArgs(args)
Expect(err).NotTo(HaveOccurred())
delegates, err := GetK8sNetwork(kubeClient, k8sArgs, tmpDir)
pod, err := kubeClient.GetPod(string(k8sArgs.K8S_POD_NAMESPACE), string(k8sArgs.K8S_POD_NAME))
networks, err := GetPodNetwork(pod)
Expect(err).NotTo(HaveOccurred())
delegates, err := GetNetworkDelegates(kubeClient, pod, networks, tmpDir, false)
Expect(err).NotTo(HaveOccurred())
Expect(fKubeClient.PodCount).To(Equal(1))
Expect(fKubeClient.NetCount).To(Equal(3))
@ -173,7 +183,7 @@ var _ = Describe("k8sclient operations", func() {
})
It("fails when the JSON format annotation is invalid", func() {
fakePod := testutils.NewFakePod("testpod", "[adsfasdfasdfasf]")
fakePod := testutils.NewFakePod("testpod", "[adsfasdfasdfasf]", "")
args := &skel.CmdArgs{
Args: fmt.Sprintf("K8S_POD_NAME=%s;K8S_POD_NAMESPACE=%s", fakePod.ObjectMeta.Name, fakePod.ObjectMeta.Namespace),
}
@ -185,13 +195,14 @@ var _ = Describe("k8sclient operations", func() {
Expect(err).NotTo(HaveOccurred())
k8sArgs, err := GetK8sArgs(args)
Expect(err).NotTo(HaveOccurred())
delegates, err := GetK8sNetwork(kubeClient, k8sArgs, tmpDir)
Expect(len(delegates)).To(Equal(0))
pod, err := kubeClient.GetPod(string(k8sArgs.K8S_POD_NAMESPACE), string(k8sArgs.K8S_POD_NAME))
networks, err := GetPodNetwork(pod)
Expect(len(networks)).To(Equal(0))
Expect(err).To(MatchError("parsePodNetworkAnnotation: failed to parse pod Network Attachment Selection Annotation JSON format: invalid character 'a' looking for beginning of value"))
})
It("retrieves delegates from kubernetes using on-disk config files", func() {
fakePod := testutils.NewFakePod("testpod", "net1,net2")
fakePod := testutils.NewFakePod("testpod", "net1,net2", "")
args := &skel.CmdArgs{
Args: fmt.Sprintf("K8S_POD_NAME=%s;K8S_POD_NAMESPACE=%s", fakePod.ObjectMeta.Name, fakePod.ObjectMeta.Namespace),
}
@ -215,7 +226,10 @@ var _ = Describe("k8sclient operations", func() {
Expect(err).NotTo(HaveOccurred())
k8sArgs, err := GetK8sArgs(args)
Expect(err).NotTo(HaveOccurred())
delegates, err := GetK8sNetwork(kubeClient, k8sArgs, tmpDir)
pod, err := kubeClient.GetPod(string(k8sArgs.K8S_POD_NAMESPACE), string(k8sArgs.K8S_POD_NAME))
networks, err := GetPodNetwork(pod)
Expect(err).NotTo(HaveOccurred())
delegates, err := GetNetworkDelegates(kubeClient, pod, networks, tmpDir, false)
Expect(err).NotTo(HaveOccurred())
Expect(fKubeClient.PodCount).To(Equal(1))
Expect(fKubeClient.NetCount).To(Equal(2))
@ -228,7 +242,7 @@ var _ = Describe("k8sclient operations", func() {
})
It("injects network name into minimal thick plugin CNI config", func() {
fakePod := testutils.NewFakePod("testpod", "net1")
fakePod := testutils.NewFakePod("testpod", "net1", "")
args := &skel.CmdArgs{
Args: fmt.Sprintf("K8S_POD_NAME=%s;K8S_POD_NAMESPACE=%s", fakePod.ObjectMeta.Name, fakePod.ObjectMeta.Namespace),
}
@ -241,7 +255,10 @@ var _ = Describe("k8sclient operations", func() {
Expect(err).NotTo(HaveOccurred())
k8sArgs, err := GetK8sArgs(args)
Expect(err).NotTo(HaveOccurred())
delegates, err := GetK8sNetwork(kubeClient, k8sArgs, tmpDir)
pod, err := kubeClient.GetPod(string(k8sArgs.K8S_POD_NAMESPACE), string(k8sArgs.K8S_POD_NAME))
networks, err := GetPodNetwork(pod)
Expect(err).NotTo(HaveOccurred())
delegates, err := GetNetworkDelegates(kubeClient, pod, networks, tmpDir, false)
Expect(err).NotTo(HaveOccurred())
Expect(fKubeClient.PodCount).To(Equal(1))
Expect(fKubeClient.NetCount).To(Equal(1))
@ -252,7 +269,7 @@ var _ = Describe("k8sclient operations", func() {
})
It("fails when on-disk config file is not valid", func() {
fakePod := testutils.NewFakePod("testpod", "net1,net2")
fakePod := testutils.NewFakePod("testpod", "net1,net2", "")
args := &skel.CmdArgs{
Args: fmt.Sprintf("K8S_POD_NAME=%s;K8S_POD_NAMESPACE=%s", fakePod.ObjectMeta.Name, fakePod.ObjectMeta.Namespace),
}
@ -272,8 +289,326 @@ var _ = Describe("k8sclient operations", func() {
Expect(err).NotTo(HaveOccurred())
k8sArgs, err := GetK8sArgs(args)
Expect(err).NotTo(HaveOccurred())
delegates, err := GetK8sNetwork(kubeClient, k8sArgs, tmpDir)
pod, err := kubeClient.GetPod(string(k8sArgs.K8S_POD_NAMESPACE), string(k8sArgs.K8S_POD_NAME))
networks, err := GetPodNetwork(pod)
Expect(err).NotTo(HaveOccurred())
delegates, err := GetNetworkDelegates(kubeClient, pod, networks, tmpDir, false)
Expect(len(delegates)).To(Equal(0))
Expect(err).To(MatchError(fmt.Sprintf("GetK8sNetwork: failed getting the delegate: cniConfigFromNetworkResource: err in getCNIConfigFromFile: Error loading CNI config file %s: error parsing configuration: invalid character 'a' looking for beginning of value", net2Name)))
Expect(err).To(MatchError(fmt.Sprintf("GetPodNetwork: failed getting the delegate: cniConfigFromNetworkResource: err in getCNIConfigFromFile: Error loading CNI config file %s: error parsing configuration: invalid character 'a' looking for beginning of value", net2Name)))
})
It("retrieves cluster network from CRD", func() {
fakePod := testutils.NewFakePod("testpod", "", "")
conf := `{
"name":"node-cni-network",
"type":"multus",
"clusterNetwork": "myCRD1",
"kubeconfig":"/etc/kubernetes/node-kubeconfig.yaml"
}`
netConf, err := types.LoadNetConf([]byte(conf))
Expect(err).NotTo(HaveOccurred())
args := &skel.CmdArgs{
Args: fmt.Sprintf("K8S_POD_NAME=%s;K8S_POD_NAMESPACE=%s", fakePod.ObjectMeta.Name, fakePod.ObjectMeta.Namespace),
}
fKubeClient := testutils.NewFakeKubeClient()
fKubeClient.AddNetConfig("kube-system", "myCRD1", "{\"type\": \"mynet\"}")
fKubeClient.AddPod(fakePod)
kubeClient, err := GetK8sClient("", fKubeClient)
Expect(err).NotTo(HaveOccurred())
k8sArgs, err := GetK8sArgs(args)
Expect(err).NotTo(HaveOccurred())
err = GetDefaultNetworks(k8sArgs, netConf, kubeClient)
Expect(err).NotTo(HaveOccurred())
Expect(len(netConf.Delegates)).To(Equal(1))
Expect(netConf.Delegates[0].Conf.Name).To(Equal("myCRD1"))
Expect(netConf.Delegates[0].Conf.Type).To(Equal("mynet"))
})
It("retrieves default networks from CRD", func() {
fakePod := testutils.NewFakePod("testpod", "", "")
conf := `{
"name":"node-cni-network",
"type":"multus",
"clusterNetwork": "myCRD1",
"defaultNetworks": ["myCRD2"],
"kubeconfig":"/etc/kubernetes/node-kubeconfig.yaml"
}`
netConf, err := types.LoadNetConf([]byte(conf))
Expect(err).NotTo(HaveOccurred())
args := &skel.CmdArgs{
Args: fmt.Sprintf("K8S_POD_NAME=%s;K8S_POD_NAMESPACE=%s", fakePod.ObjectMeta.Name, fakePod.ObjectMeta.Namespace),
}
fKubeClient := testutils.NewFakeKubeClient()
fKubeClient.AddNetConfig("kube-system", "myCRD1", "{\"type\": \"mynet\"}")
fKubeClient.AddNetConfig("kube-system", "myCRD2", "{\"type\": \"mynet2\"}")
fKubeClient.AddPod(fakePod)
kubeClient, err := GetK8sClient("", fKubeClient)
Expect(err).NotTo(HaveOccurred())
k8sArgs, err := GetK8sArgs(args)
Expect(err).NotTo(HaveOccurred())
err = GetDefaultNetworks(k8sArgs, netConf, kubeClient)
Expect(err).NotTo(HaveOccurred())
Expect(len(netConf.Delegates)).To(Equal(2))
Expect(netConf.Delegates[0].Conf.Name).To(Equal("myCRD1"))
Expect(netConf.Delegates[0].Conf.Type).To(Equal("mynet"))
Expect(netConf.Delegates[1].Conf.Name).To(Equal("myCRD2"))
Expect(netConf.Delegates[1].Conf.Type).To(Equal("mynet2"))
})
It("ignore default networks from CRD in case of kube-system namespace", func() {
fakePod := testutils.NewFakePod("testpod", "", "")
// overwrite namespace
fakePod.ObjectMeta.Namespace = "kube-system"
conf := `{
"name":"node-cni-network",
"type":"multus",
"clusterNetwork": "myCRD1",
"defaultNetworks": ["myCRD2"],
"kubeconfig":"/etc/kubernetes/node-kubeconfig.yaml"
}`
netConf, err := types.LoadNetConf([]byte(conf))
Expect(err).NotTo(HaveOccurred())
args := &skel.CmdArgs{
Args: fmt.Sprintf("K8S_POD_NAME=%s;K8S_POD_NAMESPACE=%s", fakePod.ObjectMeta.Name, fakePod.ObjectMeta.Namespace),
}
fKubeClient := testutils.NewFakeKubeClient()
fKubeClient.AddNetConfig("kube-system", "myCRD1", "{\"type\": \"mynet\"}")
fKubeClient.AddNetConfig("kube-system", "myCRD2", "{\"type\": \"mynet2\"}")
fKubeClient.AddPod(fakePod)
kubeClient, err := GetK8sClient("", fKubeClient)
Expect(err).NotTo(HaveOccurred())
k8sArgs, err := GetK8sArgs(args)
Expect(err).NotTo(HaveOccurred())
err = GetDefaultNetworks(k8sArgs, netConf, kubeClient)
Expect(err).NotTo(HaveOccurred())
Expect(len(netConf.Delegates)).To(Equal(1))
Expect(netConf.Delegates[0].Conf.Name).To(Equal("myCRD1"))
Expect(netConf.Delegates[0].Conf.Type).To(Equal("mynet"))
})
It("retrieves cluster network from file", func() {
fakePod := testutils.NewFakePod("testpod", "", "")
conf := `{
"name":"node-cni-network",
"type":"multus",
"clusterNetwork": "myFile1",
"kubeconfig":"/etc/kubernetes/node-kubeconfig.yaml"
}`
netConf, err := types.LoadNetConf([]byte(conf))
netConf.ConfDir = tmpDir
Expect(err).NotTo(HaveOccurred())
args := &skel.CmdArgs{
Args: fmt.Sprintf("K8S_POD_NAME=%s;K8S_POD_NAMESPACE=%s", fakePod.ObjectMeta.Name, fakePod.ObjectMeta.Namespace),
}
fKubeClient := testutils.NewFakeKubeClient()
fKubeClient.AddPod(fakePod)
net1Name := filepath.Join(tmpDir, "10-net1.conf")
fKubeClient.AddNetFile(fakePod.ObjectMeta.Namespace, "net1", net1Name, `{
"name": "myFile1",
"type": "mynet",
"cniVersion": "0.2.0"
}`)
kubeClient, err := GetK8sClient("", fKubeClient)
Expect(err).NotTo(HaveOccurred())
k8sArgs, err := GetK8sArgs(args)
Expect(err).NotTo(HaveOccurred())
err = GetDefaultNetworks(k8sArgs, netConf, kubeClient)
Expect(err).NotTo(HaveOccurred())
Expect(len(netConf.Delegates)).To(Equal(1))
Expect(netConf.Delegates[0].Conf.Name).To(Equal("myFile1"))
Expect(netConf.Delegates[0].Conf.Type).To(Equal("mynet"))
})
It("retrieves cluster network from path", func() {
fakePod := testutils.NewFakePod("testpod", "", "")
conf := fmt.Sprintf(`{
"name":"node-cni-network",
"type":"multus",
"clusterNetwork": "%s",
"kubeconfig":"/etc/kubernetes/node-kubeconfig.yaml"
}`, tmpDir)
netConf, err := types.LoadNetConf([]byte(conf))
Expect(err).NotTo(HaveOccurred())
args := &skel.CmdArgs{
Args: fmt.Sprintf("K8S_POD_NAME=%s;K8S_POD_NAMESPACE=%s", fakePod.ObjectMeta.Name, fakePod.ObjectMeta.Namespace),
}
fKubeClient := testutils.NewFakeKubeClient()
net1Name := filepath.Join(tmpDir, "10-net1.conf")
fKubeClient.AddNetFile(fakePod.ObjectMeta.Namespace, "10-net1", net1Name, `{
"name": "net1",
"type": "mynet",
"cniVersion": "0.2.0"
}`)
fKubeClient.AddPod(fakePod)
kubeClient, err := GetK8sClient("", fKubeClient)
Expect(err).NotTo(HaveOccurred())
k8sArgs, err := GetK8sArgs(args)
Expect(err).NotTo(HaveOccurred())
err = GetDefaultNetworks(k8sArgs, netConf, kubeClient)
Expect(err).NotTo(HaveOccurred())
Expect(len(netConf.Delegates)).To(Equal(1))
Expect(netConf.Delegates[0].Conf.Name).To(Equal("net1"))
Expect(netConf.Delegates[0].Conf.Type).To(Equal("mynet"))
})
It("Error in case of CRD not found", func() {
fakePod := testutils.NewFakePod("testpod", "", "")
conf := `{
"name":"node-cni-network",
"type":"multus",
"clusterNetwork": "myCRD1",
"kubeconfig":"/etc/kubernetes/node-kubeconfig.yaml"
}`
netConf, err := types.LoadNetConf([]byte(conf))
Expect(err).NotTo(HaveOccurred())
args := &skel.CmdArgs{
Args: fmt.Sprintf("K8S_POD_NAME=%s;K8S_POD_NAMESPACE=%s", fakePod.ObjectMeta.Name, fakePod.ObjectMeta.Namespace),
}
fKubeClient := testutils.NewFakeKubeClient()
fKubeClient.AddPod(fakePod)
kubeClient, err := GetK8sClient("", fKubeClient)
Expect(err).NotTo(HaveOccurred())
k8sArgs, err := GetK8sArgs(args)
Expect(err).NotTo(HaveOccurred())
err = GetDefaultNetworks(k8sArgs, netConf, kubeClient)
Expect(err).To(HaveOccurred())
})
It("overwrite cluster network when Pod annotation is set", func() {
fakePod := testutils.NewFakePod("testpod", "", "net1")
conf := `{
"name":"node-cni-network",
"type":"multus",
"clusterNetwork": "net2",
"multusNamespace" : "kube-system",
"kubeconfig":"/etc/kubernetes/node-kubeconfig.yaml"
}`
netConf, err := types.LoadNetConf([]byte(conf))
Expect(err).NotTo(HaveOccurred())
args := &skel.CmdArgs{
Args: fmt.Sprintf("K8S_POD_NAME=%s;K8S_POD_NAMESPACE=%s", fakePod.ObjectMeta.Name, fakePod.ObjectMeta.Namespace),
}
fKubeClient := testutils.NewFakeKubeClient()
fKubeClient.AddNetConfig("kube-system", "net1", "{\"type\": \"mynet1\"}")
fKubeClient.AddNetConfig("kube-system", "net2", "{\"type\": \"mynet2\"}")
fKubeClient.AddPod(fakePod)
kubeClient, err := GetK8sClient("", fKubeClient)
Expect(err).NotTo(HaveOccurred())
k8sArgs, err := GetK8sArgs(args)
Expect(err).NotTo(HaveOccurred())
err = GetDefaultNetworks(k8sArgs, netConf, kubeClient)
Expect(err).NotTo(HaveOccurred())
Expect(len(netConf.Delegates)).To(Equal(1))
Expect(netConf.Delegates[0].Conf.Name).To(Equal("net2"))
Expect(netConf.Delegates[0].Conf.Type).To(Equal("mynet2"))
numK8sDelegates, _, err := TryLoadPodDelegates(k8sArgs, netConf, kubeClient)
Expect(err).NotTo(HaveOccurred())
Expect(numK8sDelegates).To(Equal(0))
Expect(netConf.Delegates[0].Conf.Name).To(Equal("net1"))
Expect(netConf.Delegates[0].Conf.Type).To(Equal("mynet1"))
})
It("overwrite multus config when Pod annotation is set", func() {
fakePod := testutils.NewFakePod("testpod", "", "net1")
conf := `{
"name":"node-cni-network",
"type":"multus",
"kubeconfig":"/etc/kubernetes/node-kubeconfig.yaml",
"delegates": [{
"type": "mynet2",
"name": "net2"
}]
}`
netConf, err := types.LoadNetConf([]byte(conf))
Expect(netConf.Delegates[0].Conf.Name).To(Equal("net2"))
Expect(netConf.Delegates[0].Conf.Type).To(Equal("mynet2"))
Expect(err).NotTo(HaveOccurred())
args := &skel.CmdArgs{
Args: fmt.Sprintf("K8S_POD_NAME=%s;K8S_POD_NAMESPACE=%s", fakePod.ObjectMeta.Name, fakePod.ObjectMeta.Namespace),
}
fKubeClient := testutils.NewFakeKubeClient()
fKubeClient.AddPod(fakePod)
fKubeClient.AddNetConfig("kube-system", "net1", "{\"type\": \"mynet1\"}")
kubeClient, err := GetK8sClient("", fKubeClient)
Expect(err).NotTo(HaveOccurred())
k8sArgs, err := GetK8sArgs(args)
Expect(err).NotTo(HaveOccurred())
numK8sDelegates, _, err := TryLoadPodDelegates(k8sArgs, netConf, kubeClient)
Expect(err).NotTo(HaveOccurred())
Expect(numK8sDelegates).To(Equal(0))
Expect(netConf.Delegates[0].Conf.Name).To(Equal("net1"))
Expect(netConf.Delegates[0].Conf.Type).To(Equal("mynet1"))
})
It("Errors when namespace isolation is violated", func() {
fakePod := testutils.NewFakePod("testpod", "kube-system/net1", "")
conf := `{
"name":"node-cni-network",
"type":"multus",
"delegates": [{
"name": "weave1",
"cniVersion": "0.2.0",
"type": "weave-net"
}],
"kubeconfig":"/etc/kubernetes/node-kubeconfig.yaml",
"namespaceIsolation": true
}`
netConf, err := types.LoadNetConf([]byte(conf))
Expect(err).NotTo(HaveOccurred())
net1 := `{
"name": "net1",
"type": "mynet",
"cniVersion": "0.2.0"
}`
args := &skel.CmdArgs{
Args: fmt.Sprintf("K8S_POD_NAME=%s;K8S_POD_NAMESPACE=%s", fakePod.ObjectMeta.Name, fakePod.ObjectMeta.Namespace),
}
fKubeClient := testutils.NewFakeKubeClient()
fKubeClient.AddPod(fakePod)
fKubeClient.AddNetConfig("kube-system", "net1", net1)
kubeClient, err := GetK8sClient("", fKubeClient)
Expect(err).NotTo(HaveOccurred())
k8sArgs, err := GetK8sArgs(args)
Expect(err).NotTo(HaveOccurred())
pod, err := kubeClient.GetPod(string(k8sArgs.K8S_POD_NAMESPACE), string(k8sArgs.K8S_POD_NAME))
networks, err := GetPodNetwork(pod)
Expect(err).NotTo(HaveOccurred())
_, err = GetNetworkDelegates(kubeClient, pod, networks, tmpDir, netConf.NamespaceIsolation)
Expect(err).To(HaveOccurred())
Expect(err).To(MatchError("GetPodNetwork: namespace isolation violation: podnamespace: test / target namespace: kube-system"))
})
})

View File

@ -0,0 +1,113 @@
package kubeletclient
import (
"os"
"path/filepath"
"time"
"github.com/intel/multus-cni/checkpoint"
"github.com/intel/multus-cni/logging"
"github.com/intel/multus-cni/types"
"golang.org/x/net/context"
v1 "k8s.io/api/core/v1"
"k8s.io/kubernetes/pkg/kubelet/apis/podresources"
podresourcesapi "k8s.io/kubernetes/pkg/kubelet/apis/podresources/v1alpha1"
"k8s.io/kubernetes/pkg/kubelet/util"
)
const (
defaultKubeletSocketFile = "kubelet.sock"
defaultPodResourcesMaxSize = 1024 * 1024 * 16 // 16 Mb
)
var (
kubeletSocket string
defaultPodResourcesPath = "/var/lib/kubelet/pod-resources"
)
// GetResourceClient returns an instance of ResourceClient interface initialized with Pod resource information
func GetResourceClient() (types.ResourceClient, error) {
// If Kubelet resource API endpoint exist use that by default
// Or else fallback with checkpoint file
if hasKubeletAPIEndpoint() {
logging.Printf(logging.VerboseLevel, "GetResourceClient(): using Kubelet resource API endpoint")
return getKubeletClient()
} else {
logging.Printf(logging.VerboseLevel, "GetResourceClient(): using Kubelet device plugin checkpoint")
return checkpoint.GetCheckpoint()
}
}
func getKubeletClient() (types.ResourceClient, error) {
newClient := &kubeletClient{}
if kubeletSocket == "" {
kubeletSocket = util.LocalEndpoint(defaultPodResourcesPath, podresources.Socket)
}
client, conn, err := podresources.GetClient(kubeletSocket, 10*time.Second, defaultPodResourcesMaxSize)
if err != nil {
return nil, logging.Errorf("GetResourceClient(): error getting grpc client: %v\n", err)
}
defer conn.Close()
if err := newClient.getPodResources(client); err != nil {
return nil, logging.Errorf("GetResourceClient(): error getting resource client: %v\n", err)
}
return newClient, nil
}
type kubeletClient struct {
resources []*podresourcesapi.PodResources
}
func (rc *kubeletClient) getPodResources(client podresourcesapi.PodResourcesListerClient) error {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
resp, err := client.List(ctx, &podresourcesapi.ListPodResourcesRequest{})
if err != nil {
return logging.Errorf("getPodResources(): %v.Get(_) = _, %v", client, err)
}
rc.resources = resp.PodResources
return nil
}
// GetPodResourceMap returns an instance of a map of Pod ResourceInfo given a (Pod name, namespace) tuple
func (rc *kubeletClient) GetPodResourceMap(pod *v1.Pod) (map[string]*types.ResourceInfo, error) {
resourceMap := make(map[string]*types.ResourceInfo)
name := pod.Name
ns := pod.Namespace
if name == "" || ns == "" {
return nil, logging.Errorf("GetPodResourcesMap(): Pod name or namespace cannot be empty")
}
for _, pr := range rc.resources {
if pr.Name == name && pr.Namespace == ns {
for _, cnt := range pr.Containers {
for _, dev := range cnt.Devices {
if rInfo, ok := resourceMap[dev.ResourceName]; ok {
rInfo.DeviceIDs = append(rInfo.DeviceIDs, dev.DeviceIds...)
} else {
resourceMap[dev.ResourceName] = &types.ResourceInfo{DeviceIDs: dev.DeviceIds}
}
}
}
}
}
return resourceMap, nil
}
func hasKubeletAPIEndpoint() bool {
// Check for kubelet resource API socket file
kubeletAPISocket := filepath.Join(defaultPodResourcesPath, defaultKubeletSocketFile)
if _, err := os.Stat(kubeletAPISocket); err != nil {
logging.Verbosef("hasKubeletAPIEndpoint(): error looking up kubelet resource api socket file: %q", err)
return false
}
return true
}

View File

@ -0,0 +1,209 @@
package kubeletclient
import (
"context"
"io/ioutil"
"os"
"path/filepath"
"testing"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"google.golang.org/grpc"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
k8sTypes "k8s.io/apimachinery/pkg/types"
"k8s.io/kubernetes/pkg/kubelet/util"
mtypes "github.com/intel/multus-cni/types"
podresourcesapi "k8s.io/kubernetes/pkg/kubelet/apis/podresources/v1alpha1"
)
var (
socketDir string
socketName string
fakeServer *fakeResourceServer
)
type fakeResourceServer struct {
server *grpc.Server
}
func (m *fakeResourceServer) List(ctx context.Context, req *podresourcesapi.ListPodResourcesRequest) (*podresourcesapi.ListPodResourcesResponse, error) {
podName := "pod-name"
podNamespace := "pod-namespace"
containerName := "container-name"
devs := []*podresourcesapi.ContainerDevices{
{
ResourceName: "resource",
DeviceIds: []string{"dev0", "dev1"},
},
}
resp := &podresourcesapi.ListPodResourcesResponse{
PodResources: []*podresourcesapi.PodResources{
{
Name: podName,
Namespace: podNamespace,
Containers: []*podresourcesapi.ContainerResources{
{
Name: containerName,
Devices: devs,
},
},
},
},
}
return resp, nil
}
func TestKubeletclient(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Kubeletclient Suite")
}
func setUp() error {
tempSocketDir, err := ioutil.TempDir("", "kubelet-resource-client")
if err != nil {
return err
}
defaultPodResourcesPath = filepath.Join(tempSocketDir, defaultPodResourcesPath)
if err := os.MkdirAll(defaultPodResourcesPath, os.ModeDir); err != nil {
return err
}
socketDir = defaultPodResourcesPath
socketName = filepath.Join(socketDir, "kubelet.sock")
fakeServer = &fakeResourceServer{server: grpc.NewServer()}
podresourcesapi.RegisterPodResourcesListerServer(fakeServer.server, fakeServer)
lis, err := util.CreateListener(socketName)
if err != nil {
return nil
}
go fakeServer.server.Serve(lis)
return nil
}
func tearDown(path string) error {
if fakeServer != nil {
fakeServer.server.Stop()
}
if err := os.RemoveAll(path); err != nil {
return err
}
return nil
}
var _ = BeforeSuite(func() {
err := setUp()
Expect(err).NotTo(HaveOccurred())
})
var _ = AfterSuite(func() {
err := tearDown(socketDir)
Expect(err).NotTo(HaveOccurred())
})
var _ = Describe("Kubelet resource endpoint data read operations", func() {
Context("GetResourceClient()", func() {
It("should return no error", func() {
kubeletSocket = socketName
_, err := GetResourceClient()
Expect(err).NotTo(HaveOccurred())
})
})
Context("GetPodResourceMap() with valid pod name and namespace", func() {
It("should return no error", func() {
podUID := k8sTypes.UID("970a395d-bb3b-11e8-89df-408d5c537d23")
fakePod := &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "pod-name",
Namespace: "pod-namespace",
UID: podUID,
},
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Name: "container-name",
},
},
},
}
kubeletSocket = socketName
client, err := getKubeletClient()
Expect(err).NotTo(HaveOccurred())
outputRMap := map[string]*mtypes.ResourceInfo{
"resource": &mtypes.ResourceInfo{DeviceIDs: []string{"dev0", "dev1"}},
}
resourceMap, err := client.GetPodResourceMap(fakePod)
Expect(err).NotTo(HaveOccurred())
Expect(resourceMap).ShouldNot(BeNil())
Expect(resourceMap).To(Equal(outputRMap))
})
})
Context("GetPodResourceMap() with empty podname", func() {
It("should return error", func() {
podUID := k8sTypes.UID("970a395d-bb3b-11e8-89df-408d5c537d23")
fakePod := &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "",
Namespace: "pod-namespace",
UID: podUID,
},
}
kubeletSocket = socketName
client, err := getKubeletClient()
Expect(err).NotTo(HaveOccurred())
_, err = client.GetPodResourceMap(fakePod)
Expect(err).To(HaveOccurred())
})
})
Context("GetPodResourceMap() with empty namespace", func() {
It("should return error", func() {
podUID := k8sTypes.UID("970a395d-bb3b-11e8-89df-408d5c537d23")
fakePod := &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "pod-name",
Namespace: "",
UID: podUID,
},
}
kubeletSocket = socketName
client, err := getKubeletClient()
Expect(err).NotTo(HaveOccurred())
_, err = client.GetPodResourceMap(fakePod)
Expect(err).To(HaveOccurred())
})
})
Context("GetPodResourceMap() with non-existent podname and namespace", func() {
It("should return no error", func() {
podUID := k8sTypes.UID("970a395d-bb3b-11e8-89df-408d5c537d23")
fakePod := &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "whateverpod",
Namespace: "whatevernamespace",
UID: podUID,
},
}
kubeletSocket = socketName
client, err := getKubeletClient()
Expect(err).NotTo(HaveOccurred())
emptyRMap := map[string]*mtypes.ResourceInfo{}
resourceMap, err := client.GetPodResourceMap(fakePod)
Expect(err).NotTo(HaveOccurred())
Expect(resourceMap).ShouldNot(BeNil())
Expect(resourceMap).To(Equal(emptyRMap))
})
})
})

View File

@ -29,6 +29,7 @@ type Level uint32
const (
PanicLevel Level = iota
ErrorLevel
VerboseLevel
DebugLevel
MaxLevel
UnknownLevel
@ -44,6 +45,8 @@ func (l Level) String() string {
switch l {
case PanicLevel:
return "panic"
case VerboseLevel:
return "verbose"
case ErrorLevel:
return "error"
case DebugLevel:
@ -76,6 +79,10 @@ func Debugf(format string, a ...interface{}) {
Printf(DebugLevel, format, a...)
}
func Verbosef(format string, a ...interface{}) {
Printf(VerboseLevel, format, a...)
}
func Errorf(format string, a ...interface{}) error {
Printf(ErrorLevel, format, a...)
return fmt.Errorf(format, a...)
@ -88,10 +95,16 @@ func Panicf(format string, a ...interface{}) {
Printf(PanicLevel, "========= Stack trace output end ========")
}
func GetLoggingLevel(levelStr string) Level {
func GetLoggingLevel() Level {
return loggingLevel
}
func getLoggingLevel(levelStr string) Level {
switch strings.ToLower(levelStr) {
case "debug":
return DebugLevel
case "verbose":
return VerboseLevel
case "error":
return ErrorLevel
case "panic":
@ -102,7 +115,7 @@ func GetLoggingLevel(levelStr string) Level {
}
func SetLogLevel(levelStr string) {
level := GetLoggingLevel(levelStr)
level := getLoggingLevel(levelStr)
if level < MaxLevel {
loggingLevel = level
}

View File

@ -50,6 +50,8 @@ var _ = Describe("logging operations", func() {
Expect(loggingLevel).To(Equal(DebugLevel))
SetLogLevel("Error")
Expect(loggingLevel).To(Equal(ErrorLevel))
SetLogLevel("VERbose")
Expect(loggingLevel).To(Equal(VerboseLevel))
SetLogLevel("PANIC")
Expect(loggingLevel).To(Equal(PanicLevel))
})

View File

@ -19,24 +19,46 @@
package main
import (
"context"
"encoding/json"
"flag"
"fmt"
"io/ioutil"
"net"
"os"
"path/filepath"
"strings"
"time"
"github.com/containernetworking/cni/libcni"
"github.com/containernetworking/cni/pkg/invoke"
"github.com/containernetworking/cni/pkg/skel"
cnitypes "github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/cni/pkg/version"
cniversion "github.com/containernetworking/cni/pkg/version"
"github.com/containernetworking/plugins/pkg/ns"
k8s "github.com/intel/multus-cni/k8sclient"
"github.com/intel/multus-cni/logging"
"github.com/intel/multus-cni/types"
"github.com/vishvananda/netlink"
"k8s.io/apimachinery/pkg/util/wait"
)
var version = "master@git"
var commit = "unknown commit"
var date = "unknown date"
var defaultReadinessBackoff = wait.Backoff{
Steps: 4,
Duration: 250 * time.Millisecond,
Factor: 4.0,
Jitter: 0.1,
}
func printVersionString() string {
return fmt.Sprintf("multus-cni version:%s, commit:%s, date:%s",
version, commit, date)
}
func saveScratchNetConf(containerID, dataDir string, netconf []byte) error {
logging.Debugf("saveScratchNetConf: %s, %s, %s", containerID, dataDir, string(netconf))
if err := os.MkdirAll(dataDir, 0700); err != nil {
@ -53,12 +75,12 @@ func saveScratchNetConf(containerID, dataDir string, netconf []byte) error {
return err
}
func consumeScratchNetConf(containerID, dataDir string) ([]byte, error) {
func consumeScratchNetConf(containerID, dataDir string) ([]byte, string, error) {
logging.Debugf("consumeScratchNetConf: %s, %s", containerID, dataDir)
path := filepath.Join(dataDir, containerID)
defer os.Remove(path)
return ioutil.ReadFile(path)
b, err := ioutil.ReadFile(path)
return b, path, err
}
func getIfname(delegate *types.DelegateNetConf, argif string, idx int) string {
@ -84,12 +106,23 @@ func saveDelegates(containerID, dataDir string, delegates []*types.DelegateNetCo
}
if err = saveScratchNetConf(containerID, dataDir, delegatesBytes); err != nil {
return logging.Errorf("error in saving the delegates : %v", err)
return logging.Errorf("error in saving the delegates : %v", err)
}
return err
}
func deleteDelegates(containerID, dataDir string) error {
logging.Debugf("deleteDelegates: %s, %s", containerID, dataDir)
path := filepath.Join(dataDir, containerID)
if err := os.Remove(path); err != nil {
return logging.Errorf("error in deleting the delegates : %v", err)
}
return nil
}
func validateIfName(nsname string, ifname string) error {
logging.Debugf("validateIfName: %s, %s", nsname, ifname)
podNs, err := ns.GetNS(nsname)
@ -111,18 +144,19 @@ func validateIfName(nsname string, ifname string) error {
return err
}
func conflistAdd(rt *libcni.RuntimeConf, rawnetconflist []byte, binDir string) (cnitypes.Result, error) {
func conflistAdd(rt *libcni.RuntimeConf, rawnetconflist []byte, binDir string, exec invoke.Exec) (cnitypes.Result, error) {
logging.Debugf("conflistAdd: %v, %s, %s", rt, string(rawnetconflist), binDir)
// In part, adapted from K8s pkg/kubelet/dockershim/network/cni/cni.go
binDirs := []string{binDir}
cniNet := libcni.CNIConfig{Path: binDirs}
binDirs := filepath.SplitList(os.Getenv("CNI_PATH"))
binDirs = append(binDirs, binDir)
cniNet := libcni.NewCNIConfig(binDirs, exec)
confList, err := libcni.ConfListFromBytes(rawnetconflist)
if err != nil {
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)
}
@ -130,18 +164,19 @@ func conflistAdd(rt *libcni.RuntimeConf, rawnetconflist []byte, binDir string) (
return result, nil
}
func conflistDel(rt *libcni.RuntimeConf, rawnetconflist []byte, binDir string) error {
func conflistDel(rt *libcni.RuntimeConf, rawnetconflist []byte, binDir string, exec invoke.Exec) error {
logging.Debugf("conflistDel: %v, %s, %s", rt, string(rawnetconflist), binDir)
// In part, adapted from K8s pkg/kubelet/dockershim/network/cni/cni.go
binDirs := []string{binDir}
cniNet := libcni.CNIConfig{Path: binDirs}
binDirs := filepath.SplitList(os.Getenv("CNI_PATH"))
binDirs = append(binDirs, binDir)
cniNet := libcni.NewCNIConfig(binDirs, exec)
confList, err := libcni.ConfListFromBytes(rawnetconflist)
if err != nil {
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)
}
@ -149,7 +184,7 @@ func conflistDel(rt *libcni.RuntimeConf, rawnetconflist []byte, binDir string) e
return err
}
func delegateAdd(exec invoke.Exec, ifName string, delegate *types.DelegateNetConf, rt *libcni.RuntimeConf, binDir string) (cnitypes.Result, error) {
func delegateAdd(exec invoke.Exec, ifName string, delegate *types.DelegateNetConf, rt *libcni.RuntimeConf, binDir string, cniArgs string) (cnitypes.Result, error) {
logging.Debugf("delegateAdd: %v, %s, %v, %v, %s", exec, ifName, delegate, rt, binDir)
if os.Setenv("CNI_IFNAME", ifName) != nil {
return nil, logging.Errorf("Multus: error in setting CNI_IFNAME")
@ -159,18 +194,69 @@ func delegateAdd(exec invoke.Exec, ifName string, delegate *types.DelegateNetCon
return nil, logging.Errorf("cannot set %q ifname to %q: %v", delegate.Conf.Type, ifName, err)
}
if delegate.ConfListPlugin != false {
result, err := conflistAdd(rt, delegate.Bytes, binDir)
if delegate.MacRequest != "" || delegate.IPRequest != "" {
if cniArgs != "" {
cniArgs = fmt.Sprintf("%s;IgnoreUnknown=true", cniArgs)
} else {
cniArgs = "IgnoreUnknown=true"
}
if delegate.MacRequest != "" {
// validate Mac address
_, err := net.ParseMAC(delegate.MacRequest)
if err != nil {
return nil, logging.Errorf("failed to parse mac address %q", delegate.MacRequest)
}
cniArgs = fmt.Sprintf("%s;MAC=%s", cniArgs, delegate.MacRequest)
logging.Debugf("Set MAC address %q to %q", delegate.MacRequest, ifName)
}
if delegate.IPRequest != "" {
// validate IP address
if strings.Contains(delegate.IPRequest, "/") {
_, _, err := net.ParseCIDR(delegate.IPRequest)
if err != nil {
return nil, logging.Errorf("failed to parse CIDR %q", delegate.MacRequest)
}
} else if net.ParseIP(delegate.IPRequest) == nil {
return nil, logging.Errorf("failed to parse IP address %q", delegate.IPRequest)
}
cniArgs = fmt.Sprintf("%s;IP=%s", cniArgs, delegate.IPRequest)
logging.Debugf("Set IP address %q to %q", delegate.IPRequest, ifName)
}
if os.Setenv("CNI_ARGS", cniArgs) != nil {
return nil, logging.Errorf("cannot set %q mac to %q and ip to %q", delegate.Conf.Type, delegate.MacRequest, delegate.IPRequest)
}
}
var result cnitypes.Result
var err error
if delegate.ConfListPlugin {
result, err = conflistAdd(rt, delegate.Bytes, binDir, exec)
if err != nil {
return nil, logging.Errorf("Multus: error in invoke Conflist add - %q: %v", delegate.ConfList.Name, err)
}
return result, nil
} else {
origpath := os.Getenv("CNI_PATH")
os.Setenv("CNI_PATH", origpath+":"+binDir)
result, err = invoke.DelegateAdd(context.Background(), delegate.Conf.Type, delegate.Bytes, exec)
os.Setenv("CNI_PATH", origpath)
if err != nil {
return nil, logging.Errorf("Multus: error in invoke Delegate add - %q: %v", delegate.Conf.Type, err)
}
}
result, err := invoke.DelegateAdd(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)
if logging.GetLoggingLevel() >= logging.VerboseLevel {
data, _ := json.Marshal(result)
var confName string
if delegate.ConfListPlugin {
confName = delegate.ConfList.Name
} else {
confName = delegate.Conf.Name
}
logging.Verbosef("Add: %s:%s:%s:%s %s", rt.Args[1][1], rt.Args[2][1], confName, rt.IfName, string(data))
}
return result, nil
@ -182,20 +268,33 @@ func delegateDel(exec invoke.Exec, ifName string, delegateConf *types.DelegateNe
return logging.Errorf("Multus: error in setting CNI_IFNAME")
}
if delegateConf.ConfListPlugin != false {
err := conflistDel(rt, delegateConf.Bytes, binDir)
if logging.GetLoggingLevel() >= logging.VerboseLevel {
var confName string
if delegateConf.ConfListPlugin {
confName = delegateConf.ConfList.Name
} else {
confName = delegateConf.Conf.Name
}
logging.Verbosef("Del: %s:%s:%s:%s %s", rt.Args[1][1], rt.Args[2][1], confName, rt.IfName, string(delegateConf.Bytes))
}
var err error
if delegateConf.ConfListPlugin {
err = conflistDel(rt, delegateConf.Bytes, binDir, exec)
if err != nil {
return logging.Errorf("Multus: error in invoke Conflist Del - %q: %v", delegateConf.ConfList.Name, err)
}
return err
} else {
origpath := os.Getenv("CNI_PATH")
os.Setenv("CNI_PATH", origpath+":"+binDir)
if err = invoke.DelegateDel(context.Background(), delegateConf.Conf.Type, delegateConf.Bytes, exec); err != nil {
os.Setenv("CNI_PATH", origpath)
return logging.Errorf("Multus: error in invoke Delegate del - %q: %v", delegateConf.Conf.Type, err)
}
os.Setenv("CNI_PATH", origpath)
}
if err := invoke.DelegateDel(delegateConf.Conf.Type, delegateConf.Bytes, exec); err != nil {
return logging.Errorf("Multus: error in invoke Delegate del - %q: %v", delegateConf.Conf.Type, err)
}
return nil
return err
}
func delPlugins(exec invoke.Exec, argIfname string, delegates []*types.DelegateNetConf, lastIdx int, rt *libcni.RuntimeConf, binDir string) error {
@ -204,20 +303,27 @@ func delPlugins(exec invoke.Exec, argIfname string, delegates []*types.DelegateN
return logging.Errorf("Multus: error in setting CNI_COMMAND to DEL")
}
var errorstrings []string
for idx := lastIdx; idx >= 0; idx-- {
ifName := getIfname(delegates[idx], argIfname, idx)
rt.IfName = ifName
// Attempt to delete all but do not error out, instead, collect all errors.
if err := delegateDel(exec, ifName, delegates[idx], rt, binDir); err != nil {
return err
errorstrings = append(errorstrings, err.Error())
}
}
// Check if we had any errors, and send them all back.
if len(errorstrings) > 0 {
return fmt.Errorf(strings.Join(errorstrings, " / "))
}
return nil
}
func cmdAdd(args *skel.CmdArgs, exec invoke.Exec, kubeClient k8s.KubeClient) (cnitypes.Result, error) {
logging.Debugf("cmdAdd: %v, %v, %v", args, exec, kubeClient)
n, err := types.LoadNetConf(args.StdinData)
logging.Debugf("cmdAdd: %v, %v, %v", args, exec, kubeClient)
if err != nil {
return nil, logging.Errorf("err in loading netconf: %v", err)
}
@ -227,29 +333,51 @@ func cmdAdd(args *skel.CmdArgs, exec invoke.Exec, kubeClient k8s.KubeClient) (cn
return nil, logging.Errorf("Multus: Err in getting k8s args: %v", err)
}
numK8sDelegates, kc, err := k8s.TryLoadK8sDelegates(k8sArgs, n, kubeClient)
wait.ExponentialBackoff(defaultReadinessBackoff, func() (bool, error) {
_, err := os.Stat(n.ReadinessIndicatorFile)
switch {
case err == nil:
return true, nil
default:
return false, nil
}
})
if n.ClusterNetwork != "" {
err = k8s.GetDefaultNetworks(k8sArgs, n, kubeClient)
if err != nil {
return nil, logging.Errorf("Multus: Failed to get clusterNetwork/defaultNetworks: %v", err)
}
// First delegate is always the master plugin
n.Delegates[0].MasterPlugin = true
}
_, kc, err := k8s.TryLoadPodDelegates(k8sArgs, n, kubeClient)
if err != nil {
return nil, logging.Errorf("Multus: Err in loading K8s Delegates k8s args: %v", err)
}
if numK8sDelegates == 0 {
// cache the multus config if we have only Multus delegates
if err := saveDelegates(args.ContainerID, n.CNIDir, n.Delegates); err != nil {
return nil, logging.Errorf("Multus: Err in saving the delegates: %v", err)
}
// cache the multus config
if err := saveDelegates(args.ContainerID, n.CNIDir, n.Delegates); err != nil {
return nil, logging.Errorf("Multus: Err in saving the delegates: %v", err)
}
var result, tmpResult cnitypes.Result
var netStatus []*types.NetworkStatus
var rt *libcni.RuntimeConf
lastIdx := 0
cniArgs := os.Getenv("CNI_ARGS")
for idx, delegate := range n.Delegates {
lastIdx = idx
ifName := getIfname(delegate, args.IfName, idx)
rt, _ = types.LoadCNIRuntimeConf(args, k8sArgs, ifName)
tmpResult, err = delegateAdd(exec, ifName, delegate, rt, n.BinDir)
rt := types.CreateCNIRuntimeConf(args, k8sArgs, ifName, n.RuntimeConfig)
tmpResult, err = delegateAdd(exec, ifName, delegate, rt, n.BinDir, cniArgs)
if err != nil {
break
// If the add failed, tear down all networks we already added
netName := delegate.Conf.Name
if netName == "" {
netName = delegate.ConfList.Name
}
// Ignore errors; DEL must be idempotent anyway
_ = delPlugins(exec, args.IfName, n.Delegates, idx, rt, n.BinDir)
return nil, logging.Errorf("Multus: Err adding pod to network %q: %v", netName, err)
}
// Master plugin result is always used if present
@ -259,10 +387,10 @@ func cmdAdd(args *skel.CmdArgs, exec invoke.Exec, kubeClient k8s.KubeClient) (cn
//create the network status, only in case Multus as kubeconfig
if n.Kubeconfig != "" && kc != nil {
if kc.Podnamespace != "kube-system" {
if !types.CheckSystemNamespaces(kc.Podnamespace, n.SystemNamespaces) {
delegateNetStatus, err := types.LoadNetworkStatus(tmpResult, delegate.Conf.Name, delegate.MasterPlugin)
if err != nil {
return nil, logging.Errorf("Multus: Err in setting networks status: %v", err)
return nil, logging.Errorf("Multus: Err in setting network status: %v", err)
}
netStatus = append(netStatus, delegateNetStatus)
@ -270,16 +398,10 @@ func cmdAdd(args *skel.CmdArgs, exec invoke.Exec, kubeClient k8s.KubeClient) (cn
}
}
if err != nil {
// Ignore errors; DEL must be idempotent anyway
_ = delPlugins(exec, args.IfName, n.Delegates, lastIdx, rt, n.BinDir)
return nil, logging.Errorf("Multus: Err in tearing down failed plugins: %v", err)
}
//set the network status annotation in apiserver, only in case Multus as kubeconfig
if n.Kubeconfig != "" && kc != nil {
if kc.Podnamespace != "kube-system" {
err = k8s.SetNetworkStatus(kc, netStatus)
if !types.CheckSystemNamespaces(kc.Podnamespace, n.SystemNamespaces) {
err = k8s.SetNetworkStatus(kubeClient, k8sArgs, netStatus, n)
if err != nil {
return nil, logging.Errorf("Multus: Err set the networks status: %v", err)
}
@ -308,63 +430,108 @@ func cmdDel(args *skel.CmdArgs, exec invoke.Exec, kubeClient k8s.KubeClient) err
return err
}
if args.Netns == "" {
return nil
}
netnsfound := true
netns, err := ns.GetNS(args.Netns)
if err != nil {
// if NetNs is passed down by the Cloud Orchestration Engine, or if it called multiple times
// if NetNs is passed down by the Cloud Orchestration Engine, or if it called multiple times
// so don't return an error if the device is already removed.
// https://github.com/kubernetes/kubernetes/issues/43014#issuecomment-287164444
_, ok := err.(ns.NSPathNotExistErr)
if ok {
return nil
netnsfound = false
logging.Debugf("cmdDel: WARNING netns may not exist, netns: %s, err: %s", args.Netns, err)
} else {
return fmt.Errorf("failed to open netns %q: %v", netns, err)
}
return fmt.Errorf("failed to open netns %q: %v", netns, err)
}
defer netns.Close()
if netns != nil {
defer netns.Close()
}
k8sArgs, err := k8s.GetK8sArgs(args)
if err != nil {
return logging.Errorf("Multus: Err in getting k8s args: %v", err)
}
numK8sDelegates, kc, err := k8s.TryLoadK8sDelegates(k8sArgs, in, kubeClient)
// Read the cache to get delegates json for the pod
netconfBytes, path, err := consumeScratchNetConf(args.ContainerID, in.CNIDir)
if err != nil {
return err
}
if numK8sDelegates == 0 {
// re-read the scratch multus config if we have only Multus delegates
netconfBytes, err := consumeScratchNetConf(args.ContainerID, in.CNIDir)
if err != nil {
if os.IsNotExist(err) {
// Per spec should ignore error if resources are missing / already removed
return nil
// Fetch delegates again if cache is not exist
if os.IsNotExist(err) {
if in.ClusterNetwork != "" {
err = k8s.GetDefaultNetworks(k8sArgs, in, kubeClient)
if err != nil {
return logging.Errorf("Multus: Failed to get clusterNetwork/defaultNetworks: %v", err)
}
// First delegate is always the master plugin
in.Delegates[0].MasterPlugin = true
}
return logging.Errorf("Multus: Err in reading the delegates: %v", err)
}
// Get pod annotation and so on
_, _, err := k8s.TryLoadPodDelegates(k8sArgs, in, kubeClient)
if err != nil {
if len(in.Delegates) == 0 {
// No delegate available so send error
return logging.Errorf("Multus: failed to get delegates: %v", err)
}
// Get clusterNetwork before, so continue to delete
logging.Errorf("Multus: failed to get delegates: %v, but continue to delete clusterNetwork", err)
}
} else {
return logging.Errorf("Multus: Err in reading the delegates: %v", err)
}
} else {
defer os.Remove(path)
if err := json.Unmarshal(netconfBytes, &in.Delegates); err != nil {
return logging.Errorf("Multus: failed to load netconf: %v", err)
}
// First delegate is always the master plugin
in.Delegates[0].MasterPlugin = true
}
//unset the network status annotation in apiserver, only in case Multus as kubeconfig
if in.Kubeconfig != "" && kc != nil {
if kc.Podnamespace != "kube-system" {
err := k8s.SetNetworkStatus(kc, nil)
if err != nil {
return logging.Errorf("Multus: Err unset the networks status: %v", err)
}
// set CNIVersion in delegate CNI config if there is no CNIVersion and multus conf have CNIVersion.
for _, v := range in.Delegates {
if v.ConfListPlugin == true && v.ConfList.CNIVersion == "" && in.CNIVersion != "" {
v.ConfList.CNIVersion = in.CNIVersion
v.Bytes, err = json.Marshal(v.ConfList)
}
}
rt, _ := types.LoadCNIRuntimeConf(args, k8sArgs, "")
// unset the network status annotation in apiserver, only in case Multus as kubeconfig
if in.Kubeconfig != "" {
if netnsfound {
if !types.CheckSystemNamespaces(string(k8sArgs.K8S_POD_NAMESPACE), in.SystemNamespaces) {
err := k8s.SetNetworkStatus(kubeClient, k8sArgs, nil, in)
if err != nil {
// error happen but continue to delete
logging.Errorf("Multus: Err unset the networks status: %v", err)
}
}
} else {
logging.Debugf("WARNING: Unset SetNetworkStatus skipped due to netns not found.")
}
}
rt := types.CreateCNIRuntimeConf(args, k8sArgs, "", in.RuntimeConfig)
return delPlugins(exec, args.IfName, in.Delegates, len(in.Delegates)-1, rt, in.BinDir)
}
func main() {
// Init command line flags to clear vendored packages' one, especially in init()
flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ExitOnError)
// add version flag
versionOpt := false
flag.BoolVar(&versionOpt, "version", false, "Show application version")
flag.BoolVar(&versionOpt, "v", false, "Show application version")
flag.Parse()
if versionOpt == true {
fmt.Printf("%s\n", printVersionString())
return
}
skel.PluginMain(
func(args *skel.CmdArgs) error {
result, err := cmdAdd(args, nil, nil)
@ -381,5 +548,5 @@ func main() {
return result.Print()
},
func(args *skel.CmdArgs) error { return cmdDel(args, nil, nil) },
version.All, "meta-plugin that delegates to other CNI plugins")
cniversion.All, "meta-plugin that delegates to other CNI plugins")
}

View File

@ -16,6 +16,7 @@
package main
import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
@ -28,7 +29,7 @@ import (
"github.com/containernetworking/cni/pkg/skel"
cnitypes "github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/cni/pkg/types/020"
"github.com/containernetworking/cni/pkg/version"
cniversion "github.com/containernetworking/cni/pkg/version"
"github.com/containernetworking/plugins/pkg/ns"
"github.com/containernetworking/plugins/pkg/testutils"
@ -52,7 +53,7 @@ type fakePlugin struct {
}
type fakeExec struct {
version.PluginDecoder
cniversion.PluginDecoder
addIndex int
delIndex int
@ -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 {
@ -123,8 +124,12 @@ func (f *fakeExec) ExecPlugin(pluginPath string, stdinData []byte, environ []str
if plugin.expectedIfname != "" {
Expect(os.Getenv("CNI_IFNAME")).To(Equal(plugin.expectedIfname))
}
if len(plugin.expectedEnv) > 0 {
matchArray(gatherCNIEnv(), plugin.expectedEnv)
cniEnv := gatherCNIEnv()
for _, expectedCniEnvVar := range plugin.expectedEnv {
Expect(cniEnv).Should(ContainElement(expectedCniEnvVar))
}
}
if plugin.err != nil {
@ -173,6 +178,8 @@ var _ = Describe("multus operations", func() {
StdinData: []byte(`{
"name": "node-cni-network",
"type": "multus",
"defaultnetworkfile": "/tmp/foo.multus.conf",
"defaultnetworkwaitseconds": 3,
"delegates": [{
"name": "weave1",
"cniVersion": "0.2.0",
@ -185,6 +192,10 @@ var _ = Describe("multus operations", func() {
}`),
}
// Touch the default network file.
configPath := "/tmp/foo.multus.conf"
os.OpenFile(configPath, os.O_RDONLY|os.O_CREATE, 0755)
fExec := &fakeExec{}
expectedResult1 := &types020.Result{
CNIVersion: "0.2.0",
@ -226,10 +237,152 @@ var _ = Describe("multus operations", func() {
err = cmdDel(args, fExec, nil)
Expect(err).NotTo(HaveOccurred())
Expect(fExec.delIndex).To(Equal(len(fExec.plugins)))
// Cleanup default network file.
if _, errStat := os.Stat(configPath); errStat == nil {
errRemove := os.Remove(configPath)
Expect(errRemove).NotTo(HaveOccurred())
}
})
It("executes delegates and cleans up on failure", func() {
expectedConf1 := `{
"name": "weave1",
"cniVersion": "0.2.0",
"type": "weave-net"
}`
expectedConf2 := `{
"name": "other1",
"cniVersion": "0.2.0",
"type": "other-plugin"
}`
args := &skel.CmdArgs{
ContainerID: "123456789",
Netns: testNS.Path(),
IfName: "eth0",
StdinData: []byte(fmt.Sprintf(`{
"name": "node-cni-network",
"type": "multus",
"defaultnetworkfile": "/tmp/foo.multus.conf",
"defaultnetworkwaitseconds": 3,
"delegates": [%s,%s]
}`, expectedConf1, expectedConf2)),
}
// Touch the default network file.
configPath := "/tmp/foo.multus.conf"
os.OpenFile(configPath, os.O_RDONLY|os.O_CREATE, 0755)
fExec := &fakeExec{}
expectedResult1 := &types020.Result{
CNIVersion: "0.2.0",
IP4: &types020.IPConfig{
IP: *testhelpers.EnsureCIDR("1.1.1.2/24"),
},
}
fExec.addPlugin(nil, "eth0", expectedConf1, expectedResult1, nil)
// This plugin invocation should fail
err := fmt.Errorf("expected plugin failure")
fExec.addPlugin(nil, "net1", expectedConf2, nil, err)
os.Setenv("CNI_COMMAND", "ADD")
os.Setenv("CNI_IFNAME", "eth0")
_, err = cmdAdd(args, fExec, nil)
Expect(fExec.addIndex).To(Equal(2))
Expect(fExec.delIndex).To(Equal(2))
Expect(err).To(MatchError("Multus: Err adding pod to network \"other1\": Multus: error in invoke Delegate add - \"other-plugin\": expected plugin failure"))
// Cleanup default network file.
if _, errStat := os.Stat(configPath); errStat == nil {
errRemove := os.Remove(configPath)
Expect(errRemove).NotTo(HaveOccurred())
}
})
It("executes delegates with interface name and MAC and IP addr", func() {
podNet := `[{"name":"net1",
"interface": "test1",
"ips":"1.2.3.4/24"},
{"name":"net2",
"mac": "c2:11:22:33:44:66",
"ips": "10.0.0.1"}
]`
fakePod := testhelpers.NewFakePod("testpod", podNet, "")
net1 := `{
"name": "net1",
"type": "mynet",
"cniVersion": "0.2.0"
}`
net2 := `{
"name": "net2",
"type": "mynet2",
"cniVersion": "0.2.0"
}`
args := &skel.CmdArgs{
ContainerID: "123456789",
Netns: testNS.Path(),
IfName: "eth0",
Args: fmt.Sprintf("K8S_POD_NAME=%s;K8S_POD_NAMESPACE=%s", fakePod.ObjectMeta.Name, fakePod.ObjectMeta.Namespace),
StdinData: []byte(`{
"name": "node-cni-network",
"type": "multus",
"kubeconfig": "/etc/kubernetes/node-kubeconfig.yaml",
"delegates": [{
"name": "weave1",
"cniVersion": "0.2.0",
"type": "weave-net"
}]
}`),
}
fExec := &fakeExec{}
expectedResult1 := &types020.Result{
CNIVersion: "0.2.0",
IP4: &types020.IPConfig{
IP: *testhelpers.EnsureCIDR("1.1.1.2/24"),
},
}
expectedConf1 := `{
"name": "weave1",
"cniVersion": "0.2.0",
"type": "weave-net"
}`
fExec.addPlugin(nil, "eth0", expectedConf1, expectedResult1, nil)
fExec.addPlugin([]string{"CNI_ARGS=IgnoreUnknown=true;IP=1.2.3.4/24"}, "test1", net1, &types020.Result{
CNIVersion: "0.2.0",
IP4: &types020.IPConfig{
IP: *testhelpers.EnsureCIDR("1.1.1.3/24"),
},
}, nil)
fExec.addPlugin([]string{"CNI_ARGS=IgnoreUnknown=true;MAC=c2:11:22:33:44:66;IP=10.0.0.1"}, "net2", net2, &types020.Result{
CNIVersion: "0.2.0",
IP4: &types020.IPConfig{
IP: *testhelpers.EnsureCIDR("1.1.1.4/24"),
},
}, nil)
fKubeClient := testhelpers.NewFakeKubeClient()
fKubeClient.AddPod(fakePod)
fKubeClient.AddNetConfig(fakePod.ObjectMeta.Namespace, "net1", net1)
fKubeClient.AddNetConfig(fakePod.ObjectMeta.Namespace, "net2", net2)
os.Setenv("CNI_COMMAND", "ADD")
os.Setenv("CNI_IFNAME", "eth0")
result, err := cmdAdd(args, fExec, fKubeClient)
Expect(err).NotTo(HaveOccurred())
Expect(fExec.addIndex).To(Equal(len(fExec.plugins)))
Expect(fKubeClient.PodCount).To(Equal(2))
Expect(fKubeClient.NetCount).To(Equal(2))
r := result.(*types020.Result)
// plugin 1 is the masterplugin
Expect(reflect.DeepEqual(r, expectedResult1)).To(BeTrue())
})
It("executes delegates and kubernetes networks", func() {
fakePod := testhelpers.NewFakePod("testpod", "net1,net2")
fakePod := testhelpers.NewFakePod("testpod", "net1,net2", "")
net1 := `{
"name": "net1",
"type": "mynet",
@ -306,4 +459,328 @@ var _ = Describe("multus operations", func() {
// plugin 1 is the masterplugin
Expect(reflect.DeepEqual(r, expectedResult1)).To(BeTrue())
})
It("executes kubernetes networks and delete it after pod removal", func() {
fakePod := testhelpers.NewFakePod("testpod", "net1", "")
net1 := `{
"name": "net1",
"type": "mynet",
"cniVersion": "0.2.0"
}`
args := &skel.CmdArgs{
ContainerID: "123456789",
Netns: testNS.Path(),
IfName: "eth0",
Args: fmt.Sprintf("K8S_POD_NAME=%s;K8S_POD_NAMESPACE=%s", fakePod.ObjectMeta.Name, fakePod.ObjectMeta.Namespace),
StdinData: []byte(`{
"name": "node-cni-network",
"type": "multus",
"kubeconfig": "/etc/kubernetes/node-kubeconfig.yaml",
"delegates": [{
"name": "weave1",
"cniVersion": "0.2.0",
"type": "weave-net"
}]
}`),
}
fExec := &fakeExec{}
expectedResult1 := &types020.Result{
CNIVersion: "0.2.0",
IP4: &types020.IPConfig{
IP: *testhelpers.EnsureCIDR("1.1.1.2/24"),
},
}
expectedConf1 := `{
"name": "weave1",
"cniVersion": "0.2.0",
"type": "weave-net"
}`
fExec.addPlugin(nil, "eth0", expectedConf1, expectedResult1, nil)
fExec.addPlugin(nil, "net1", net1, &types020.Result{
CNIVersion: "0.2.0",
IP4: &types020.IPConfig{
IP: *testhelpers.EnsureCIDR("1.1.1.3/24"),
},
}, nil)
fKubeClient := testhelpers.NewFakeKubeClient()
fKubeClient.AddPod(fakePod)
fKubeClient.AddNetConfig(fakePod.ObjectMeta.Namespace, "net1", net1)
os.Setenv("CNI_COMMAND", "ADD")
os.Setenv("CNI_IFNAME", "eth0")
result, err := cmdAdd(args, fExec, fKubeClient)
Expect(err).NotTo(HaveOccurred())
Expect(fExec.addIndex).To(Equal(len(fExec.plugins)))
Expect(fKubeClient.PodCount).To(Equal(2))
Expect(fKubeClient.NetCount).To(Equal(1))
r := result.(*types020.Result)
// plugin 1 is the masterplugin
Expect(reflect.DeepEqual(r, expectedResult1)).To(BeTrue())
os.Setenv("CNI_COMMAND", "DEL")
os.Setenv("CNI_IFNAME", "eth0")
// set fKubeClient to nil to emulate no pod info
fKubeClient.DeletePod(fakePod)
err = cmdDel(args, fExec, fKubeClient)
Expect(err).NotTo(HaveOccurred())
Expect(fExec.delIndex).To(Equal(len(fExec.plugins)))
})
It("ensure delegates get portmap runtime config", func() {
args := &skel.CmdArgs{
ContainerID: "123456789",
Netns: testNS.Path(),
IfName: "eth0",
StdinData: []byte(`{
"name": "node-cni-network",
"type": "multus",
"delegates": [{
"cniVersion": "0.3.1",
"name": "mynet-confList",
"plugins": [
{
"type": "firstPlugin",
"capabilities": {"portMappings": true}
}
]
}],
"runtimeConfig": {
"portMappings": [
{"hostPort": 8080, "containerPort": 80, "protocol": "tcp"}
]
}
}`),
}
fExec := &fakeExec{}
expectedConf1 := `{
"capabilities": {"portMappings": true},
"name": "mynet-confList",
"cniVersion": "0.3.1",
"type": "firstPlugin",
"runtimeConfig": {
"portMappings": [
{"hostPort": 8080, "containerPort": 80, "protocol": "tcp"}
]
}
}`
fExec.addPlugin(nil, "eth0", expectedConf1, nil, nil)
os.Setenv("CNI_COMMAND", "ADD")
os.Setenv("CNI_IFNAME", "eth0")
_, err := cmdAdd(args, fExec, nil)
Expect(err).NotTo(HaveOccurred())
})
It("executes clusterNetwork delegate", func() {
fakePod := testhelpers.NewFakePod("testpod", "", "kube-system/net1")
net1 := `{
"name": "net1",
"type": "mynet",
"cniVersion": "0.2.0"
}`
expectedResult1 := &types020.Result{
CNIVersion: "0.2.0",
IP4: &types020.IPConfig{
IP: *testhelpers.EnsureCIDR("1.1.1.2/24"),
},
}
args := &skel.CmdArgs{
ContainerID: "123456789",
Netns: testNS.Path(),
IfName: "eth0",
Args: fmt.Sprintf("K8S_POD_NAME=%s;K8S_POD_NAMESPACE=%s", fakePod.ObjectMeta.Name, fakePod.ObjectMeta.Namespace),
StdinData: []byte(`{
"name": "node-cni-network",
"type": "multus",
"kubeconfig": "/etc/kubernetes/node-kubeconfig.yaml",
"defaultNetworks": [],
"clusterNetwork": "net1",
"delegates": []
}`),
}
fExec := &fakeExec{}
fExec.addPlugin(nil, "eth0", net1, expectedResult1, nil)
fKubeClient := testhelpers.NewFakeKubeClient()
fKubeClient.AddPod(fakePod)
fKubeClient.AddNetConfig("kube-system", "net1", net1)
os.Setenv("CNI_COMMAND", "ADD")
os.Setenv("CNI_IFNAME", "eth0")
result, err := cmdAdd(args, fExec, fKubeClient)
Expect(err).NotTo(HaveOccurred())
Expect(fExec.addIndex).To(Equal(len(fExec.plugins)))
Expect(fKubeClient.PodCount).To(Equal(2))
Expect(fKubeClient.NetCount).To(Equal(2))
r := result.(*types020.Result)
Expect(reflect.DeepEqual(r, expectedResult1)).To(BeTrue())
os.Setenv("CNI_COMMAND", "DEL")
os.Setenv("CNI_IFNAME", "eth0")
err = cmdDel(args, fExec, fKubeClient)
Expect(err).NotTo(HaveOccurred())
Expect(fExec.delIndex).To(Equal(len(fExec.plugins)))
})
It("Verify the cache is created in dataDir", func() {
tmpCNIDir := tmpDir + "/cniData"
err := os.Mkdir(tmpCNIDir, 0777)
Expect(err).NotTo(HaveOccurred())
fakePod := testhelpers.NewFakePod("testpod", "net1", "")
net1 := `{
"name": "net1",
"type": "mynet",
"cniVersion": "0.2.0"
}`
args := &skel.CmdArgs{
ContainerID: "123456789",
Netns: testNS.Path(),
IfName: "eth0",
Args: fmt.Sprintf("K8S_POD_NAME=%s;K8S_POD_NAMESPACE=%s", fakePod.ObjectMeta.Name, fakePod.ObjectMeta.Namespace),
StdinData: []byte(fmt.Sprintf(`{
"name": "node-cni-network",
"type": "multus",
"kubeconfig": "/etc/kubernetes/node-kubeconfig.yaml",
"cniDir": "%s",
"delegates": [{
"name": "weave1",
"cniVersion": "0.2.0",
"type": "weave-net"
}]
}`, tmpCNIDir)),
}
fExec := &fakeExec{}
expectedResult1 := &types020.Result{
CNIVersion: "0.2.0",
IP4: &types020.IPConfig{
IP: *testhelpers.EnsureCIDR("1.1.1.2/24"),
},
}
expectedConf1 := `{
"name": "weave1",
"cniVersion": "0.2.0",
"type": "weave-net"
}`
fExec.addPlugin(nil, "eth0", expectedConf1, expectedResult1, nil)
fExec.addPlugin(nil, "net1", net1, &types020.Result{
CNIVersion: "0.2.0",
IP4: &types020.IPConfig{
IP: *testhelpers.EnsureCIDR("1.1.1.3/24"),
},
}, nil)
fKubeClient := testhelpers.NewFakeKubeClient()
fKubeClient.AddPod(fakePod)
fKubeClient.AddNetConfig(fakePod.ObjectMeta.Namespace, "net1", net1)
os.Setenv("CNI_COMMAND", "ADD")
os.Setenv("CNI_IFNAME", "eth0")
result, err := cmdAdd(args, fExec, fKubeClient)
Expect(err).NotTo(HaveOccurred())
Expect(fExec.addIndex).To(Equal(len(fExec.plugins)))
Expect(fKubeClient.PodCount).To(Equal(2))
Expect(fKubeClient.NetCount).To(Equal(1))
r := result.(*types020.Result)
// plugin 1 is the masterplugin
Expect(reflect.DeepEqual(r, expectedResult1)).To(BeTrue())
By("Verify cache file existence")
cacheFilePath := fmt.Sprintf("%s/%s", tmpCNIDir, "123456789")
_, err = os.Stat(cacheFilePath)
Expect(err).NotTo(HaveOccurred())
By("Delete and check net count is not incremented")
os.Setenv("CNI_COMMAND", "DEL")
os.Setenv("CNI_IFNAME", "eth0")
err = cmdDel(args, fExec, fKubeClient)
Expect(err).NotTo(HaveOccurred())
Expect(fExec.delIndex).To(Equal(len(fExec.plugins)))
Expect(fKubeClient.PodCount).To(Equal(3))
Expect(fKubeClient.NetCount).To(Equal(1))
})
It("Delete pod without cache", func() {
tmpCNIDir := tmpDir + "/cniData"
err := os.Mkdir(tmpCNIDir, 0777)
Expect(err).NotTo(HaveOccurred())
fakePod := testhelpers.NewFakePod("testpod", "net1", "")
net1 := `{
"name": "net1",
"type": "mynet",
"cniVersion": "0.2.0"
}`
args := &skel.CmdArgs{
ContainerID: "123456789",
Netns: testNS.Path(),
IfName: "eth0",
Args: fmt.Sprintf("K8S_POD_NAME=%s;K8S_POD_NAMESPACE=%s", fakePod.ObjectMeta.Name, fakePod.ObjectMeta.Namespace),
StdinData: []byte(fmt.Sprintf(`{
"name": "node-cni-network",
"type": "multus",
"kubeconfig": "/etc/kubernetes/node-kubeconfig.yaml",
"cniDir": "%s",
"delegates": [{
"name": "weave1",
"cniVersion": "0.2.0",
"type": "weave-net"
}]
}`, tmpCNIDir)),
}
fExec := &fakeExec{}
expectedResult1 := &types020.Result{
CNIVersion: "0.2.0",
IP4: &types020.IPConfig{
IP: *testhelpers.EnsureCIDR("1.1.1.2/24"),
},
}
expectedConf1 := `{
"name": "weave1",
"cniVersion": "0.2.0",
"type": "weave-net"
}`
fExec.addPlugin(nil, "eth0", expectedConf1, expectedResult1, nil)
fExec.addPlugin(nil, "net1", net1, &types020.Result{
CNIVersion: "0.2.0",
IP4: &types020.IPConfig{
IP: *testhelpers.EnsureCIDR("1.1.1.3/24"),
},
}, nil)
fKubeClient := testhelpers.NewFakeKubeClient()
fKubeClient.AddPod(fakePod)
fKubeClient.AddNetConfig(fakePod.ObjectMeta.Namespace, "net1", net1)
os.Setenv("CNI_COMMAND", "ADD")
os.Setenv("CNI_IFNAME", "eth0")
result, err := cmdAdd(args, fExec, fKubeClient)
Expect(err).NotTo(HaveOccurred())
Expect(fExec.addIndex).To(Equal(len(fExec.plugins)))
Expect(fKubeClient.PodCount).To(Equal(2))
Expect(fKubeClient.NetCount).To(Equal(1))
r := result.(*types020.Result)
// plugin 1 is the masterplugin
Expect(reflect.DeepEqual(r, expectedResult1)).To(BeTrue())
By("Verify cache file existence")
cacheFilePath := fmt.Sprintf("%s/%s", tmpCNIDir, "123456789")
_, err = os.Stat(cacheFilePath)
Expect(err).NotTo(HaveOccurred())
err = os.Remove(cacheFilePath)
Expect(err).NotTo(HaveOccurred())
By("Delete and check pod/net count is incremented")
os.Setenv("CNI_COMMAND", "DEL")
os.Setenv("CNI_IFNAME", "eth0")
err = cmdDel(args, fExec, fKubeClient)
Expect(err).NotTo(HaveOccurred())
Expect(fExec.delIndex).To(Equal(len(fExec.plugins)))
Expect(fKubeClient.PodCount).To(Equal(4))
Expect(fKubeClient.NetCount).To(Equal(2))
})
})

30
test.sh
View File

@ -1,17 +1,23 @@
#!/usr/bin/env bash
set -e
ORG_PATH="github.com/intel"
REPO_PATH="${ORG_PATH}/multus-cni"
# this if... will be removed when gomodules goes default
if [ "$GO111MODULE" == "off" ]; then
echo "Warning: this will be deprecated in near future so please use go modules!"
if [ ! -h gopath/src/${REPO_PATH} ]; then
mkdir -p gopath/src/${ORG_PATH}
ln -s ../../../.. gopath/src/${REPO_PATH} || exit 255
ORG_PATH="github.com/intel"
REPO_PATH="${ORG_PATH}/multus-cni"
if [ ! -h gopath/src/${REPO_PATH} ]; then
mkdir -p gopath/src/${ORG_PATH}
ln -s ../../../.. gopath/src/${REPO_PATH} || exit 255
fi
export GO15VENDOREXPERIMENT=1
export GOBIN=${PWD}/bin
export GOPATH=${PWD}/gopath
bash -c "umask 0; cd ${GOPATH}/src/${REPO_PATH}; PATH=${GOROOT}/bin:$(pwd)/bin:${PATH} go test -v -covermode=count -coverprofile=coverage.out ./..."
else
# test with go modules
bash -c "umask 0; go test -v -covermode=count -coverprofile=coverage.out ./..."
fi
export GO15VENDOREXPERIMENT=1
export GOBIN=${PWD}/bin
export GOPATH=${PWD}/gopath
bash -c "umask 0; cd ${GOPATH}/src/${REPO_PATH}; PATH=${GOROOT}/bin:$(pwd)/bin:${PATH} go test -v -covermode=count -coverprofile=coverage.out ./..."

View File

@ -103,7 +103,12 @@ func (f *FakeKubeClient) AddPod(pod *v1.Pod) {
f.pods[key] = pod
}
func NewFakePod(name string, netAnnotation string) *v1.Pod {
func (f *FakeKubeClient) DeletePod(pod *v1.Pod) {
key := fmt.Sprintf("%s/%s", pod.ObjectMeta.Namespace, pod.ObjectMeta.Name)
delete(f.pods, key)
}
func NewFakePod(name string, netAnnotation string, defaultNetAnnotation string) *v1.Pod {
pod := &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: name,
@ -115,13 +120,19 @@ func NewFakePod(name string, netAnnotation string) *v1.Pod {
},
},
}
annotations := make(map[string]string)
if netAnnotation != "" {
netAnnotation = strings.Replace(netAnnotation, "\n", "", -1)
netAnnotation = strings.Replace(netAnnotation, "\t", "", -1)
pod.ObjectMeta.Annotations = map[string]string{
"k8s.v1.cni.cncf.io/networks": netAnnotation,
}
annotations["k8s.v1.cni.cncf.io/networks"] = netAnnotation
}
if defaultNetAnnotation != "" {
annotations["v1.multus-cni.io/default-network"] = defaultNetAnnotation
}
pod.ObjectMeta.Annotations = annotations
return pod
}

View File

@ -27,9 +27,11 @@ import (
)
const (
defaultCNIDir = "/var/lib/cni/multus"
defaultConfDir = "/etc/cni/multus/net.d"
defaultBinDir = "/opt/cni/bin"
defaultCNIDir = "/var/lib/cni/multus"
defaultConfDir = "/etc/cni/multus/net.d"
defaultBinDir = "/opt/cni/bin"
defaultReadinessIndicatorFile = ""
defaultMultusNamespace = "kube-system"
)
func LoadDelegateNetConfList(bytes []byte, delegateConf *DelegateNetConf) error {
@ -50,9 +52,11 @@ func LoadDelegateNetConfList(bytes []byte, delegateConf *DelegateNetConf) error
}
// Convert raw CNI JSON into a DelegateNetConf structure
func LoadDelegateNetConf(bytes []byte, ifnameRequest string) (*DelegateNetConf, error) {
func LoadDelegateNetConf(bytes []byte, net *NetworkSelectionElement, deviceID string) (*DelegateNetConf, error) {
var err error
logging.Debugf("LoadDelegateNetConf: %s, %v, %s", string(bytes), net, deviceID)
delegateConf := &DelegateNetConf{}
logging.Debugf("LoadDelegateNetConf: %s, %s", string(bytes), ifnameRequest)
if err := json.Unmarshal(bytes, &delegateConf.Conf); err != nil {
return nil, logging.Errorf("error in LoadDelegateNetConf - unmarshalling delegate config: %v", err)
}
@ -62,10 +66,31 @@ func LoadDelegateNetConf(bytes []byte, ifnameRequest string) (*DelegateNetConf,
if err := LoadDelegateNetConfList(bytes, delegateConf); err != nil {
return nil, logging.Errorf("error in LoadDelegateNetConf: %v", err)
}
if deviceID != "" {
bytes, err = addDeviceIDInConfList(bytes, deviceID)
if err != nil {
return nil, logging.Errorf("LoadDelegateNetConf(): failed to add deviceID in NetConfList bytes: %v", err)
}
}
} else {
if deviceID != "" {
bytes, err = delegateAddDeviceID(bytes, deviceID)
if err != nil {
return nil, logging.Errorf("LoadDelegateNetConf(): failed to add deviceID in NetConf bytes: %v", err)
}
}
}
if ifnameRequest != "" {
delegateConf.IfnameRequest = ifnameRequest
if net != nil {
if net.InterfaceRequest != "" {
delegateConf.IfnameRequest = net.InterfaceRequest
}
if net.MacRequest != "" {
delegateConf.MacRequest = net.MacRequest
}
if net.IPRequest != "" {
delegateConf.IPRequest = net.IPRequest
}
}
delegateConf.Bytes = bytes
@ -73,9 +98,9 @@ func LoadDelegateNetConf(bytes []byte, ifnameRequest string) (*DelegateNetConf,
return delegateConf, nil
}
func LoadCNIRuntimeConf(args *skel.CmdArgs, k8sArgs *K8sArgs, ifName string) (*libcni.RuntimeConf, error) {
func CreateCNIRuntimeConf(args *skel.CmdArgs, k8sArgs *K8sArgs, ifName string, rc *RuntimeConfig) *libcni.RuntimeConf {
logging.Debugf("LoadCNIRuntimeConf: %v, %v, %s", args, k8sArgs, ifName)
logging.Debugf("LoadCNIRuntimeConf: %v, %v, %s, %v", args, k8sArgs, ifName, rc)
// In part, adapted from K8s pkg/kubelet/dockershim/network/cni/cni.go#buildCNIRuntimeConf
// Todo
// ingress, egress and bandwidth capability features as same as kubelet.
@ -90,11 +115,17 @@ func LoadCNIRuntimeConf(args *skel.CmdArgs, k8sArgs *K8sArgs, ifName string) (*l
{"K8S_POD_INFRA_CONTAINER_ID", string(k8sArgs.K8S_POD_INFRA_CONTAINER_ID)},
},
}
return rt, nil
if rc != nil {
rt.CapabilityArgs = map[string]interface{}{
"portMappings": rc.PortMaps,
}
}
return rt
}
func LoadNetworkStatus(r types.Result, netName string, defaultNet bool) (*NetworkStatus, error) {
logging.Debugf("LoadNetworkStatus: %v, %s, %s", r, netName, defaultNet)
logging.Debugf("LoadNetworkStatus: %v, %s, %t", r, netName, defaultNet)
// Convert whatever the IPAM result was into the current Result type
result, err := current.NewResultFromResult(r)
@ -169,8 +200,8 @@ func LoadNetConf(bytes []byte) (*NetConf, error) {
// the master plugin. Kubernetes CRD delegates are then appended to
// the existing delegate list and all delegates executed in-order.
if len(netconf.RawDelegates) == 0 {
return nil, logging.Errorf("at least one delegate must be specified")
if len(netconf.RawDelegates) == 0 && netconf.ClusterNetwork == "" {
return nil, logging.Errorf("at least one delegate/defaultNetwork must be specified")
}
if netconf.CNIDir == "" {
@ -185,21 +216,40 @@ func LoadNetConf(bytes []byte) (*NetConf, error) {
netconf.BinDir = defaultBinDir
}
for idx, rawConf := range netconf.RawDelegates {
bytes, err := json.Marshal(rawConf)
if err != nil {
return nil, logging.Errorf("error marshalling delegate %d config: %v", idx, err)
}
delegateConf, err := LoadDelegateNetConf(bytes, "")
if err != nil {
return nil, logging.Errorf("failed to load delegate %d config: %v", idx, err)
}
netconf.Delegates = append(netconf.Delegates, delegateConf)
if netconf.ReadinessIndicatorFile == "" {
netconf.ReadinessIndicatorFile = defaultReadinessIndicatorFile
}
netconf.RawDelegates = nil
// First delegate is always the master plugin
netconf.Delegates[0].MasterPlugin = true
if len(netconf.SystemNamespaces) == 0 {
netconf.SystemNamespaces = []string{"kube-system"}
}
if netconf.MultusNamespace == "" {
netconf.MultusNamespace = defaultMultusNamespace
}
// get RawDelegates and put delegates field
if netconf.ClusterNetwork == "" {
// for Delegates
if len(netconf.RawDelegates) == 0 {
return nil, logging.Errorf("at least one delegate must be specified")
}
for idx, rawConf := range netconf.RawDelegates {
bytes, err := json.Marshal(rawConf)
if err != nil {
return nil, logging.Errorf("error marshalling delegate %d config: %v", idx, err)
}
delegateConf, err := LoadDelegateNetConf(bytes, nil, "")
if err != nil {
return nil, logging.Errorf("failed to load delegate %d config: %v", idx, err)
}
netconf.Delegates = append(netconf.Delegates, delegateConf)
}
netconf.RawDelegates = nil
// First delegate is always the master plugin
netconf.Delegates[0].MasterPlugin = true
}
return netconf, nil
}
@ -210,3 +260,69 @@ func (n *NetConf) AddDelegates(newDelegates []*DelegateNetConf) error {
n.Delegates = append(n.Delegates, newDelegates...)
return nil
}
// delegateAddDeviceID injects deviceID information in delegate bytes
func delegateAddDeviceID(inBytes []byte, deviceID string) ([]byte, error) {
var rawConfig map[string]interface{}
var err error
err = json.Unmarshal(inBytes, &rawConfig)
if err != nil {
return nil, logging.Errorf("delegateAddDeviceID: failed to unmarshal inBytes: %v", err)
}
// Inject deviceID
rawConfig["deviceID"] = deviceID
rawConfig["pciBusID"] = deviceID
configBytes, err := json.Marshal(rawConfig)
if err != nil {
return nil, logging.Errorf("delegateAddDeviceID: failed to re-marshal Spec.Config: %v", err)
}
logging.Debugf("delegateAddDeviceID(): updated configBytes %s", string(configBytes))
return configBytes, nil
}
// addDeviceIDInConfList injects deviceID information in delegate bytes
func addDeviceIDInConfList(inBytes []byte, deviceID string) ([]byte, error) {
var rawConfig map[string]interface{}
var err error
err = json.Unmarshal(inBytes, &rawConfig)
if err != nil {
return nil, logging.Errorf("addDeviceIDInConfList(): failed to unmarshal inBytes: %v", err)
}
pList, ok := rawConfig["plugins"]
if !ok {
return nil, logging.Errorf("addDeviceIDInConfList(): unable to get plugin list")
}
pMap, ok := pList.([]interface{})
if !ok {
return nil, logging.Errorf("addDeviceIDInConfList(): unable to typecast plugin list")
}
firstPlugin, ok := pMap[0].(map[string]interface{})
if !ok {
return nil, logging.Errorf("addDeviceIDInConfList(): unable to typecast pMap")
}
// Inject deviceID
firstPlugin["deviceID"] = deviceID
firstPlugin["pciBusID"] = deviceID
configBytes, err := json.Marshal(rawConfig)
if err != nil {
return nil, logging.Errorf("addDeviceIDInConfList(): failed to re-marshal: %v", err)
}
logging.Debugf("addDeviceIDInConfList(): updated configBytes %s", string(configBytes))
return configBytes, nil
}
// CheckSystemNamespaces checks whether given namespace is in systemNamespaces or not.
func CheckSystemNamespaces(namespace string, systemNamespaces []string) bool {
for _, nsname := range systemNamespaces {
if namespace == nsname {
return true
}
}
return false
}

View File

@ -16,6 +16,7 @@
package types
import (
"encoding/json"
"testing"
. "github.com/onsi/ginkgo"
@ -35,13 +36,20 @@ var _ = Describe("config operations", func() {
"kubeconfig": "/etc/kubernetes/node-kubeconfig.yaml",
"delegates": [{
"type": "weave-net"
}]
}],
"runtimeConfig": {
"portMappings": [
{"hostPort": 8080, "containerPort": 80, "protocol": "tcp"}
]
}
}`
netConf, err := LoadNetConf([]byte(conf))
Expect(err).NotTo(HaveOccurred())
Expect(len(netConf.Delegates)).To(Equal(1))
Expect(netConf.Delegates[0].Conf.Type).To(Equal("weave-net"))
Expect(netConf.Delegates[0].MasterPlugin).To(BeTrue())
Expect(len(netConf.RuntimeConfig.PortMaps)).To(Equal(1))
})
It("succeeds if only delegates are set", func() {
@ -81,4 +89,137 @@ var _ = Describe("config operations", func() {
_, err := LoadNetConf([]byte(conf))
Expect(err).To(HaveOccurred())
})
It("has defaults set for network readiness", func() {
conf := `{
"name": "defaultnetwork",
"type": "multus",
"kubeconfig": "/etc/kubernetes/kubelet.conf",
"delegates": [{
"cniVersion": "0.3.0",
"name": "defaultnetwork",
"type": "flannel",
"isDefaultGateway": true
}]
}`
netConf, err := LoadNetConf([]byte(conf))
Expect(err).NotTo(HaveOccurred())
Expect(netConf.ReadinessIndicatorFile).To(Equal(""))
})
It("honors overrides for network readiness", func() {
conf := `{
"name": "defaultnetwork",
"type": "multus",
"readinessindicatorfile": "/etc/cni/net.d/foo",
"kubeconfig": "/etc/kubernetes/kubelet.conf",
"delegates": [{
"cniVersion": "0.3.0",
"name": "defaultnetwork",
"type": "flannel",
"isDefaultGateway": true
}]
}`
netConf, err := LoadNetConf([]byte(conf))
Expect(err).NotTo(HaveOccurred())
Expect(netConf.ReadinessIndicatorFile).To(Equal("/etc/cni/net.d/foo"))
})
It("check CheckSystemNamespaces() works fine", func() {
b1 := CheckSystemNamespaces("foobar", []string{"barfoo", "bafoo", "foobar"})
Expect(b1).To(Equal(true))
b2 := CheckSystemNamespaces("foobar1", []string{"barfoo", "bafoo", "foobar"})
Expect(b2).To(Equal(false))
})
It("assigns deviceID in delegated conf", func() {
conf := `{
"name": "second-network",
"type": "sriov"
}`
type sriovNetConf struct {
Name string `json:"name"`
Type string `json:"type"`
DeviceID string `json:"deviceID"`
}
sriovConf := &sriovNetConf{}
delegateNetConf, err := LoadDelegateNetConf([]byte(conf), nil, "0000:00:00.0")
Expect(err).NotTo(HaveOccurred())
err = json.Unmarshal(delegateNetConf.Bytes, &sriovConf)
Expect(err).NotTo(HaveOccurred())
Expect(sriovConf.DeviceID).To(Equal("0000:00:00.0"))
})
It("assigns deviceID in delegated conf list", func() {
conf := `{
"name": "second-network",
"plugins": [
{
"type": "sriov"
}
]
}`
type sriovNetConf struct {
Name string `json:"name"`
Type string `json:"type"`
DeviceID string `json:"deviceID"`
}
type sriovNetConfList struct {
Plugins []*sriovNetConf `json:"plugins"`
}
sriovConfList := &sriovNetConfList{}
delegateNetConf, err := LoadDelegateNetConf([]byte(conf), nil, "0000:00:00.1")
Expect(err).NotTo(HaveOccurred())
err = json.Unmarshal(delegateNetConf.Bytes, &sriovConfList)
Expect(err).NotTo(HaveOccurred())
Expect(sriovConfList.Plugins[0].DeviceID).To(Equal("0000:00:00.1"))
})
It("assigns pciBusID in delegated conf", func() {
conf := `{
"name": "second-network",
"type": "host-device"
}`
type hostDeviceNetConf struct {
Name string `json:"name"`
Type string `json:"type"`
PCIBusID string `json:"pciBusID"`
}
hostDeviceConf := &hostDeviceNetConf{}
delegateNetConf, err := LoadDelegateNetConf([]byte(conf), nil, "0000:00:00.2")
Expect(err).NotTo(HaveOccurred())
err = json.Unmarshal(delegateNetConf.Bytes, &hostDeviceConf)
Expect(err).NotTo(HaveOccurred())
Expect(hostDeviceConf.PCIBusID).To(Equal("0000:00:00.2"))
})
It("assigns pciBusID in delegated conf list", func() {
conf := `{
"name": "second-network",
"plugins": [
{
"type": "host-device"
}
]
}`
type hostDeviceNetConf struct {
Name string `json:"name"`
Type string `json:"type"`
PCIBusID string `json:"pciBusID"`
}
type hostDeviceNetConfList struct {
Plugins []*hostDeviceNetConf `json:"plugins"`
}
hostDeviceConfList := &hostDeviceNetConfList{}
delegateNetConf, err := LoadDelegateNetConf([]byte(conf), nil, "0000:00:00.3")
Expect(err).NotTo(HaveOccurred())
err = json.Unmarshal(delegateNetConf.Bytes, &hostDeviceConfList)
Expect(err).NotTo(HaveOccurred())
Expect(hostDeviceConfList.Plugins[0].PCIBusID).To(Equal("0000:00:00.3"))
})
})

View File

@ -20,6 +20,7 @@ import (
"github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/cni/pkg/types/current"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
@ -36,12 +37,34 @@ type NetConf struct {
CNIDir string `json:"cniDir"`
BinDir string `json:"binDir"`
// RawDelegates is private to the NetConf class; use Delegates instead
RawDelegates []map[string]interface{} `json:"delegates"`
Delegates []*DelegateNetConf `json:"-"`
NetStatus []*NetworkStatus `json:"-"`
Kubeconfig string `json:"kubeconfig"`
LogFile string `json:"logFile"`
LogLevel string `json:"logLevel"`
RawDelegates []map[string]interface{} `json:"delegates"`
Delegates []*DelegateNetConf `json:"-"`
NetStatus []*NetworkStatus `json:"-"`
Kubeconfig string `json:"kubeconfig"`
ClusterNetwork string `json:"clusterNetwork"`
DefaultNetworks []string `json:"defaultNetworks"`
LogFile string `json:"logFile"`
LogLevel string `json:"logLevel"`
RuntimeConfig *RuntimeConfig `json:"runtimeConfig,omitempty"`
// Default network readiness options
ReadinessIndicatorFile string `json:"readinessindicatorfile"`
// Option to isolate the usage of CR's to the namespace in which a pod resides.
NamespaceIsolation bool `json:"namespaceIsolation"`
// Option to set system namespaces (to avoid to add defaultNetworks)
SystemNamespaces []string `json:"systemNamespaces"`
// Option to set the namespace that multus-cni uses (clusterNetwork/defaultNetworks)
MultusNamespace string `json:"multusNamespace"`
}
type RuntimeConfig struct {
PortMaps []PortMapEntry `json:"portMappings,omitempty"`
}
type PortMapEntry struct {
HostPort int `json:"hostPort"`
ContainerPort int `json:"containerPort"`
Protocol string `json:"protocol"`
HostIP string `json:"hostIP,omitempty"`
}
type NetworkStatus struct {
@ -57,6 +80,8 @@ type DelegateNetConf struct {
Conf types.NetConf
ConfList types.NetConfList
IfnameRequest string `json:"ifnameRequest,omitempty"`
MacRequest string `json:"macRequest,omitempty"`
IPRequest string `json:"ipRequest,omitempty"`
// MasterPlugin is only used internal housekeeping
MasterPlugin bool `json:"-"`
// Conflist plugin is only used internal housekeeping
@ -102,13 +127,13 @@ type NetworkSelectionElement struct {
Namespace string `json:"namespace,omitempty"`
// IPRequest contains an optional requested IP address for this network
// attachment
IPRequest string `json:"ipRequest,omitempty"`
IPRequest string `json:"ips,omitempty"`
// MacRequest contains an optional requested MAC address for this
// network attachment
MacRequest string `json:"macRequest,omitempty"`
MacRequest string `json:"mac,omitempty"`
// InterfaceRequest contains an optional requested name for the
// network interface this attachment will create in the container
InterfaceRequest string `json:"interfaceRequest,omitempty"`
InterfaceRequest string `json:"interface,omitempty"`
}
// K8sArgs is the valid CNI_ARGS used for Kubernetes
@ -119,3 +144,15 @@ type K8sArgs struct {
K8S_POD_NAMESPACE types.UnmarshallableString
K8S_POD_INFRA_CONTAINER_ID types.UnmarshallableString
}
// ResourceInfo is struct to hold Pod device allocation information
type ResourceInfo struct {
Index int
DeviceIDs []string
}
// ResourceClient provides a kubelet Pod resource handle
type ResourceClient interface {
// GetPodResourceMap returns an instance of a map of Pod ResourceInfo given a (Pod name, namespace) tuple
GetPodResourceMap(*v1.Pod) (map[string]*ResourceInfo, error)
}

1
vendor/github.com/Microsoft/go-winio/.gitignore generated vendored Normal file
View File

@ -0,0 +1 @@
*.exe

22
vendor/github.com/Microsoft/go-winio/LICENSE generated vendored Normal file
View File

@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2015 Microsoft
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

22
vendor/github.com/Microsoft/go-winio/README.md generated vendored Normal file
View File

@ -0,0 +1,22 @@
# go-winio
This repository contains utilities for efficiently performing Win32 IO operations in
Go. Currently, this is focused on accessing named pipes and other file handles, and
for using named pipes as a net transport.
This code relies on IO completion ports to avoid blocking IO on system threads, allowing Go
to reuse the thread to schedule another goroutine. This limits support to Windows Vista and
newer operating systems. This is similar to the implementation of network sockets in Go's net
package.
Please see the LICENSE file for licensing information.
This project has adopted the [Microsoft Open Source Code of
Conduct](https://opensource.microsoft.com/codeofconduct/). For more information
see the [Code of Conduct
FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact
[opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional
questions or comments.
Thanks to natefinch for the inspiration for this library. See https://github.com/natefinch/npipe
for another named pipe implementation.

280
vendor/github.com/Microsoft/go-winio/backup.go generated vendored Normal file
View File

@ -0,0 +1,280 @@
// +build windows
package winio
import (
"encoding/binary"
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"runtime"
"syscall"
"unicode/utf16"
)
//sys backupRead(h syscall.Handle, b []byte, bytesRead *uint32, abort bool, processSecurity bool, context *uintptr) (err error) = BackupRead
//sys backupWrite(h syscall.Handle, b []byte, bytesWritten *uint32, abort bool, processSecurity bool, context *uintptr) (err error) = BackupWrite
const (
BackupData = uint32(iota + 1)
BackupEaData
BackupSecurity
BackupAlternateData
BackupLink
BackupPropertyData
BackupObjectId
BackupReparseData
BackupSparseBlock
BackupTxfsData
)
const (
StreamSparseAttributes = uint32(8)
)
const (
WRITE_DAC = 0x40000
WRITE_OWNER = 0x80000
ACCESS_SYSTEM_SECURITY = 0x1000000
)
// BackupHeader represents a backup stream of a file.
type BackupHeader struct {
Id uint32 // The backup stream ID
Attributes uint32 // Stream attributes
Size int64 // The size of the stream in bytes
Name string // The name of the stream (for BackupAlternateData only).
Offset int64 // The offset of the stream in the file (for BackupSparseBlock only).
}
type win32StreamId struct {
StreamId uint32
Attributes uint32
Size uint64
NameSize uint32
}
// BackupStreamReader reads from a stream produced by the BackupRead Win32 API and produces a series
// of BackupHeader values.
type BackupStreamReader struct {
r io.Reader
bytesLeft int64
}
// NewBackupStreamReader produces a BackupStreamReader from any io.Reader.
func NewBackupStreamReader(r io.Reader) *BackupStreamReader {
return &BackupStreamReader{r, 0}
}
// Next returns the next backup stream and prepares for calls to Read(). It skips the remainder of the current stream if
// it was not completely read.
func (r *BackupStreamReader) Next() (*BackupHeader, error) {
if r.bytesLeft > 0 {
if s, ok := r.r.(io.Seeker); ok {
// Make sure Seek on io.SeekCurrent sometimes succeeds
// before trying the actual seek.
if _, err := s.Seek(0, io.SeekCurrent); err == nil {
if _, err = s.Seek(r.bytesLeft, io.SeekCurrent); err != nil {
return nil, err
}
r.bytesLeft = 0
}
}
if _, err := io.Copy(ioutil.Discard, r); err != nil {
return nil, err
}
}
var wsi win32StreamId
if err := binary.Read(r.r, binary.LittleEndian, &wsi); err != nil {
return nil, err
}
hdr := &BackupHeader{
Id: wsi.StreamId,
Attributes: wsi.Attributes,
Size: int64(wsi.Size),
}
if wsi.NameSize != 0 {
name := make([]uint16, int(wsi.NameSize/2))
if err := binary.Read(r.r, binary.LittleEndian, name); err != nil {
return nil, err
}
hdr.Name = syscall.UTF16ToString(name)
}
if wsi.StreamId == BackupSparseBlock {
if err := binary.Read(r.r, binary.LittleEndian, &hdr.Offset); err != nil {
return nil, err
}
hdr.Size -= 8
}
r.bytesLeft = hdr.Size
return hdr, nil
}
// Read reads from the current backup stream.
func (r *BackupStreamReader) Read(b []byte) (int, error) {
if r.bytesLeft == 0 {
return 0, io.EOF
}
if int64(len(b)) > r.bytesLeft {
b = b[:r.bytesLeft]
}
n, err := r.r.Read(b)
r.bytesLeft -= int64(n)
if err == io.EOF {
err = io.ErrUnexpectedEOF
} else if r.bytesLeft == 0 && err == nil {
err = io.EOF
}
return n, err
}
// BackupStreamWriter writes a stream compatible with the BackupWrite Win32 API.
type BackupStreamWriter struct {
w io.Writer
bytesLeft int64
}
// NewBackupStreamWriter produces a BackupStreamWriter on top of an io.Writer.
func NewBackupStreamWriter(w io.Writer) *BackupStreamWriter {
return &BackupStreamWriter{w, 0}
}
// WriteHeader writes the next backup stream header and prepares for calls to Write().
func (w *BackupStreamWriter) WriteHeader(hdr *BackupHeader) error {
if w.bytesLeft != 0 {
return fmt.Errorf("missing %d bytes", w.bytesLeft)
}
name := utf16.Encode([]rune(hdr.Name))
wsi := win32StreamId{
StreamId: hdr.Id,
Attributes: hdr.Attributes,
Size: uint64(hdr.Size),
NameSize: uint32(len(name) * 2),
}
if hdr.Id == BackupSparseBlock {
// Include space for the int64 block offset
wsi.Size += 8
}
if err := binary.Write(w.w, binary.LittleEndian, &wsi); err != nil {
return err
}
if len(name) != 0 {
if err := binary.Write(w.w, binary.LittleEndian, name); err != nil {
return err
}
}
if hdr.Id == BackupSparseBlock {
if err := binary.Write(w.w, binary.LittleEndian, hdr.Offset); err != nil {
return err
}
}
w.bytesLeft = hdr.Size
return nil
}
// Write writes to the current backup stream.
func (w *BackupStreamWriter) Write(b []byte) (int, error) {
if w.bytesLeft < int64(len(b)) {
return 0, fmt.Errorf("too many bytes by %d", int64(len(b))-w.bytesLeft)
}
n, err := w.w.Write(b)
w.bytesLeft -= int64(n)
return n, err
}
// BackupFileReader provides an io.ReadCloser interface on top of the BackupRead Win32 API.
type BackupFileReader struct {
f *os.File
includeSecurity bool
ctx uintptr
}
// NewBackupFileReader returns a new BackupFileReader from a file handle. If includeSecurity is true,
// Read will attempt to read the security descriptor of the file.
func NewBackupFileReader(f *os.File, includeSecurity bool) *BackupFileReader {
r := &BackupFileReader{f, includeSecurity, 0}
return r
}
// Read reads a backup stream from the file by calling the Win32 API BackupRead().
func (r *BackupFileReader) Read(b []byte) (int, error) {
var bytesRead uint32
err := backupRead(syscall.Handle(r.f.Fd()), b, &bytesRead, false, r.includeSecurity, &r.ctx)
if err != nil {
return 0, &os.PathError{"BackupRead", r.f.Name(), err}
}
runtime.KeepAlive(r.f)
if bytesRead == 0 {
return 0, io.EOF
}
return int(bytesRead), nil
}
// Close frees Win32 resources associated with the BackupFileReader. It does not close
// the underlying file.
func (r *BackupFileReader) Close() error {
if r.ctx != 0 {
backupRead(syscall.Handle(r.f.Fd()), nil, nil, true, false, &r.ctx)
runtime.KeepAlive(r.f)
r.ctx = 0
}
return nil
}
// BackupFileWriter provides an io.WriteCloser interface on top of the BackupWrite Win32 API.
type BackupFileWriter struct {
f *os.File
includeSecurity bool
ctx uintptr
}
// NewBackupFileWriter returns a new BackupFileWriter from a file handle. If includeSecurity is true,
// Write() will attempt to restore the security descriptor from the stream.
func NewBackupFileWriter(f *os.File, includeSecurity bool) *BackupFileWriter {
w := &BackupFileWriter{f, includeSecurity, 0}
return w
}
// Write restores a portion of the file using the provided backup stream.
func (w *BackupFileWriter) Write(b []byte) (int, error) {
var bytesWritten uint32
err := backupWrite(syscall.Handle(w.f.Fd()), b, &bytesWritten, false, w.includeSecurity, &w.ctx)
if err != nil {
return 0, &os.PathError{"BackupWrite", w.f.Name(), err}
}
runtime.KeepAlive(w.f)
if int(bytesWritten) != len(b) {
return int(bytesWritten), errors.New("not all bytes could be written")
}
return len(b), nil
}
// Close frees Win32 resources associated with the BackupFileWriter. It does not
// close the underlying file.
func (w *BackupFileWriter) Close() error {
if w.ctx != 0 {
backupWrite(syscall.Handle(w.f.Fd()), nil, nil, true, false, &w.ctx)
runtime.KeepAlive(w.f)
w.ctx = 0
}
return nil
}
// OpenForBackup opens a file or directory, potentially skipping access checks if the backup
// or restore privileges have been acquired.
//
// If the file opened was a directory, it cannot be used with Readdir().
func OpenForBackup(path string, access uint32, share uint32, createmode uint32) (*os.File, error) {
winPath, err := syscall.UTF16FromString(path)
if err != nil {
return nil, err
}
h, err := syscall.CreateFile(&winPath[0], access, share, nil, createmode, syscall.FILE_FLAG_BACKUP_SEMANTICS|syscall.FILE_FLAG_OPEN_REPARSE_POINT, 0)
if err != nil {
err = &os.PathError{Op: "open", Path: path, Err: err}
return nil, err
}
return os.NewFile(uintptr(h), path), nil
}

137
vendor/github.com/Microsoft/go-winio/ea.go generated vendored Normal file
View File

@ -0,0 +1,137 @@
package winio
import (
"bytes"
"encoding/binary"
"errors"
)
type fileFullEaInformation struct {
NextEntryOffset uint32
Flags uint8
NameLength uint8
ValueLength uint16
}
var (
fileFullEaInformationSize = binary.Size(&fileFullEaInformation{})
errInvalidEaBuffer = errors.New("invalid extended attribute buffer")
errEaNameTooLarge = errors.New("extended attribute name too large")
errEaValueTooLarge = errors.New("extended attribute value too large")
)
// ExtendedAttribute represents a single Windows EA.
type ExtendedAttribute struct {
Name string
Value []byte
Flags uint8
}
func parseEa(b []byte) (ea ExtendedAttribute, nb []byte, err error) {
var info fileFullEaInformation
err = binary.Read(bytes.NewReader(b), binary.LittleEndian, &info)
if err != nil {
err = errInvalidEaBuffer
return
}
nameOffset := fileFullEaInformationSize
nameLen := int(info.NameLength)
valueOffset := nameOffset + int(info.NameLength) + 1
valueLen := int(info.ValueLength)
nextOffset := int(info.NextEntryOffset)
if valueLen+valueOffset > len(b) || nextOffset < 0 || nextOffset > len(b) {
err = errInvalidEaBuffer
return
}
ea.Name = string(b[nameOffset : nameOffset+nameLen])
ea.Value = b[valueOffset : valueOffset+valueLen]
ea.Flags = info.Flags
if info.NextEntryOffset != 0 {
nb = b[info.NextEntryOffset:]
}
return
}
// DecodeExtendedAttributes decodes a list of EAs from a FILE_FULL_EA_INFORMATION
// buffer retrieved from BackupRead, ZwQueryEaFile, etc.
func DecodeExtendedAttributes(b []byte) (eas []ExtendedAttribute, err error) {
for len(b) != 0 {
ea, nb, err := parseEa(b)
if err != nil {
return nil, err
}
eas = append(eas, ea)
b = nb
}
return
}
func writeEa(buf *bytes.Buffer, ea *ExtendedAttribute, last bool) error {
if int(uint8(len(ea.Name))) != len(ea.Name) {
return errEaNameTooLarge
}
if int(uint16(len(ea.Value))) != len(ea.Value) {
return errEaValueTooLarge
}
entrySize := uint32(fileFullEaInformationSize + len(ea.Name) + 1 + len(ea.Value))
withPadding := (entrySize + 3) &^ 3
nextOffset := uint32(0)
if !last {
nextOffset = withPadding
}
info := fileFullEaInformation{
NextEntryOffset: nextOffset,
Flags: ea.Flags,
NameLength: uint8(len(ea.Name)),
ValueLength: uint16(len(ea.Value)),
}
err := binary.Write(buf, binary.LittleEndian, &info)
if err != nil {
return err
}
_, err = buf.Write([]byte(ea.Name))
if err != nil {
return err
}
err = buf.WriteByte(0)
if err != nil {
return err
}
_, err = buf.Write(ea.Value)
if err != nil {
return err
}
_, err = buf.Write([]byte{0, 0, 0}[0 : withPadding-entrySize])
if err != nil {
return err
}
return nil
}
// EncodeExtendedAttributes encodes a list of EAs into a FILE_FULL_EA_INFORMATION
// buffer for use with BackupWrite, ZwSetEaFile, etc.
func EncodeExtendedAttributes(eas []ExtendedAttribute) ([]byte, error) {
var buf bytes.Buffer
for i := range eas {
last := false
if i == len(eas)-1 {
last = true
}
err := writeEa(&buf, &eas[i], last)
if err != nil {
return nil, err
}
}
return buf.Bytes(), nil
}

323
vendor/github.com/Microsoft/go-winio/file.go generated vendored Normal file
View File

@ -0,0 +1,323 @@
// +build windows
package winio
import (
"errors"
"io"
"runtime"
"sync"
"sync/atomic"
"syscall"
"time"
)
//sys cancelIoEx(file syscall.Handle, o *syscall.Overlapped) (err error) = CancelIoEx
//sys createIoCompletionPort(file syscall.Handle, port syscall.Handle, key uintptr, threadCount uint32) (newport syscall.Handle, err error) = CreateIoCompletionPort
//sys getQueuedCompletionStatus(port syscall.Handle, bytes *uint32, key *uintptr, o **ioOperation, timeout uint32) (err error) = GetQueuedCompletionStatus
//sys setFileCompletionNotificationModes(h syscall.Handle, flags uint8) (err error) = SetFileCompletionNotificationModes
//sys wsaGetOverlappedResult(h syscall.Handle, o *syscall.Overlapped, bytes *uint32, wait bool, flags *uint32) (err error) = ws2_32.WSAGetOverlappedResult
type atomicBool int32
func (b *atomicBool) isSet() bool { return atomic.LoadInt32((*int32)(b)) != 0 }
func (b *atomicBool) setFalse() { atomic.StoreInt32((*int32)(b), 0) }
func (b *atomicBool) setTrue() { atomic.StoreInt32((*int32)(b), 1) }
func (b *atomicBool) swap(new bool) bool {
var newInt int32
if new {
newInt = 1
}
return atomic.SwapInt32((*int32)(b), newInt) == 1
}
const (
cFILE_SKIP_COMPLETION_PORT_ON_SUCCESS = 1
cFILE_SKIP_SET_EVENT_ON_HANDLE = 2
)
var (
ErrFileClosed = errors.New("file has already been closed")
ErrTimeout = &timeoutError{}
)
type timeoutError struct{}
func (e *timeoutError) Error() string { return "i/o timeout" }
func (e *timeoutError) Timeout() bool { return true }
func (e *timeoutError) Temporary() bool { return true }
type timeoutChan chan struct{}
var ioInitOnce sync.Once
var ioCompletionPort syscall.Handle
// ioResult contains the result of an asynchronous IO operation
type ioResult struct {
bytes uint32
err error
}
// ioOperation represents an outstanding asynchronous Win32 IO
type ioOperation struct {
o syscall.Overlapped
ch chan ioResult
}
func initIo() {
h, err := createIoCompletionPort(syscall.InvalidHandle, 0, 0, 0xffffffff)
if err != nil {
panic(err)
}
ioCompletionPort = h
go ioCompletionProcessor(h)
}
// win32File implements Reader, Writer, and Closer on a Win32 handle without blocking in a syscall.
// It takes ownership of this handle and will close it if it is garbage collected.
type win32File struct {
handle syscall.Handle
wg sync.WaitGroup
wgLock sync.RWMutex
closing atomicBool
socket bool
readDeadline deadlineHandler
writeDeadline deadlineHandler
}
type deadlineHandler struct {
setLock sync.Mutex
channel timeoutChan
channelLock sync.RWMutex
timer *time.Timer
timedout atomicBool
}
// makeWin32File makes a new win32File from an existing file handle
func makeWin32File(h syscall.Handle) (*win32File, error) {
f := &win32File{handle: h}
ioInitOnce.Do(initIo)
_, err := createIoCompletionPort(h, ioCompletionPort, 0, 0xffffffff)
if err != nil {
return nil, err
}
err = setFileCompletionNotificationModes(h, cFILE_SKIP_COMPLETION_PORT_ON_SUCCESS|cFILE_SKIP_SET_EVENT_ON_HANDLE)
if err != nil {
return nil, err
}
f.readDeadline.channel = make(timeoutChan)
f.writeDeadline.channel = make(timeoutChan)
return f, nil
}
func MakeOpenFile(h syscall.Handle) (io.ReadWriteCloser, error) {
// If we return the result of makeWin32File directly, it can result in an
// interface-wrapped nil, rather than a nil interface value.
f, err := makeWin32File(h)
if err != nil {
return nil, err
}
return f, nil
}
// closeHandle closes the resources associated with a Win32 handle
func (f *win32File) closeHandle() {
f.wgLock.Lock()
// Atomically set that we are closing, releasing the resources only once.
if !f.closing.swap(true) {
f.wgLock.Unlock()
// cancel all IO and wait for it to complete
cancelIoEx(f.handle, nil)
f.wg.Wait()
// at this point, no new IO can start
syscall.Close(f.handle)
f.handle = 0
} else {
f.wgLock.Unlock()
}
}
// Close closes a win32File.
func (f *win32File) Close() error {
f.closeHandle()
return nil
}
// prepareIo prepares for a new IO operation.
// The caller must call f.wg.Done() when the IO is finished, prior to Close() returning.
func (f *win32File) prepareIo() (*ioOperation, error) {
f.wgLock.RLock()
if f.closing.isSet() {
f.wgLock.RUnlock()
return nil, ErrFileClosed
}
f.wg.Add(1)
f.wgLock.RUnlock()
c := &ioOperation{}
c.ch = make(chan ioResult)
return c, nil
}
// ioCompletionProcessor processes completed async IOs forever
func ioCompletionProcessor(h syscall.Handle) {
for {
var bytes uint32
var key uintptr
var op *ioOperation
err := getQueuedCompletionStatus(h, &bytes, &key, &op, syscall.INFINITE)
if op == nil {
panic(err)
}
op.ch <- ioResult{bytes, err}
}
}
// asyncIo processes the return value from ReadFile or WriteFile, blocking until
// the operation has actually completed.
func (f *win32File) asyncIo(c *ioOperation, d *deadlineHandler, bytes uint32, err error) (int, error) {
if err != syscall.ERROR_IO_PENDING {
return int(bytes), err
}
if f.closing.isSet() {
cancelIoEx(f.handle, &c.o)
}
var timeout timeoutChan
if d != nil {
d.channelLock.Lock()
timeout = d.channel
d.channelLock.Unlock()
}
var r ioResult
select {
case r = <-c.ch:
err = r.err
if err == syscall.ERROR_OPERATION_ABORTED {
if f.closing.isSet() {
err = ErrFileClosed
}
} else if err != nil && f.socket {
// err is from Win32. Query the overlapped structure to get the winsock error.
var bytes, flags uint32
err = wsaGetOverlappedResult(f.handle, &c.o, &bytes, false, &flags)
}
case <-timeout:
cancelIoEx(f.handle, &c.o)
r = <-c.ch
err = r.err
if err == syscall.ERROR_OPERATION_ABORTED {
err = ErrTimeout
}
}
// runtime.KeepAlive is needed, as c is passed via native
// code to ioCompletionProcessor, c must remain alive
// until the channel read is complete.
runtime.KeepAlive(c)
return int(r.bytes), err
}
// Read reads from a file handle.
func (f *win32File) Read(b []byte) (int, error) {
c, err := f.prepareIo()
if err != nil {
return 0, err
}
defer f.wg.Done()
if f.readDeadline.timedout.isSet() {
return 0, ErrTimeout
}
var bytes uint32
err = syscall.ReadFile(f.handle, b, &bytes, &c.o)
n, err := f.asyncIo(c, &f.readDeadline, bytes, err)
runtime.KeepAlive(b)
// Handle EOF conditions.
if err == nil && n == 0 && len(b) != 0 {
return 0, io.EOF
} else if err == syscall.ERROR_BROKEN_PIPE {
return 0, io.EOF
} else {
return n, err
}
}
// Write writes to a file handle.
func (f *win32File) Write(b []byte) (int, error) {
c, err := f.prepareIo()
if err != nil {
return 0, err
}
defer f.wg.Done()
if f.writeDeadline.timedout.isSet() {
return 0, ErrTimeout
}
var bytes uint32
err = syscall.WriteFile(f.handle, b, &bytes, &c.o)
n, err := f.asyncIo(c, &f.writeDeadline, bytes, err)
runtime.KeepAlive(b)
return n, err
}
func (f *win32File) SetReadDeadline(deadline time.Time) error {
return f.readDeadline.set(deadline)
}
func (f *win32File) SetWriteDeadline(deadline time.Time) error {
return f.writeDeadline.set(deadline)
}
func (f *win32File) Flush() error {
return syscall.FlushFileBuffers(f.handle)
}
func (f *win32File) Fd() uintptr {
return uintptr(f.handle)
}
func (d *deadlineHandler) set(deadline time.Time) error {
d.setLock.Lock()
defer d.setLock.Unlock()
if d.timer != nil {
if !d.timer.Stop() {
<-d.channel
}
d.timer = nil
}
d.timedout.setFalse()
select {
case <-d.channel:
d.channelLock.Lock()
d.channel = make(chan struct{})
d.channelLock.Unlock()
default:
}
if deadline.IsZero() {
return nil
}
timeoutIO := func() {
d.timedout.setTrue()
close(d.channel)
}
now := time.Now()
duration := deadline.Sub(now)
if deadline.After(now) {
// Deadline is in the future, set a timer to wait
d.timer = time.AfterFunc(duration, timeoutIO)
} else {
// Deadline is in the past. Cancel all pending IO now.
timeoutIO()
}
return nil
}

61
vendor/github.com/Microsoft/go-winio/fileinfo.go generated vendored Normal file
View File

@ -0,0 +1,61 @@
// +build windows
package winio
import (
"os"
"runtime"
"syscall"
"unsafe"
)
//sys getFileInformationByHandleEx(h syscall.Handle, class uint32, buffer *byte, size uint32) (err error) = GetFileInformationByHandleEx
//sys setFileInformationByHandle(h syscall.Handle, class uint32, buffer *byte, size uint32) (err error) = SetFileInformationByHandle
const (
fileBasicInfo = 0
fileIDInfo = 0x12
)
// FileBasicInfo contains file access time and file attributes information.
type FileBasicInfo struct {
CreationTime, LastAccessTime, LastWriteTime, ChangeTime syscall.Filetime
FileAttributes uint32
pad uint32 // padding
}
// GetFileBasicInfo retrieves times and attributes for a file.
func GetFileBasicInfo(f *os.File) (*FileBasicInfo, error) {
bi := &FileBasicInfo{}
if err := getFileInformationByHandleEx(syscall.Handle(f.Fd()), fileBasicInfo, (*byte)(unsafe.Pointer(bi)), uint32(unsafe.Sizeof(*bi))); err != nil {
return nil, &os.PathError{Op: "GetFileInformationByHandleEx", Path: f.Name(), Err: err}
}
runtime.KeepAlive(f)
return bi, nil
}
// SetFileBasicInfo sets times and attributes for a file.
func SetFileBasicInfo(f *os.File, bi *FileBasicInfo) error {
if err := setFileInformationByHandle(syscall.Handle(f.Fd()), fileBasicInfo, (*byte)(unsafe.Pointer(bi)), uint32(unsafe.Sizeof(*bi))); err != nil {
return &os.PathError{Op: "SetFileInformationByHandle", Path: f.Name(), Err: err}
}
runtime.KeepAlive(f)
return nil
}
// FileIDInfo contains the volume serial number and file ID for a file. This pair should be
// unique on a system.
type FileIDInfo struct {
VolumeSerialNumber uint64
FileID [16]byte
}
// GetFileID retrieves the unique (volume, file ID) pair for a file.
func GetFileID(f *os.File) (*FileIDInfo, error) {
fileID := &FileIDInfo{}
if err := getFileInformationByHandleEx(syscall.Handle(f.Fd()), fileIDInfo, (*byte)(unsafe.Pointer(fileID)), uint32(unsafe.Sizeof(*fileID))); err != nil {
return nil, &os.PathError{Op: "GetFileInformationByHandleEx", Path: f.Name(), Err: err}
}
runtime.KeepAlive(f)
return fileID, nil
}

9
vendor/github.com/Microsoft/go-winio/go.mod generated vendored Normal file
View File

@ -0,0 +1,9 @@
module github.com/Microsoft/go-winio
go 1.12
require (
github.com/pkg/errors v0.8.1
github.com/sirupsen/logrus v1.4.1
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b
)

16
vendor/github.com/Microsoft/go-winio/go.sum generated vendored Normal file
View File

@ -0,0 +1,16 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sirupsen/logrus v1.4.1 h1:GL2rEmy6nsikmW0r8opw9JIRScdMF5hA8cOYLH7In1k=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b h1:ag/x1USPSsqHud38I9BAC88qdNLDHHtQ4mlgQIZPPNA=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

305
vendor/github.com/Microsoft/go-winio/hvsock.go generated vendored Normal file
View File

@ -0,0 +1,305 @@
package winio
import (
"fmt"
"io"
"net"
"os"
"syscall"
"time"
"unsafe"
"github.com/Microsoft/go-winio/pkg/guid"
)
//sys bind(s syscall.Handle, name unsafe.Pointer, namelen int32) (err error) [failretval==socketError] = ws2_32.bind
const (
afHvSock = 34 // AF_HYPERV
socketError = ^uintptr(0)
)
// An HvsockAddr is an address for a AF_HYPERV socket.
type HvsockAddr struct {
VMID guid.GUID
ServiceID guid.GUID
}
type rawHvsockAddr struct {
Family uint16
_ uint16
VMID guid.GUID
ServiceID guid.GUID
}
// Network returns the address's network name, "hvsock".
func (addr *HvsockAddr) Network() string {
return "hvsock"
}
func (addr *HvsockAddr) String() string {
return fmt.Sprintf("%s:%s", &addr.VMID, &addr.ServiceID)
}
// VsockServiceID returns an hvsock service ID corresponding to the specified AF_VSOCK port.
func VsockServiceID(port uint32) guid.GUID {
g, _ := guid.FromString("00000000-facb-11e6-bd58-64006a7986d3")
g.Data1 = port
return g
}
func (addr *HvsockAddr) raw() rawHvsockAddr {
return rawHvsockAddr{
Family: afHvSock,
VMID: addr.VMID,
ServiceID: addr.ServiceID,
}
}
func (addr *HvsockAddr) fromRaw(raw *rawHvsockAddr) {
addr.VMID = raw.VMID
addr.ServiceID = raw.ServiceID
}
// HvsockListener is a socket listener for the AF_HYPERV address family.
type HvsockListener struct {
sock *win32File
addr HvsockAddr
}
// HvsockConn is a connected socket of the AF_HYPERV address family.
type HvsockConn struct {
sock *win32File
local, remote HvsockAddr
}
func newHvSocket() (*win32File, error) {
fd, err := syscall.Socket(afHvSock, syscall.SOCK_STREAM, 1)
if err != nil {
return nil, os.NewSyscallError("socket", err)
}
f, err := makeWin32File(fd)
if err != nil {
syscall.Close(fd)
return nil, err
}
f.socket = true
return f, nil
}
// ListenHvsock listens for connections on the specified hvsock address.
func ListenHvsock(addr *HvsockAddr) (_ *HvsockListener, err error) {
l := &HvsockListener{addr: *addr}
sock, err := newHvSocket()
if err != nil {
return nil, l.opErr("listen", err)
}
sa := addr.raw()
err = bind(sock.handle, unsafe.Pointer(&sa), int32(unsafe.Sizeof(sa)))
if err != nil {
return nil, l.opErr("listen", os.NewSyscallError("socket", err))
}
err = syscall.Listen(sock.handle, 16)
if err != nil {
return nil, l.opErr("listen", os.NewSyscallError("listen", err))
}
return &HvsockListener{sock: sock, addr: *addr}, nil
}
func (l *HvsockListener) opErr(op string, err error) error {
return &net.OpError{Op: op, Net: "hvsock", Addr: &l.addr, Err: err}
}
// Addr returns the listener's network address.
func (l *HvsockListener) Addr() net.Addr {
return &l.addr
}
// Accept waits for the next connection and returns it.
func (l *HvsockListener) Accept() (_ net.Conn, err error) {
sock, err := newHvSocket()
if err != nil {
return nil, l.opErr("accept", err)
}
defer func() {
if sock != nil {
sock.Close()
}
}()
c, err := l.sock.prepareIo()
if err != nil {
return nil, l.opErr("accept", err)
}
defer l.sock.wg.Done()
// AcceptEx, per documentation, requires an extra 16 bytes per address.
const addrlen = uint32(16 + unsafe.Sizeof(rawHvsockAddr{}))
var addrbuf [addrlen * 2]byte
var bytes uint32
err = syscall.AcceptEx(l.sock.handle, sock.handle, &addrbuf[0], 0, addrlen, addrlen, &bytes, &c.o)
_, err = l.sock.asyncIo(c, nil, bytes, err)
if err != nil {
return nil, l.opErr("accept", os.NewSyscallError("acceptex", err))
}
conn := &HvsockConn{
sock: sock,
}
conn.local.fromRaw((*rawHvsockAddr)(unsafe.Pointer(&addrbuf[0])))
conn.remote.fromRaw((*rawHvsockAddr)(unsafe.Pointer(&addrbuf[addrlen])))
sock = nil
return conn, nil
}
// Close closes the listener, causing any pending Accept calls to fail.
func (l *HvsockListener) Close() error {
return l.sock.Close()
}
/* Need to finish ConnectEx handling
func DialHvsock(ctx context.Context, addr *HvsockAddr) (*HvsockConn, error) {
sock, err := newHvSocket()
if err != nil {
return nil, err
}
defer func() {
if sock != nil {
sock.Close()
}
}()
c, err := sock.prepareIo()
if err != nil {
return nil, err
}
defer sock.wg.Done()
var bytes uint32
err = windows.ConnectEx(windows.Handle(sock.handle), sa, nil, 0, &bytes, &c.o)
_, err = sock.asyncIo(ctx, c, nil, bytes, err)
if err != nil {
return nil, err
}
conn := &HvsockConn{
sock: sock,
remote: *addr,
}
sock = nil
return conn, nil
}
*/
func (conn *HvsockConn) opErr(op string, err error) error {
return &net.OpError{Op: op, Net: "hvsock", Source: &conn.local, Addr: &conn.remote, Err: err}
}
func (conn *HvsockConn) Read(b []byte) (int, error) {
c, err := conn.sock.prepareIo()
if err != nil {
return 0, conn.opErr("read", err)
}
defer conn.sock.wg.Done()
buf := syscall.WSABuf{Buf: &b[0], Len: uint32(len(b))}
var flags, bytes uint32
err = syscall.WSARecv(conn.sock.handle, &buf, 1, &bytes, &flags, &c.o, nil)
n, err := conn.sock.asyncIo(c, &conn.sock.readDeadline, bytes, err)
if err != nil {
if _, ok := err.(syscall.Errno); ok {
err = os.NewSyscallError("wsarecv", err)
}
return 0, conn.opErr("read", err)
} else if n == 0 {
err = io.EOF
}
return n, err
}
func (conn *HvsockConn) Write(b []byte) (int, error) {
t := 0
for len(b) != 0 {
n, err := conn.write(b)
if err != nil {
return t + n, err
}
t += n
b = b[n:]
}
return t, nil
}
func (conn *HvsockConn) write(b []byte) (int, error) {
c, err := conn.sock.prepareIo()
if err != nil {
return 0, conn.opErr("write", err)
}
defer conn.sock.wg.Done()
buf := syscall.WSABuf{Buf: &b[0], Len: uint32(len(b))}
var bytes uint32
err = syscall.WSASend(conn.sock.handle, &buf, 1, &bytes, 0, &c.o, nil)
n, err := conn.sock.asyncIo(c, &conn.sock.writeDeadline, bytes, err)
if err != nil {
if _, ok := err.(syscall.Errno); ok {
err = os.NewSyscallError("wsasend", err)
}
return 0, conn.opErr("write", err)
}
return n, err
}
// Close closes the socket connection, failing any pending read or write calls.
func (conn *HvsockConn) Close() error {
return conn.sock.Close()
}
func (conn *HvsockConn) shutdown(how int) error {
err := syscall.Shutdown(conn.sock.handle, syscall.SHUT_RD)
if err != nil {
return os.NewSyscallError("shutdown", err)
}
return nil
}
// CloseRead shuts down the read end of the socket.
func (conn *HvsockConn) CloseRead() error {
err := conn.shutdown(syscall.SHUT_RD)
if err != nil {
return conn.opErr("close", err)
}
return nil
}
// CloseWrite shuts down the write end of the socket, notifying the other endpoint that
// no more data will be written.
func (conn *HvsockConn) CloseWrite() error {
err := conn.shutdown(syscall.SHUT_WR)
if err != nil {
return conn.opErr("close", err)
}
return nil
}
// LocalAddr returns the local address of the connection.
func (conn *HvsockConn) LocalAddr() net.Addr {
return &conn.local
}
// RemoteAddr returns the remote address of the connection.
func (conn *HvsockConn) RemoteAddr() net.Addr {
return &conn.remote
}
// SetDeadline implements the net.Conn SetDeadline method.
func (conn *HvsockConn) SetDeadline(t time.Time) error {
conn.SetReadDeadline(t)
conn.SetWriteDeadline(t)
return nil
}
// SetReadDeadline implements the net.Conn SetReadDeadline method.
func (conn *HvsockConn) SetReadDeadline(t time.Time) error {
return conn.sock.SetReadDeadline(t)
}
// SetWriteDeadline implements the net.Conn SetWriteDeadline method.
func (conn *HvsockConn) SetWriteDeadline(t time.Time) error {
return conn.sock.SetWriteDeadline(t)
}

510
vendor/github.com/Microsoft/go-winio/pipe.go generated vendored Normal file
View File

@ -0,0 +1,510 @@
// +build windows
package winio
import (
"context"
"errors"
"fmt"
"io"
"net"
"os"
"runtime"
"syscall"
"time"
"unsafe"
)
//sys connectNamedPipe(pipe syscall.Handle, o *syscall.Overlapped) (err error) = ConnectNamedPipe
//sys createNamedPipe(name string, flags uint32, pipeMode uint32, maxInstances uint32, outSize uint32, inSize uint32, defaultTimeout uint32, sa *syscall.SecurityAttributes) (handle syscall.Handle, err error) [failretval==syscall.InvalidHandle] = CreateNamedPipeW
//sys createFile(name string, access uint32, mode uint32, sa *syscall.SecurityAttributes, createmode uint32, attrs uint32, templatefile syscall.Handle) (handle syscall.Handle, err error) [failretval==syscall.InvalidHandle] = CreateFileW
//sys getNamedPipeInfo(pipe syscall.Handle, flags *uint32, outSize *uint32, inSize *uint32, maxInstances *uint32) (err error) = GetNamedPipeInfo
//sys getNamedPipeHandleState(pipe syscall.Handle, state *uint32, curInstances *uint32, maxCollectionCount *uint32, collectDataTimeout *uint32, userName *uint16, maxUserNameSize uint32) (err error) = GetNamedPipeHandleStateW
//sys localAlloc(uFlags uint32, length uint32) (ptr uintptr) = LocalAlloc
//sys ntCreateNamedPipeFile(pipe *syscall.Handle, access uint32, oa *objectAttributes, iosb *ioStatusBlock, share uint32, disposition uint32, options uint32, typ uint32, readMode uint32, completionMode uint32, maxInstances uint32, inboundQuota uint32, outputQuota uint32, timeout *int64) (status ntstatus) = ntdll.NtCreateNamedPipeFile
//sys rtlNtStatusToDosError(status ntstatus) (winerr error) = ntdll.RtlNtStatusToDosErrorNoTeb
//sys rtlDosPathNameToNtPathName(name *uint16, ntName *unicodeString, filePart uintptr, reserved uintptr) (status ntstatus) = ntdll.RtlDosPathNameToNtPathName_U
//sys rtlDefaultNpAcl(dacl *uintptr) (status ntstatus) = ntdll.RtlDefaultNpAcl
type ioStatusBlock struct {
Status, Information uintptr
}
type objectAttributes struct {
Length uintptr
RootDirectory uintptr
ObjectName *unicodeString
Attributes uintptr
SecurityDescriptor *securityDescriptor
SecurityQoS uintptr
}
type unicodeString struct {
Length uint16
MaximumLength uint16
Buffer uintptr
}
type securityDescriptor struct {
Revision byte
Sbz1 byte
Control uint16
Owner uintptr
Group uintptr
Sacl uintptr
Dacl uintptr
}
type ntstatus int32
func (status ntstatus) Err() error {
if status >= 0 {
return nil
}
return rtlNtStatusToDosError(status)
}
const (
cERROR_PIPE_BUSY = syscall.Errno(231)
cERROR_NO_DATA = syscall.Errno(232)
cERROR_PIPE_CONNECTED = syscall.Errno(535)
cERROR_SEM_TIMEOUT = syscall.Errno(121)
cSECURITY_SQOS_PRESENT = 0x100000
cSECURITY_ANONYMOUS = 0
cPIPE_TYPE_MESSAGE = 4
cPIPE_READMODE_MESSAGE = 2
cFILE_OPEN = 1
cFILE_CREATE = 2
cFILE_PIPE_MESSAGE_TYPE = 1
cFILE_PIPE_REJECT_REMOTE_CLIENTS = 2
cSE_DACL_PRESENT = 4
)
var (
// ErrPipeListenerClosed is returned for pipe operations on listeners that have been closed.
// This error should match net.errClosing since docker takes a dependency on its text.
ErrPipeListenerClosed = errors.New("use of closed network connection")
errPipeWriteClosed = errors.New("pipe has been closed for write")
)
type win32Pipe struct {
*win32File
path string
}
type win32MessageBytePipe struct {
win32Pipe
writeClosed bool
readEOF bool
}
type pipeAddress string
func (f *win32Pipe) LocalAddr() net.Addr {
return pipeAddress(f.path)
}
func (f *win32Pipe) RemoteAddr() net.Addr {
return pipeAddress(f.path)
}
func (f *win32Pipe) SetDeadline(t time.Time) error {
f.SetReadDeadline(t)
f.SetWriteDeadline(t)
return nil
}
// CloseWrite closes the write side of a message pipe in byte mode.
func (f *win32MessageBytePipe) CloseWrite() error {
if f.writeClosed {
return errPipeWriteClosed
}
err := f.win32File.Flush()
if err != nil {
return err
}
_, err = f.win32File.Write(nil)
if err != nil {
return err
}
f.writeClosed = true
return nil
}
// Write writes bytes to a message pipe in byte mode. Zero-byte writes are ignored, since
// they are used to implement CloseWrite().
func (f *win32MessageBytePipe) Write(b []byte) (int, error) {
if f.writeClosed {
return 0, errPipeWriteClosed
}
if len(b) == 0 {
return 0, nil
}
return f.win32File.Write(b)
}
// Read reads bytes from a message pipe in byte mode. A read of a zero-byte message on a message
// mode pipe will return io.EOF, as will all subsequent reads.
func (f *win32MessageBytePipe) Read(b []byte) (int, error) {
if f.readEOF {
return 0, io.EOF
}
n, err := f.win32File.Read(b)
if err == io.EOF {
// If this was the result of a zero-byte read, then
// it is possible that the read was due to a zero-size
// message. Since we are simulating CloseWrite with a
// zero-byte message, ensure that all future Read() calls
// also return EOF.
f.readEOF = true
} else if err == syscall.ERROR_MORE_DATA {
// ERROR_MORE_DATA indicates that the pipe's read mode is message mode
// and the message still has more bytes. Treat this as a success, since
// this package presents all named pipes as byte streams.
err = nil
}
return n, err
}
func (s pipeAddress) Network() string {
return "pipe"
}
func (s pipeAddress) String() string {
return string(s)
}
// tryDialPipe attempts to dial the pipe at `path` until `ctx` cancellation or timeout.
func tryDialPipe(ctx context.Context, path *string) (syscall.Handle, error) {
for {
select {
case <-ctx.Done():
return syscall.Handle(0), ctx.Err()
default:
h, err := createFile(*path, syscall.GENERIC_READ|syscall.GENERIC_WRITE, 0, nil, syscall.OPEN_EXISTING, syscall.FILE_FLAG_OVERLAPPED|cSECURITY_SQOS_PRESENT|cSECURITY_ANONYMOUS, 0)
if err == nil {
return h, nil
}
if err != cERROR_PIPE_BUSY {
return h, &os.PathError{Err: err, Op: "open", Path: *path}
}
// Wait 10 msec and try again. This is a rather simplistic
// view, as we always try each 10 milliseconds.
time.Sleep(time.Millisecond * 10)
}
}
}
// DialPipe connects to a named pipe by path, timing out if the connection
// takes longer than the specified duration. If timeout is nil, then we use
// a default timeout of 2 seconds. (We do not use WaitNamedPipe.)
func DialPipe(path string, timeout *time.Duration) (net.Conn, error) {
var absTimeout time.Time
if timeout != nil {
absTimeout = time.Now().Add(*timeout)
} else {
absTimeout = time.Now().Add(time.Second * 2)
}
ctx, _ := context.WithDeadline(context.Background(), absTimeout)
conn, err := DialPipeContext(ctx, path)
if err == context.DeadlineExceeded {
return nil, ErrTimeout
}
return conn, err
}
// DialPipeContext attempts to connect to a named pipe by `path` until `ctx`
// cancellation or timeout.
func DialPipeContext(ctx context.Context, path string) (net.Conn, error) {
var err error
var h syscall.Handle
h, err = tryDialPipe(ctx, &path)
if err != nil {
return nil, err
}
var flags uint32
err = getNamedPipeInfo(h, &flags, nil, nil, nil)
if err != nil {
return nil, err
}
f, err := makeWin32File(h)
if err != nil {
syscall.Close(h)
return nil, err
}
// If the pipe is in message mode, return a message byte pipe, which
// supports CloseWrite().
if flags&cPIPE_TYPE_MESSAGE != 0 {
return &win32MessageBytePipe{
win32Pipe: win32Pipe{win32File: f, path: path},
}, nil
}
return &win32Pipe{win32File: f, path: path}, nil
}
type acceptResponse struct {
f *win32File
err error
}
type win32PipeListener struct {
firstHandle syscall.Handle
path string
config PipeConfig
acceptCh chan (chan acceptResponse)
closeCh chan int
doneCh chan int
}
func makeServerPipeHandle(path string, sd []byte, c *PipeConfig, first bool) (syscall.Handle, error) {
path16, err := syscall.UTF16FromString(path)
if err != nil {
return 0, &os.PathError{Op: "open", Path: path, Err: err}
}
var oa objectAttributes
oa.Length = unsafe.Sizeof(oa)
var ntPath unicodeString
if err := rtlDosPathNameToNtPathName(&path16[0], &ntPath, 0, 0).Err(); err != nil {
return 0, &os.PathError{Op: "open", Path: path, Err: err}
}
defer localFree(ntPath.Buffer)
oa.ObjectName = &ntPath
// The security descriptor is only needed for the first pipe.
if first {
if sd != nil {
len := uint32(len(sd))
sdb := localAlloc(0, len)
defer localFree(sdb)
copy((*[0xffff]byte)(unsafe.Pointer(sdb))[:], sd)
oa.SecurityDescriptor = (*securityDescriptor)(unsafe.Pointer(sdb))
} else {
// Construct the default named pipe security descriptor.
var dacl uintptr
if err := rtlDefaultNpAcl(&dacl).Err(); err != nil {
return 0, fmt.Errorf("getting default named pipe ACL: %s", err)
}
defer localFree(dacl)
sdb := &securityDescriptor{
Revision: 1,
Control: cSE_DACL_PRESENT,
Dacl: dacl,
}
oa.SecurityDescriptor = sdb
}
}
typ := uint32(cFILE_PIPE_REJECT_REMOTE_CLIENTS)
if c.MessageMode {
typ |= cFILE_PIPE_MESSAGE_TYPE
}
disposition := uint32(cFILE_OPEN)
access := uint32(syscall.GENERIC_READ | syscall.GENERIC_WRITE | syscall.SYNCHRONIZE)
if first {
disposition = cFILE_CREATE
// By not asking for read or write access, the named pipe file system
// will put this pipe into an initially disconnected state, blocking
// client connections until the next call with first == false.
access = syscall.SYNCHRONIZE
}
timeout := int64(-50 * 10000) // 50ms
var (
h syscall.Handle
iosb ioStatusBlock
)
err = ntCreateNamedPipeFile(&h, access, &oa, &iosb, syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE, disposition, 0, typ, 0, 0, 0xffffffff, uint32(c.InputBufferSize), uint32(c.OutputBufferSize), &timeout).Err()
if err != nil {
return 0, &os.PathError{Op: "open", Path: path, Err: err}
}
runtime.KeepAlive(ntPath)
return h, nil
}
func (l *win32PipeListener) makeServerPipe() (*win32File, error) {
h, err := makeServerPipeHandle(l.path, nil, &l.config, false)
if err != nil {
return nil, err
}
f, err := makeWin32File(h)
if err != nil {
syscall.Close(h)
return nil, err
}
return f, nil
}
func (l *win32PipeListener) makeConnectedServerPipe() (*win32File, error) {
p, err := l.makeServerPipe()
if err != nil {
return nil, err
}
// Wait for the client to connect.
ch := make(chan error)
go func(p *win32File) {
ch <- connectPipe(p)
}(p)
select {
case err = <-ch:
if err != nil {
p.Close()
p = nil
}
case <-l.closeCh:
// Abort the connect request by closing the handle.
p.Close()
p = nil
err = <-ch
if err == nil || err == ErrFileClosed {
err = ErrPipeListenerClosed
}
}
return p, err
}
func (l *win32PipeListener) listenerRoutine() {
closed := false
for !closed {
select {
case <-l.closeCh:
closed = true
case responseCh := <-l.acceptCh:
var (
p *win32File
err error
)
for {
p, err = l.makeConnectedServerPipe()
// If the connection was immediately closed by the client, try
// again.
if err != cERROR_NO_DATA {
break
}
}
responseCh <- acceptResponse{p, err}
closed = err == ErrPipeListenerClosed
}
}
syscall.Close(l.firstHandle)
l.firstHandle = 0
// Notify Close() and Accept() callers that the handle has been closed.
close(l.doneCh)
}
// PipeConfig contain configuration for the pipe listener.
type PipeConfig struct {
// SecurityDescriptor contains a Windows security descriptor in SDDL format.
SecurityDescriptor string
// MessageMode determines whether the pipe is in byte or message mode. In either
// case the pipe is read in byte mode by default. The only practical difference in
// this implementation is that CloseWrite() is only supported for message mode pipes;
// CloseWrite() is implemented as a zero-byte write, but zero-byte writes are only
// transferred to the reader (and returned as io.EOF in this implementation)
// when the pipe is in message mode.
MessageMode bool
// InputBufferSize specifies the size the input buffer, in bytes.
InputBufferSize int32
// OutputBufferSize specifies the size the input buffer, in bytes.
OutputBufferSize int32
}
// ListenPipe creates a listener on a Windows named pipe path, e.g. \\.\pipe\mypipe.
// The pipe must not already exist.
func ListenPipe(path string, c *PipeConfig) (net.Listener, error) {
var (
sd []byte
err error
)
if c == nil {
c = &PipeConfig{}
}
if c.SecurityDescriptor != "" {
sd, err = SddlToSecurityDescriptor(c.SecurityDescriptor)
if err != nil {
return nil, err
}
}
h, err := makeServerPipeHandle(path, sd, c, true)
if err != nil {
return nil, err
}
l := &win32PipeListener{
firstHandle: h,
path: path,
config: *c,
acceptCh: make(chan (chan acceptResponse)),
closeCh: make(chan int),
doneCh: make(chan int),
}
go l.listenerRoutine()
return l, nil
}
func connectPipe(p *win32File) error {
c, err := p.prepareIo()
if err != nil {
return err
}
defer p.wg.Done()
err = connectNamedPipe(p.handle, &c.o)
_, err = p.asyncIo(c, nil, 0, err)
if err != nil && err != cERROR_PIPE_CONNECTED {
return err
}
return nil
}
func (l *win32PipeListener) Accept() (net.Conn, error) {
ch := make(chan acceptResponse)
select {
case l.acceptCh <- ch:
response := <-ch
err := response.err
if err != nil {
return nil, err
}
if l.config.MessageMode {
return &win32MessageBytePipe{
win32Pipe: win32Pipe{win32File: response.f, path: l.path},
}, nil
}
return &win32Pipe{win32File: response.f, path: l.path}, nil
case <-l.doneCh:
return nil, ErrPipeListenerClosed
}
}
func (l *win32PipeListener) Close() error {
select {
case l.closeCh <- 1:
<-l.doneCh
case <-l.doneCh:
}
return nil
}
func (l *win32PipeListener) Addr() net.Addr {
return pipeAddress(l.path)
}

235
vendor/github.com/Microsoft/go-winio/pkg/guid/guid.go generated vendored Normal file
View File

@ -0,0 +1,235 @@
// Package guid provides a GUID type. The backing structure for a GUID is
// identical to that used by the golang.org/x/sys/windows GUID type.
// There are two main binary encodings used for a GUID, the big-endian encoding,
// and the Windows (mixed-endian) encoding. See here for details:
// https://en.wikipedia.org/wiki/Universally_unique_identifier#Encoding
package guid
import (
"crypto/rand"
"crypto/sha1"
"encoding"
"encoding/binary"
"fmt"
"strconv"
"golang.org/x/sys/windows"
)
// Variant specifies which GUID variant (or "type") of the GUID. It determines
// how the entirety of the rest of the GUID is interpreted.
type Variant uint8
// The variants specified by RFC 4122.
const (
// VariantUnknown specifies a GUID variant which does not conform to one of
// the variant encodings specified in RFC 4122.
VariantUnknown Variant = iota
VariantNCS
VariantRFC4122
VariantMicrosoft
VariantFuture
)
// Version specifies how the bits in the GUID were generated. For instance, a
// version 4 GUID is randomly generated, and a version 5 is generated from the
// hash of an input string.
type Version uint8
var _ = (encoding.TextMarshaler)(GUID{})
var _ = (encoding.TextUnmarshaler)(&GUID{})
// GUID represents a GUID/UUID. It has the same structure as
// golang.org/x/sys/windows.GUID so that it can be used with functions expecting
// that type. It is defined as its own type so that stringification and
// marshaling can be supported. The representation matches that used by native
// Windows code.
type GUID windows.GUID
// NewV4 returns a new version 4 (pseudorandom) GUID, as defined by RFC 4122.
func NewV4() (GUID, error) {
var b [16]byte
if _, err := rand.Read(b[:]); err != nil {
return GUID{}, err
}
g := FromArray(b)
g.setVersion(4) // Version 4 means randomly generated.
g.setVariant(VariantRFC4122)
return g, nil
}
// NewV5 returns a new version 5 (generated from a string via SHA-1 hashing)
// GUID, as defined by RFC 4122. The RFC is unclear on the encoding of the name,
// and the sample code treats it as a series of bytes, so we do the same here.
//
// Some implementations, such as those found on Windows, treat the name as a
// big-endian UTF16 stream of bytes. If that is desired, the string can be
// encoded as such before being passed to this function.
func NewV5(namespace GUID, name []byte) (GUID, error) {
b := sha1.New()
namespaceBytes := namespace.ToArray()
b.Write(namespaceBytes[:])
b.Write(name)
a := [16]byte{}
copy(a[:], b.Sum(nil))
g := FromArray(a)
g.setVersion(5) // Version 5 means generated from a string.
g.setVariant(VariantRFC4122)
return g, nil
}
func fromArray(b [16]byte, order binary.ByteOrder) GUID {
var g GUID
g.Data1 = order.Uint32(b[0:4])
g.Data2 = order.Uint16(b[4:6])
g.Data3 = order.Uint16(b[6:8])
copy(g.Data4[:], b[8:16])
return g
}
func (g GUID) toArray(order binary.ByteOrder) [16]byte {
b := [16]byte{}
order.PutUint32(b[0:4], g.Data1)
order.PutUint16(b[4:6], g.Data2)
order.PutUint16(b[6:8], g.Data3)
copy(b[8:16], g.Data4[:])
return b
}
// FromArray constructs a GUID from a big-endian encoding array of 16 bytes.
func FromArray(b [16]byte) GUID {
return fromArray(b, binary.BigEndian)
}
// ToArray returns an array of 16 bytes representing the GUID in big-endian
// encoding.
func (g GUID) ToArray() [16]byte {
return g.toArray(binary.BigEndian)
}
// FromWindowsArray constructs a GUID from a Windows encoding array of bytes.
func FromWindowsArray(b [16]byte) GUID {
return fromArray(b, binary.LittleEndian)
}
// ToWindowsArray returns an array of 16 bytes representing the GUID in Windows
// encoding.
func (g GUID) ToWindowsArray() [16]byte {
return g.toArray(binary.LittleEndian)
}
func (g GUID) String() string {
return fmt.Sprintf(
"%08x-%04x-%04x-%04x-%012x",
g.Data1,
g.Data2,
g.Data3,
g.Data4[:2],
g.Data4[2:])
}
// FromString parses a string containing a GUID and returns the GUID. The only
// format currently supported is the `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`
// format.
func FromString(s string) (GUID, error) {
if len(s) != 36 {
return GUID{}, fmt.Errorf("invalid GUID %q", s)
}
if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' {
return GUID{}, fmt.Errorf("invalid GUID %q", s)
}
var g GUID
data1, err := strconv.ParseUint(s[0:8], 16, 32)
if err != nil {
return GUID{}, fmt.Errorf("invalid GUID %q", s)
}
g.Data1 = uint32(data1)
data2, err := strconv.ParseUint(s[9:13], 16, 16)
if err != nil {
return GUID{}, fmt.Errorf("invalid GUID %q", s)
}
g.Data2 = uint16(data2)
data3, err := strconv.ParseUint(s[14:18], 16, 16)
if err != nil {
return GUID{}, fmt.Errorf("invalid GUID %q", s)
}
g.Data3 = uint16(data3)
for i, x := range []int{19, 21, 24, 26, 28, 30, 32, 34} {
v, err := strconv.ParseUint(s[x:x+2], 16, 8)
if err != nil {
return GUID{}, fmt.Errorf("invalid GUID %q", s)
}
g.Data4[i] = uint8(v)
}
return g, nil
}
func (g *GUID) setVariant(v Variant) {
d := g.Data4[0]
switch v {
case VariantNCS:
d = (d & 0x7f)
case VariantRFC4122:
d = (d & 0x3f) | 0x80
case VariantMicrosoft:
d = (d & 0x1f) | 0xc0
case VariantFuture:
d = (d & 0x0f) | 0xe0
case VariantUnknown:
fallthrough
default:
panic(fmt.Sprintf("invalid variant: %d", v))
}
g.Data4[0] = d
}
// Variant returns the GUID variant, as defined in RFC 4122.
func (g GUID) Variant() Variant {
b := g.Data4[0]
if b&0x80 == 0 {
return VariantNCS
} else if b&0xc0 == 0x80 {
return VariantRFC4122
} else if b&0xe0 == 0xc0 {
return VariantMicrosoft
} else if b&0xe0 == 0xe0 {
return VariantFuture
}
return VariantUnknown
}
func (g *GUID) setVersion(v Version) {
g.Data3 = (g.Data3 & 0x0fff) | (uint16(v) << 12)
}
// Version returns the GUID version, as defined in RFC 4122.
func (g GUID) Version() Version {
return Version((g.Data3 & 0xF000) >> 12)
}
// MarshalText returns the textual representation of the GUID.
func (g GUID) MarshalText() ([]byte, error) {
return []byte(g.String()), nil
}
// UnmarshalText takes the textual representation of a GUID, and unmarhals it
// into this GUID.
func (g *GUID) UnmarshalText(text []byte) error {
g2, err := FromString(string(text))
if err != nil {
return err
}
*g = g2
return nil
}

202
vendor/github.com/Microsoft/go-winio/privilege.go generated vendored Normal file
View File

@ -0,0 +1,202 @@
// +build windows
package winio
import (
"bytes"
"encoding/binary"
"fmt"
"runtime"
"sync"
"syscall"
"unicode/utf16"
"golang.org/x/sys/windows"
)
//sys adjustTokenPrivileges(token windows.Token, releaseAll bool, input *byte, outputSize uint32, output *byte, requiredSize *uint32) (success bool, err error) [true] = advapi32.AdjustTokenPrivileges
//sys impersonateSelf(level uint32) (err error) = advapi32.ImpersonateSelf
//sys revertToSelf() (err error) = advapi32.RevertToSelf
//sys openThreadToken(thread syscall.Handle, accessMask uint32, openAsSelf bool, token *windows.Token) (err error) = advapi32.OpenThreadToken
//sys getCurrentThread() (h syscall.Handle) = GetCurrentThread
//sys lookupPrivilegeValue(systemName string, name string, luid *uint64) (err error) = advapi32.LookupPrivilegeValueW
//sys lookupPrivilegeName(systemName string, luid *uint64, buffer *uint16, size *uint32) (err error) = advapi32.LookupPrivilegeNameW
//sys lookupPrivilegeDisplayName(systemName string, name *uint16, buffer *uint16, size *uint32, languageId *uint32) (err error) = advapi32.LookupPrivilegeDisplayNameW
const (
SE_PRIVILEGE_ENABLED = 2
ERROR_NOT_ALL_ASSIGNED syscall.Errno = 1300
SeBackupPrivilege = "SeBackupPrivilege"
SeRestorePrivilege = "SeRestorePrivilege"
)
const (
securityAnonymous = iota
securityIdentification
securityImpersonation
securityDelegation
)
var (
privNames = make(map[string]uint64)
privNameMutex sync.Mutex
)
// PrivilegeError represents an error enabling privileges.
type PrivilegeError struct {
privileges []uint64
}
func (e *PrivilegeError) Error() string {
s := ""
if len(e.privileges) > 1 {
s = "Could not enable privileges "
} else {
s = "Could not enable privilege "
}
for i, p := range e.privileges {
if i != 0 {
s += ", "
}
s += `"`
s += getPrivilegeName(p)
s += `"`
}
return s
}
// RunWithPrivilege enables a single privilege for a function call.
func RunWithPrivilege(name string, fn func() error) error {
return RunWithPrivileges([]string{name}, fn)
}
// RunWithPrivileges enables privileges for a function call.
func RunWithPrivileges(names []string, fn func() error) error {
privileges, err := mapPrivileges(names)
if err != nil {
return err
}
runtime.LockOSThread()
defer runtime.UnlockOSThread()
token, err := newThreadToken()
if err != nil {
return err
}
defer releaseThreadToken(token)
err = adjustPrivileges(token, privileges, SE_PRIVILEGE_ENABLED)
if err != nil {
return err
}
return fn()
}
func mapPrivileges(names []string) ([]uint64, error) {
var privileges []uint64
privNameMutex.Lock()
defer privNameMutex.Unlock()
for _, name := range names {
p, ok := privNames[name]
if !ok {
err := lookupPrivilegeValue("", name, &p)
if err != nil {
return nil, err
}
privNames[name] = p
}
privileges = append(privileges, p)
}
return privileges, nil
}
// EnableProcessPrivileges enables privileges globally for the process.
func EnableProcessPrivileges(names []string) error {
return enableDisableProcessPrivilege(names, SE_PRIVILEGE_ENABLED)
}
// DisableProcessPrivileges disables privileges globally for the process.
func DisableProcessPrivileges(names []string) error {
return enableDisableProcessPrivilege(names, 0)
}
func enableDisableProcessPrivilege(names []string, action uint32) error {
privileges, err := mapPrivileges(names)
if err != nil {
return err
}
p, _ := windows.GetCurrentProcess()
var token windows.Token
err = windows.OpenProcessToken(p, windows.TOKEN_ADJUST_PRIVILEGES|windows.TOKEN_QUERY, &token)
if err != nil {
return err
}
defer token.Close()
return adjustPrivileges(token, privileges, action)
}
func adjustPrivileges(token windows.Token, privileges []uint64, action uint32) error {
var b bytes.Buffer
binary.Write(&b, binary.LittleEndian, uint32(len(privileges)))
for _, p := range privileges {
binary.Write(&b, binary.LittleEndian, p)
binary.Write(&b, binary.LittleEndian, action)
}
prevState := make([]byte, b.Len())
reqSize := uint32(0)
success, err := adjustTokenPrivileges(token, false, &b.Bytes()[0], uint32(len(prevState)), &prevState[0], &reqSize)
if !success {
return err
}
if err == ERROR_NOT_ALL_ASSIGNED {
return &PrivilegeError{privileges}
}
return nil
}
func getPrivilegeName(luid uint64) string {
var nameBuffer [256]uint16
bufSize := uint32(len(nameBuffer))
err := lookupPrivilegeName("", &luid, &nameBuffer[0], &bufSize)
if err != nil {
return fmt.Sprintf("<unknown privilege %d>", luid)
}
var displayNameBuffer [256]uint16
displayBufSize := uint32(len(displayNameBuffer))
var langID uint32
err = lookupPrivilegeDisplayName("", &nameBuffer[0], &displayNameBuffer[0], &displayBufSize, &langID)
if err != nil {
return fmt.Sprintf("<unknown privilege %s>", string(utf16.Decode(nameBuffer[:bufSize])))
}
return string(utf16.Decode(displayNameBuffer[:displayBufSize]))
}
func newThreadToken() (windows.Token, error) {
err := impersonateSelf(securityImpersonation)
if err != nil {
return 0, err
}
var token windows.Token
err = openThreadToken(getCurrentThread(), syscall.TOKEN_ADJUST_PRIVILEGES|syscall.TOKEN_QUERY, false, &token)
if err != nil {
rerr := revertToSelf()
if rerr != nil {
panic(rerr)
}
return 0, err
}
return token, nil
}
func releaseThreadToken(h windows.Token) {
err := revertToSelf()
if err != nil {
panic(err)
}
h.Close()
}

128
vendor/github.com/Microsoft/go-winio/reparse.go generated vendored Normal file
View File

@ -0,0 +1,128 @@
package winio
import (
"bytes"
"encoding/binary"
"fmt"
"strings"
"unicode/utf16"
"unsafe"
)
const (
reparseTagMountPoint = 0xA0000003
reparseTagSymlink = 0xA000000C
)
type reparseDataBuffer struct {
ReparseTag uint32
ReparseDataLength uint16
Reserved uint16
SubstituteNameOffset uint16
SubstituteNameLength uint16
PrintNameOffset uint16
PrintNameLength uint16
}
// ReparsePoint describes a Win32 symlink or mount point.
type ReparsePoint struct {
Target string
IsMountPoint bool
}
// UnsupportedReparsePointError is returned when trying to decode a non-symlink or
// mount point reparse point.
type UnsupportedReparsePointError struct {
Tag uint32
}
func (e *UnsupportedReparsePointError) Error() string {
return fmt.Sprintf("unsupported reparse point %x", e.Tag)
}
// DecodeReparsePoint decodes a Win32 REPARSE_DATA_BUFFER structure containing either a symlink
// or a mount point.
func DecodeReparsePoint(b []byte) (*ReparsePoint, error) {
tag := binary.LittleEndian.Uint32(b[0:4])
return DecodeReparsePointData(tag, b[8:])
}
func DecodeReparsePointData(tag uint32, b []byte) (*ReparsePoint, error) {
isMountPoint := false
switch tag {
case reparseTagMountPoint:
isMountPoint = true
case reparseTagSymlink:
default:
return nil, &UnsupportedReparsePointError{tag}
}
nameOffset := 8 + binary.LittleEndian.Uint16(b[4:6])
if !isMountPoint {
nameOffset += 4
}
nameLength := binary.LittleEndian.Uint16(b[6:8])
name := make([]uint16, nameLength/2)
err := binary.Read(bytes.NewReader(b[nameOffset:nameOffset+nameLength]), binary.LittleEndian, &name)
if err != nil {
return nil, err
}
return &ReparsePoint{string(utf16.Decode(name)), isMountPoint}, nil
}
func isDriveLetter(c byte) bool {
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
}
// EncodeReparsePoint encodes a Win32 REPARSE_DATA_BUFFER structure describing a symlink or
// mount point.
func EncodeReparsePoint(rp *ReparsePoint) []byte {
// Generate an NT path and determine if this is a relative path.
var ntTarget string
relative := false
if strings.HasPrefix(rp.Target, `\\?\`) {
ntTarget = `\??\` + rp.Target[4:]
} else if strings.HasPrefix(rp.Target, `\\`) {
ntTarget = `\??\UNC\` + rp.Target[2:]
} else if len(rp.Target) >= 2 && isDriveLetter(rp.Target[0]) && rp.Target[1] == ':' {
ntTarget = `\??\` + rp.Target
} else {
ntTarget = rp.Target
relative = true
}
// The paths must be NUL-terminated even though they are counted strings.
target16 := utf16.Encode([]rune(rp.Target + "\x00"))
ntTarget16 := utf16.Encode([]rune(ntTarget + "\x00"))
size := int(unsafe.Sizeof(reparseDataBuffer{})) - 8
size += len(ntTarget16)*2 + len(target16)*2
tag := uint32(reparseTagMountPoint)
if !rp.IsMountPoint {
tag = reparseTagSymlink
size += 4 // Add room for symlink flags
}
data := reparseDataBuffer{
ReparseTag: tag,
ReparseDataLength: uint16(size),
SubstituteNameOffset: 0,
SubstituteNameLength: uint16((len(ntTarget16) - 1) * 2),
PrintNameOffset: uint16(len(ntTarget16) * 2),
PrintNameLength: uint16((len(target16) - 1) * 2),
}
var b bytes.Buffer
binary.Write(&b, binary.LittleEndian, &data)
if !rp.IsMountPoint {
flags := uint32(0)
if relative {
flags |= 1
}
binary.Write(&b, binary.LittleEndian, flags)
}
binary.Write(&b, binary.LittleEndian, ntTarget16)
binary.Write(&b, binary.LittleEndian, target16)
return b.Bytes()
}

98
vendor/github.com/Microsoft/go-winio/sd.go generated vendored Normal file
View File

@ -0,0 +1,98 @@
// +build windows
package winio
import (
"syscall"
"unsafe"
)
//sys lookupAccountName(systemName *uint16, accountName string, sid *byte, sidSize *uint32, refDomain *uint16, refDomainSize *uint32, sidNameUse *uint32) (err error) = advapi32.LookupAccountNameW
//sys convertSidToStringSid(sid *byte, str **uint16) (err error) = advapi32.ConvertSidToStringSidW
//sys convertStringSecurityDescriptorToSecurityDescriptor(str string, revision uint32, sd *uintptr, size *uint32) (err error) = advapi32.ConvertStringSecurityDescriptorToSecurityDescriptorW
//sys convertSecurityDescriptorToStringSecurityDescriptor(sd *byte, revision uint32, secInfo uint32, sddl **uint16, sddlSize *uint32) (err error) = advapi32.ConvertSecurityDescriptorToStringSecurityDescriptorW
//sys localFree(mem uintptr) = LocalFree
//sys getSecurityDescriptorLength(sd uintptr) (len uint32) = advapi32.GetSecurityDescriptorLength
const (
cERROR_NONE_MAPPED = syscall.Errno(1332)
)
type AccountLookupError struct {
Name string
Err error
}
func (e *AccountLookupError) Error() string {
if e.Name == "" {
return "lookup account: empty account name specified"
}
var s string
switch e.Err {
case cERROR_NONE_MAPPED:
s = "not found"
default:
s = e.Err.Error()
}
return "lookup account " + e.Name + ": " + s
}
type SddlConversionError struct {
Sddl string
Err error
}
func (e *SddlConversionError) Error() string {
return "convert " + e.Sddl + ": " + e.Err.Error()
}
// LookupSidByName looks up the SID of an account by name
func LookupSidByName(name string) (sid string, err error) {
if name == "" {
return "", &AccountLookupError{name, cERROR_NONE_MAPPED}
}
var sidSize, sidNameUse, refDomainSize uint32
err = lookupAccountName(nil, name, nil, &sidSize, nil, &refDomainSize, &sidNameUse)
if err != nil && err != syscall.ERROR_INSUFFICIENT_BUFFER {
return "", &AccountLookupError{name, err}
}
sidBuffer := make([]byte, sidSize)
refDomainBuffer := make([]uint16, refDomainSize)
err = lookupAccountName(nil, name, &sidBuffer[0], &sidSize, &refDomainBuffer[0], &refDomainSize, &sidNameUse)
if err != nil {
return "", &AccountLookupError{name, err}
}
var strBuffer *uint16
err = convertSidToStringSid(&sidBuffer[0], &strBuffer)
if err != nil {
return "", &AccountLookupError{name, err}
}
sid = syscall.UTF16ToString((*[0xffff]uint16)(unsafe.Pointer(strBuffer))[:])
localFree(uintptr(unsafe.Pointer(strBuffer)))
return sid, nil
}
func SddlToSecurityDescriptor(sddl string) ([]byte, error) {
var sdBuffer uintptr
err := convertStringSecurityDescriptorToSecurityDescriptor(sddl, 1, &sdBuffer, nil)
if err != nil {
return nil, &SddlConversionError{sddl, err}
}
defer localFree(sdBuffer)
sd := make([]byte, getSecurityDescriptorLength(sdBuffer))
copy(sd, (*[0xffff]byte)(unsafe.Pointer(sdBuffer))[:len(sd)])
return sd, nil
}
func SecurityDescriptorToSddl(sd []byte) (string, error) {
var sddl *uint16
// The returned string length seems to including an aribtrary number of terminating NULs.
// Don't use it.
err := convertSecurityDescriptorToStringSecurityDescriptor(&sd[0], 1, 0xff, &sddl, nil)
if err != nil {
return "", err
}
defer localFree(uintptr(unsafe.Pointer(sddl)))
return syscall.UTF16ToString((*[0xffff]uint16)(unsafe.Pointer(sddl))[:]), nil
}

3
vendor/github.com/Microsoft/go-winio/syscall.go generated vendored Normal file
View File

@ -0,0 +1,3 @@
package winio
//go:generate go run $GOROOT/src/syscall/mksyscall_windows.go -output zsyscall_windows.go file.go pipe.go sd.go fileinfo.go privilege.go backup.go hvsock.go

View File

@ -0,0 +1,562 @@
// Code generated by 'go generate'; DO NOT EDIT.
package winio
import (
"syscall"
"unsafe"
"golang.org/x/sys/windows"
)
var _ unsafe.Pointer
// Do the interface allocations only once for common
// Errno values.
const (
errnoERROR_IO_PENDING = 997
)
var (
errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING)
)
// errnoErr returns common boxed Errno values, to prevent
// allocations at runtime.
func errnoErr(e syscall.Errno) error {
switch e {
case 0:
return nil
case errnoERROR_IO_PENDING:
return errERROR_IO_PENDING
}
// TODO: add more here, after collecting data on the common
// error values see on Windows. (perhaps when running
// all.bat?)
return e
}
var (
modkernel32 = windows.NewLazySystemDLL("kernel32.dll")
modws2_32 = windows.NewLazySystemDLL("ws2_32.dll")
modntdll = windows.NewLazySystemDLL("ntdll.dll")
modadvapi32 = windows.NewLazySystemDLL("advapi32.dll")
procCancelIoEx = modkernel32.NewProc("CancelIoEx")
procCreateIoCompletionPort = modkernel32.NewProc("CreateIoCompletionPort")
procGetQueuedCompletionStatus = modkernel32.NewProc("GetQueuedCompletionStatus")
procSetFileCompletionNotificationModes = modkernel32.NewProc("SetFileCompletionNotificationModes")
procWSAGetOverlappedResult = modws2_32.NewProc("WSAGetOverlappedResult")
procConnectNamedPipe = modkernel32.NewProc("ConnectNamedPipe")
procCreateNamedPipeW = modkernel32.NewProc("CreateNamedPipeW")
procCreateFileW = modkernel32.NewProc("CreateFileW")
procGetNamedPipeInfo = modkernel32.NewProc("GetNamedPipeInfo")
procGetNamedPipeHandleStateW = modkernel32.NewProc("GetNamedPipeHandleStateW")
procLocalAlloc = modkernel32.NewProc("LocalAlloc")
procNtCreateNamedPipeFile = modntdll.NewProc("NtCreateNamedPipeFile")
procRtlNtStatusToDosErrorNoTeb = modntdll.NewProc("RtlNtStatusToDosErrorNoTeb")
procRtlDosPathNameToNtPathName_U = modntdll.NewProc("RtlDosPathNameToNtPathName_U")
procRtlDefaultNpAcl = modntdll.NewProc("RtlDefaultNpAcl")
procLookupAccountNameW = modadvapi32.NewProc("LookupAccountNameW")
procConvertSidToStringSidW = modadvapi32.NewProc("ConvertSidToStringSidW")
procConvertStringSecurityDescriptorToSecurityDescriptorW = modadvapi32.NewProc("ConvertStringSecurityDescriptorToSecurityDescriptorW")
procConvertSecurityDescriptorToStringSecurityDescriptorW = modadvapi32.NewProc("ConvertSecurityDescriptorToStringSecurityDescriptorW")
procLocalFree = modkernel32.NewProc("LocalFree")
procGetSecurityDescriptorLength = modadvapi32.NewProc("GetSecurityDescriptorLength")
procGetFileInformationByHandleEx = modkernel32.NewProc("GetFileInformationByHandleEx")
procSetFileInformationByHandle = modkernel32.NewProc("SetFileInformationByHandle")
procAdjustTokenPrivileges = modadvapi32.NewProc("AdjustTokenPrivileges")
procImpersonateSelf = modadvapi32.NewProc("ImpersonateSelf")
procRevertToSelf = modadvapi32.NewProc("RevertToSelf")
procOpenThreadToken = modadvapi32.NewProc("OpenThreadToken")
procGetCurrentThread = modkernel32.NewProc("GetCurrentThread")
procLookupPrivilegeValueW = modadvapi32.NewProc("LookupPrivilegeValueW")
procLookupPrivilegeNameW = modadvapi32.NewProc("LookupPrivilegeNameW")
procLookupPrivilegeDisplayNameW = modadvapi32.NewProc("LookupPrivilegeDisplayNameW")
procBackupRead = modkernel32.NewProc("BackupRead")
procBackupWrite = modkernel32.NewProc("BackupWrite")
procbind = modws2_32.NewProc("bind")
)
func cancelIoEx(file syscall.Handle, o *syscall.Overlapped) (err error) {
r1, _, e1 := syscall.Syscall(procCancelIoEx.Addr(), 2, uintptr(file), uintptr(unsafe.Pointer(o)), 0)
if r1 == 0 {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func createIoCompletionPort(file syscall.Handle, port syscall.Handle, key uintptr, threadCount uint32) (newport syscall.Handle, err error) {
r0, _, e1 := syscall.Syscall6(procCreateIoCompletionPort.Addr(), 4, uintptr(file), uintptr(port), uintptr(key), uintptr(threadCount), 0, 0)
newport = syscall.Handle(r0)
if newport == 0 {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func getQueuedCompletionStatus(port syscall.Handle, bytes *uint32, key *uintptr, o **ioOperation, timeout uint32) (err error) {
r1, _, e1 := syscall.Syscall6(procGetQueuedCompletionStatus.Addr(), 5, uintptr(port), uintptr(unsafe.Pointer(bytes)), uintptr(unsafe.Pointer(key)), uintptr(unsafe.Pointer(o)), uintptr(timeout), 0)
if r1 == 0 {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func setFileCompletionNotificationModes(h syscall.Handle, flags uint8) (err error) {
r1, _, e1 := syscall.Syscall(procSetFileCompletionNotificationModes.Addr(), 2, uintptr(h), uintptr(flags), 0)
if r1 == 0 {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func wsaGetOverlappedResult(h syscall.Handle, o *syscall.Overlapped, bytes *uint32, wait bool, flags *uint32) (err error) {
var _p0 uint32
if wait {
_p0 = 1
} else {
_p0 = 0
}
r1, _, e1 := syscall.Syscall6(procWSAGetOverlappedResult.Addr(), 5, uintptr(h), uintptr(unsafe.Pointer(o)), uintptr(unsafe.Pointer(bytes)), uintptr(_p0), uintptr(unsafe.Pointer(flags)), 0)
if r1 == 0 {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func connectNamedPipe(pipe syscall.Handle, o *syscall.Overlapped) (err error) {
r1, _, e1 := syscall.Syscall(procConnectNamedPipe.Addr(), 2, uintptr(pipe), uintptr(unsafe.Pointer(o)), 0)
if r1 == 0 {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func createNamedPipe(name string, flags uint32, pipeMode uint32, maxInstances uint32, outSize uint32, inSize uint32, defaultTimeout uint32, sa *syscall.SecurityAttributes) (handle syscall.Handle, err error) {
var _p0 *uint16
_p0, err = syscall.UTF16PtrFromString(name)
if err != nil {
return
}
return _createNamedPipe(_p0, flags, pipeMode, maxInstances, outSize, inSize, defaultTimeout, sa)
}
func _createNamedPipe(name *uint16, flags uint32, pipeMode uint32, maxInstances uint32, outSize uint32, inSize uint32, defaultTimeout uint32, sa *syscall.SecurityAttributes) (handle syscall.Handle, err error) {
r0, _, e1 := syscall.Syscall9(procCreateNamedPipeW.Addr(), 8, uintptr(unsafe.Pointer(name)), uintptr(flags), uintptr(pipeMode), uintptr(maxInstances), uintptr(outSize), uintptr(inSize), uintptr(defaultTimeout), uintptr(unsafe.Pointer(sa)), 0)
handle = syscall.Handle(r0)
if handle == syscall.InvalidHandle {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func createFile(name string, access uint32, mode uint32, sa *syscall.SecurityAttributes, createmode uint32, attrs uint32, templatefile syscall.Handle) (handle syscall.Handle, err error) {
var _p0 *uint16
_p0, err = syscall.UTF16PtrFromString(name)
if err != nil {
return
}
return _createFile(_p0, access, mode, sa, createmode, attrs, templatefile)
}
func _createFile(name *uint16, access uint32, mode uint32, sa *syscall.SecurityAttributes, createmode uint32, attrs uint32, templatefile syscall.Handle) (handle syscall.Handle, err error) {
r0, _, e1 := syscall.Syscall9(procCreateFileW.Addr(), 7, uintptr(unsafe.Pointer(name)), uintptr(access), uintptr(mode), uintptr(unsafe.Pointer(sa)), uintptr(createmode), uintptr(attrs), uintptr(templatefile), 0, 0)
handle = syscall.Handle(r0)
if handle == syscall.InvalidHandle {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func getNamedPipeInfo(pipe syscall.Handle, flags *uint32, outSize *uint32, inSize *uint32, maxInstances *uint32) (err error) {
r1, _, e1 := syscall.Syscall6(procGetNamedPipeInfo.Addr(), 5, uintptr(pipe), uintptr(unsafe.Pointer(flags)), uintptr(unsafe.Pointer(outSize)), uintptr(unsafe.Pointer(inSize)), uintptr(unsafe.Pointer(maxInstances)), 0)
if r1 == 0 {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func getNamedPipeHandleState(pipe syscall.Handle, state *uint32, curInstances *uint32, maxCollectionCount *uint32, collectDataTimeout *uint32, userName *uint16, maxUserNameSize uint32) (err error) {
r1, _, e1 := syscall.Syscall9(procGetNamedPipeHandleStateW.Addr(), 7, uintptr(pipe), uintptr(unsafe.Pointer(state)), uintptr(unsafe.Pointer(curInstances)), uintptr(unsafe.Pointer(maxCollectionCount)), uintptr(unsafe.Pointer(collectDataTimeout)), uintptr(unsafe.Pointer(userName)), uintptr(maxUserNameSize), 0, 0)
if r1 == 0 {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func localAlloc(uFlags uint32, length uint32) (ptr uintptr) {
r0, _, _ := syscall.Syscall(procLocalAlloc.Addr(), 2, uintptr(uFlags), uintptr(length), 0)
ptr = uintptr(r0)
return
}
func ntCreateNamedPipeFile(pipe *syscall.Handle, access uint32, oa *objectAttributes, iosb *ioStatusBlock, share uint32, disposition uint32, options uint32, typ uint32, readMode uint32, completionMode uint32, maxInstances uint32, inboundQuota uint32, outputQuota uint32, timeout *int64) (status ntstatus) {
r0, _, _ := syscall.Syscall15(procNtCreateNamedPipeFile.Addr(), 14, uintptr(unsafe.Pointer(pipe)), uintptr(access), uintptr(unsafe.Pointer(oa)), uintptr(unsafe.Pointer(iosb)), uintptr(share), uintptr(disposition), uintptr(options), uintptr(typ), uintptr(readMode), uintptr(completionMode), uintptr(maxInstances), uintptr(inboundQuota), uintptr(outputQuota), uintptr(unsafe.Pointer(timeout)), 0)
status = ntstatus(r0)
return
}
func rtlNtStatusToDosError(status ntstatus) (winerr error) {
r0, _, _ := syscall.Syscall(procRtlNtStatusToDosErrorNoTeb.Addr(), 1, uintptr(status), 0, 0)
if r0 != 0 {
winerr = syscall.Errno(r0)
}
return
}
func rtlDosPathNameToNtPathName(name *uint16, ntName *unicodeString, filePart uintptr, reserved uintptr) (status ntstatus) {
r0, _, _ := syscall.Syscall6(procRtlDosPathNameToNtPathName_U.Addr(), 4, uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(ntName)), uintptr(filePart), uintptr(reserved), 0, 0)
status = ntstatus(r0)
return
}
func rtlDefaultNpAcl(dacl *uintptr) (status ntstatus) {
r0, _, _ := syscall.Syscall(procRtlDefaultNpAcl.Addr(), 1, uintptr(unsafe.Pointer(dacl)), 0, 0)
status = ntstatus(r0)
return
}
func lookupAccountName(systemName *uint16, accountName string, sid *byte, sidSize *uint32, refDomain *uint16, refDomainSize *uint32, sidNameUse *uint32) (err error) {
var _p0 *uint16
_p0, err = syscall.UTF16PtrFromString(accountName)
if err != nil {
return
}
return _lookupAccountName(systemName, _p0, sid, sidSize, refDomain, refDomainSize, sidNameUse)
}
func _lookupAccountName(systemName *uint16, accountName *uint16, sid *byte, sidSize *uint32, refDomain *uint16, refDomainSize *uint32, sidNameUse *uint32) (err error) {
r1, _, e1 := syscall.Syscall9(procLookupAccountNameW.Addr(), 7, uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(accountName)), uintptr(unsafe.Pointer(sid)), uintptr(unsafe.Pointer(sidSize)), uintptr(unsafe.Pointer(refDomain)), uintptr(unsafe.Pointer(refDomainSize)), uintptr(unsafe.Pointer(sidNameUse)), 0, 0)
if r1 == 0 {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func convertSidToStringSid(sid *byte, str **uint16) (err error) {
r1, _, e1 := syscall.Syscall(procConvertSidToStringSidW.Addr(), 2, uintptr(unsafe.Pointer(sid)), uintptr(unsafe.Pointer(str)), 0)
if r1 == 0 {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func convertStringSecurityDescriptorToSecurityDescriptor(str string, revision uint32, sd *uintptr, size *uint32) (err error) {
var _p0 *uint16
_p0, err = syscall.UTF16PtrFromString(str)
if err != nil {
return
}
return _convertStringSecurityDescriptorToSecurityDescriptor(_p0, revision, sd, size)
}
func _convertStringSecurityDescriptorToSecurityDescriptor(str *uint16, revision uint32, sd *uintptr, size *uint32) (err error) {
r1, _, e1 := syscall.Syscall6(procConvertStringSecurityDescriptorToSecurityDescriptorW.Addr(), 4, uintptr(unsafe.Pointer(str)), uintptr(revision), uintptr(unsafe.Pointer(sd)), uintptr(unsafe.Pointer(size)), 0, 0)
if r1 == 0 {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func convertSecurityDescriptorToStringSecurityDescriptor(sd *byte, revision uint32, secInfo uint32, sddl **uint16, sddlSize *uint32) (err error) {
r1, _, e1 := syscall.Syscall6(procConvertSecurityDescriptorToStringSecurityDescriptorW.Addr(), 5, uintptr(unsafe.Pointer(sd)), uintptr(revision), uintptr(secInfo), uintptr(unsafe.Pointer(sddl)), uintptr(unsafe.Pointer(sddlSize)), 0)
if r1 == 0 {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func localFree(mem uintptr) {
syscall.Syscall(procLocalFree.Addr(), 1, uintptr(mem), 0, 0)
return
}
func getSecurityDescriptorLength(sd uintptr) (len uint32) {
r0, _, _ := syscall.Syscall(procGetSecurityDescriptorLength.Addr(), 1, uintptr(sd), 0, 0)
len = uint32(r0)
return
}
func getFileInformationByHandleEx(h syscall.Handle, class uint32, buffer *byte, size uint32) (err error) {
r1, _, e1 := syscall.Syscall6(procGetFileInformationByHandleEx.Addr(), 4, uintptr(h), uintptr(class), uintptr(unsafe.Pointer(buffer)), uintptr(size), 0, 0)
if r1 == 0 {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func setFileInformationByHandle(h syscall.Handle, class uint32, buffer *byte, size uint32) (err error) {
r1, _, e1 := syscall.Syscall6(procSetFileInformationByHandle.Addr(), 4, uintptr(h), uintptr(class), uintptr(unsafe.Pointer(buffer)), uintptr(size), 0, 0)
if r1 == 0 {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func adjustTokenPrivileges(token windows.Token, releaseAll bool, input *byte, outputSize uint32, output *byte, requiredSize *uint32) (success bool, err error) {
var _p0 uint32
if releaseAll {
_p0 = 1
} else {
_p0 = 0
}
r0, _, e1 := syscall.Syscall6(procAdjustTokenPrivileges.Addr(), 6, uintptr(token), uintptr(_p0), uintptr(unsafe.Pointer(input)), uintptr(outputSize), uintptr(unsafe.Pointer(output)), uintptr(unsafe.Pointer(requiredSize)))
success = r0 != 0
if true {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func impersonateSelf(level uint32) (err error) {
r1, _, e1 := syscall.Syscall(procImpersonateSelf.Addr(), 1, uintptr(level), 0, 0)
if r1 == 0 {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func revertToSelf() (err error) {
r1, _, e1 := syscall.Syscall(procRevertToSelf.Addr(), 0, 0, 0, 0)
if r1 == 0 {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func openThreadToken(thread syscall.Handle, accessMask uint32, openAsSelf bool, token *windows.Token) (err error) {
var _p0 uint32
if openAsSelf {
_p0 = 1
} else {
_p0 = 0
}
r1, _, e1 := syscall.Syscall6(procOpenThreadToken.Addr(), 4, uintptr(thread), uintptr(accessMask), uintptr(_p0), uintptr(unsafe.Pointer(token)), 0, 0)
if r1 == 0 {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func getCurrentThread() (h syscall.Handle) {
r0, _, _ := syscall.Syscall(procGetCurrentThread.Addr(), 0, 0, 0, 0)
h = syscall.Handle(r0)
return
}
func lookupPrivilegeValue(systemName string, name string, luid *uint64) (err error) {
var _p0 *uint16
_p0, err = syscall.UTF16PtrFromString(systemName)
if err != nil {
return
}
var _p1 *uint16
_p1, err = syscall.UTF16PtrFromString(name)
if err != nil {
return
}
return _lookupPrivilegeValue(_p0, _p1, luid)
}
func _lookupPrivilegeValue(systemName *uint16, name *uint16, luid *uint64) (err error) {
r1, _, e1 := syscall.Syscall(procLookupPrivilegeValueW.Addr(), 3, uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(luid)))
if r1 == 0 {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func lookupPrivilegeName(systemName string, luid *uint64, buffer *uint16, size *uint32) (err error) {
var _p0 *uint16
_p0, err = syscall.UTF16PtrFromString(systemName)
if err != nil {
return
}
return _lookupPrivilegeName(_p0, luid, buffer, size)
}
func _lookupPrivilegeName(systemName *uint16, luid *uint64, buffer *uint16, size *uint32) (err error) {
r1, _, e1 := syscall.Syscall6(procLookupPrivilegeNameW.Addr(), 4, uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(luid)), uintptr(unsafe.Pointer(buffer)), uintptr(unsafe.Pointer(size)), 0, 0)
if r1 == 0 {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func lookupPrivilegeDisplayName(systemName string, name *uint16, buffer *uint16, size *uint32, languageId *uint32) (err error) {
var _p0 *uint16
_p0, err = syscall.UTF16PtrFromString(systemName)
if err != nil {
return
}
return _lookupPrivilegeDisplayName(_p0, name, buffer, size, languageId)
}
func _lookupPrivilegeDisplayName(systemName *uint16, name *uint16, buffer *uint16, size *uint32, languageId *uint32) (err error) {
r1, _, e1 := syscall.Syscall6(procLookupPrivilegeDisplayNameW.Addr(), 5, uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(buffer)), uintptr(unsafe.Pointer(size)), uintptr(unsafe.Pointer(languageId)), 0)
if r1 == 0 {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func backupRead(h syscall.Handle, b []byte, bytesRead *uint32, abort bool, processSecurity bool, context *uintptr) (err error) {
var _p0 *byte
if len(b) > 0 {
_p0 = &b[0]
}
var _p1 uint32
if abort {
_p1 = 1
} else {
_p1 = 0
}
var _p2 uint32
if processSecurity {
_p2 = 1
} else {
_p2 = 0
}
r1, _, e1 := syscall.Syscall9(procBackupRead.Addr(), 7, uintptr(h), uintptr(unsafe.Pointer(_p0)), uintptr(len(b)), uintptr(unsafe.Pointer(bytesRead)), uintptr(_p1), uintptr(_p2), uintptr(unsafe.Pointer(context)), 0, 0)
if r1 == 0 {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func backupWrite(h syscall.Handle, b []byte, bytesWritten *uint32, abort bool, processSecurity bool, context *uintptr) (err error) {
var _p0 *byte
if len(b) > 0 {
_p0 = &b[0]
}
var _p1 uint32
if abort {
_p1 = 1
} else {
_p1 = 0
}
var _p2 uint32
if processSecurity {
_p2 = 1
} else {
_p2 = 0
}
r1, _, e1 := syscall.Syscall9(procBackupWrite.Addr(), 7, uintptr(h), uintptr(unsafe.Pointer(_p0)), uintptr(len(b)), uintptr(unsafe.Pointer(bytesWritten)), uintptr(_p1), uintptr(_p2), uintptr(unsafe.Pointer(context)), 0, 0)
if r1 == 0 {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func bind(s syscall.Handle, name unsafe.Pointer, namelen int32) (err error) {
r1, _, e1 := syscall.Syscall(procbind.Addr(), 3, uintptr(s), uintptr(name), uintptr(namelen))
if r1 == socketError {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}

View File

@ -1,17 +0,0 @@
clone_folder: c:\gopath\src\github.com\containernetworking\cni
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:
- go get -t ./...
- go test -v ./...

View File

@ -1,5 +0,0 @@
bin/
gopath/
*.sw[ponm]
.vagrant
release-*

View File

@ -1,37 +0,0 @@
language: go
dist: trusty
go:
- 1.9.x
- 1.10.x
env:
matrix:
- TARGET=amd64
- TARGET=arm
- TARGET=arm64
- TARGET=ppc64le
- TARGET=s390x
matrix:
fast_finish: true
install:
- go get golang.org/x/tools/cmd/cover
- go get github.com/modocache/gover
- go get github.com/mattn/goveralls
- go get -t ./...
script:
- >
if [ "${TARGET}" == "amd64" ]; then
GOARCH="${TARGET}" ./test.sh;
else
GOARCH="${TARGET}" go list ./... | xargs -n1 go build -v -o /dev/null
fi
notifications:
email: false
git:
depth: 9999999

View File

@ -1,4 +0,0 @@
## Community Code of Conduct
CNI follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md).

View File

@ -1,125 +0,0 @@
# How to Contribute
CNI is [Apache 2.0 licensed](LICENSE) and accepts contributions via GitHub
pull requests. This document outlines some of the conventions on development
workflow, commit message formatting, contact points and other resources to make
it easier to get your contribution accepted.
We gratefully welcome improvements to documentation as well as to code.
# Certificate of Origin
By contributing to this project you agree to the Developer Certificate of
Origin (DCO). This document was created by the Linux Kernel community and is a
simple statement that you, as a contributor, have the legal right to make the
contribution. See the [DCO](DCO) file for details.
# Email and Chat
The project uses the 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
Please avoid emailing maintainers found in the MAINTAINERS file directly. They
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!
## Contribution workflow
This is a rough outline of how to prepare a contribution:
- Create a topic branch from where you want to base your work (usually branched from master).
- Make commits of logical units.
- 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
- 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
- Make sure any new code files have a license header (this is now enforced by automated tests)
- Submit a pull request to the original repository.
## How to run the test suite
We generally require test coverage of any new features or bug fixes.
Here's how you can run the test suite on any system (even Mac or Windows) using
[Vagrant](https://www.vagrantup.com/) and a hypervisor of your choice:
```bash
vagrant up
vagrant ssh
# you're now in a shell in a virtual machine
sudo su
cd /go/src/github.com/containernetworking/cni
# to run the full test suite
./test.sh
# to focus on a particular test suite
cd plugins/main/loopback
go test
```
# Acceptance policy
These things will make a PR more likely to be accepted:
* a well-described requirement
* tests for new code
* tests for old code!
* new code and tests follow the conventions in old code and tests
* a good commit message (see below)
In general, we will merge a PR once two maintainers have endorsed it.
Trivial changes (e.g., corrections to spelling) may get waved through.
For substantial changes, more people may become involved, and you might get asked to resubmit the PR or divide the changes into more than one PR.
### Format of the Commit Message
We follow a rough convention for commit messages that is designed to answer two
questions: what changed and why. The subject line should feature the what and
the body of the commit should describe the why.
```
scripts: add the test-cluster command
this uses tmux to setup a test cluster that you can easily kill and
start for debugging.
Fixes #38
```
The format can be described more formally as follows:
```
<subsystem>: <what changed>
<BLANK LINE>
<why this change was made>
<BLANK LINE>
<footer>
```
The first line is the subject and should be no longer than 70 characters, the
second line is always blank, and other lines should be wrapped at 80 characters.
This allows the message to be easier to read on GitHub as well as in various
git tools.
## 3rd party plugins
So you've built a CNI plugin. Where should it live?
Short answer: We'd be happy to link to it from our [list of 3rd party plugins](README.md#3rd-party-plugins).
But we'd rather you kept the code in your own repo.
Long answer: An advantage of the CNI model is that independent plugins can be
built, distributed and used without any code changes to this repository. While
some widely used plugins (and a few less-popular legacy ones) live in this repo,
we're reluctant to add more.
If you have a good reason why the CNI maintainers should take custody of your
plugin, please open an issue or PR.

View File

@ -1,114 +0,0 @@
# Extension conventions
There are three ways of passing information to plugins using the Container Network Interface (CNI), none of which require the [spec](SPEC.md) to be updated. These are
- plugin specific fields in the JSON config
- `args` field in the JSON config
- `CNI_ARGS` environment variable
This document aims to provide guidance on which method should be used and to provide a convention for how common information should be passed.
Establishing these conventions allows plugins to work across multiple runtimes. This helps both plugins and the runtimes.
## Plugins
* Plugin authors should aim to support these conventions where it makes sense for their plugin. This means they are more likely to "just work" with a wider range of runtimes.
* Plugins should accept arguments according to these conventions if they implement the same basic functionality as other plugins. If plugins have shared functionality that isn't coverered by these conventions then a PR should be opened against this document.
## Runtimes
* Runtime authors should follow these conventions if they want to pass additional information to plugins. This will allow the extra information to be consumed by the widest range of plugins.
* These conventions serve as an abstraction for the runtime. For example, port forwarding is highly implementation specific, but users should be able to select the plugin of their choice without changing the runtime.
# Current conventions
Additional conventions can be created by creating PRs which modify this document.
## Dynamic Plugin specific fields (Capabilities / Runtime Configuration)
[Plugin specific fields](https://github.com/containernetworking/cni/blob/master/SPEC.md#network-configuration) formed part of the original CNI spec and have been present since the initial release.
> 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 may be ignored by plugins.
A plugin can define any additional fields it needs to work properly. It should return an error if it can't act on fields that were expected or where the field values were malformed.
This method of passing information to a plugin is recommended when the following conditions hold:
* The configuration has specific meaning to the plugin (i.e. it's not just general meta data)
* the plugin is expected to act on the configuration or return an error if it can't
Dynamic information (i.e. data that a runtime fills out) should be placed in a `runtimeConfig` section. Plugins can request
that the runtime insert this dynamic configuration by explicitly listing their `capabilities` in the network configuration.
For example, the configuration for a port mapping plugin might look like this to an operator (it should be included as part of a [network configuration list](https://github.com/containernetworking/cni/blob/master/SPEC.md#network-configuration-lists).
```json
{
"name" : "ExamplePlugin",
"type" : "port-mapper",
"capabilities": {"portMappings": true}
}
```
But the runtime would fill in the mappings so the plugin itself would receive something like this.
```json
{
"name" : "ExamplePlugin",
"type" : "port-mapper",
"runtimeConfig": {
"portMappings": [
{"hostPort": 8080, "containerPort": 80, "protocol": "tcp"}
]
}
}
```
### Well-known Capabilities
| 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.<br/> <pre>[<br/> { "hostPort": 8080, "containerPort": 80, "protocol": "tcp" },<br /> { "hostPort": 8000, "containerPort": 8001, "protocol": "udp" }<br /> ]<br /></pre> | 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. <br/><pre>[<br/> [<br/> { "subnet": "10.1.2.0/24", "rangeStart": "10.1.2.3", "rangeEnd": 10.1.2.99", "gateway": "10.1.2.254" } <br/> ]<br/>]</pre> | 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. <pre> { "ingressRate": 2048, "ingressBurst": 1600, "egressRate": 4096, "egressBurst": 1600 } </pre> | none | cni `bandwidth` plugin |
## "args" in network config
`args` in [network config](https://github.com/containernetworking/cni/blob/master/SPEC.md#network-configuration) were introduced as an optional field into the `0.2.0` release of the CNI spec. The first CNI code release that it appeared in was `v0.4.0`.
> 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.
`args` provide a way of providing more structured data than the flat strings that CNI_ARGS can support.
`args` should be used for _optional_ meta-data. Runtimes can place additional data in `args` and plugins that don't understand that data should just ignore it. Runtimes should not require that a plugin understands or consumes that data provided, and so a runtime should not expect to receive an error if the data could not be acted on.
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`.
For example:
```json
{
"cniVersion":"0.2.0",
"name":"net",
"args":{
"cni":{
"labels": [{"key": "app", "value": "myapp"}]
}
},
<REST OF CNI CONFIG HERE>
"ipam":{
<IPAM CONFIG HERE>
}
}
```
| Area | Purpose| Spec and Example | Runtime implementations | Plugin Implementations |
| ----- | ------ | ------------ | ----------------------- | ---------------------- |
| labels | Pass`key=value` labels to plugins | <pre>"labels" : [<br /> { "key" : "app", "value" : "myapp" },<br /> { "key" : "env", "value" : "prod" }<br />] </pre> | none | none |
| ips | Request static IPs | <pre>"ips": ["10.2.2.42", "2001:db8::5"]</pre> | 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.
| 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).|
## 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.
For example, the `bridge` plugin adds the host-side interface to a bridge. So, it should accept any previous result that includes a host-side interface, including `tap` devices. If not called as a chained plugin, it creates a `veth` pair first.
Plugins that meet this convention are usable by a larger set of runtimes and interfaces, including hypervisors and DPDK providers.

View File

@ -1,36 +0,0 @@
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.

View File

@ -1,16 +0,0 @@
# Overview
The `cnitool` is a utility that can be used to test a CNI plugin
without the need for a container runtime. The `cnitool` takes a
`network name` and a `network namespace` and a command to `ADD` or
`DEL`,.i.e, attach or detach containers from a network. The `cnitool`
relies on the following environment variables to operate properly:
* `NETCONFPATH`: This environment variable needs to be set to a
directory. It defaults to `/etc/cni/net.d`. The `cnitool` searches
for CNI configuration files in this directory with the extension
`*.conf` or `*.json`. It loads all the CNI configuration files in
this directory and if it finds a CNI configuration with the `network
name` given to the cnitool it returns the corresponding CNI
configuration, else it returns `nil`.
* `CNI_PATH`: For a given CNI configuration `cnitool` will search for
the corresponding CNI plugin in this path.

View File

@ -1,259 +0,0 @@
# How to upgrade to CNI Specification v0.3.1
The 0.3.0 specification contained a small error. The Result structure's `ip` field should have been renamed to `ips` to be consistent with the IPAM result structure definition; this rename was missed when updating the Result to accommodate multiple IP addresses and interfaces. All first-party CNI plugins (bridge, host-local, etc) were updated to use `ips` (and thus be inconsistent with the 0.3.0 specification) and most other plugins have not been updated to the 0.3.0 specification yet, so few (if any) users should be impacted by this change.
The 0.3.1 specification corrects the Result structure to use the `ips` field name as originally intended. This is the only change between 0.3.0 and 0.3.1.
# How to upgrade to CNI Specification v0.3.0
Version 0.3.0 of the [CNI Specification](../SPEC.md) provides rich information
about container network configuration, including details of network interfaces
and support for multiple IP addresses.
To support this new data, the specification changed in a couple significant
ways that will impact CNI users, plugin authors, and runtime authors.
This document provides guidance for how to upgrade:
- [For CNI Users](#for-cni-users)
- [For Plugin Authors](#for-plugin-authors)
- [For Runtime Authors](#for-runtime-authors)
**Note**: the CNI Spec is versioned independently from the GitHub releases
for this repo. For example, Release v0.4.0 supports Spec version v0.2.0,
and Release v0.5.0 supports Spec v0.3.0.
----
## For CNI Users
If you maintain CNI configuration files for a container runtime that uses CNI,
ensure that the configuration files specify a `cniVersion` field and that the
version there is supported by your container runtime and CNI plugins.
Configuration files without a version field should be given version 0.2.0.
The CNI spec includes example configuration files for
[single plugins](https://github.com/containernetworking/cni/blob/master/SPEC.md#example-configurations)
and for [lists of chained plugins](https://github.com/containernetworking/cni/blob/master/SPEC.md#example-configurations).
Consult the documentation for your runtime and plugins to determine what
CNI spec versions they support. Test any plugin upgrades before deploying to
production. You may find [cnitool](https://github.com/containernetworking/cni/tree/master/cnitool)
useful. Specifically, your configuration version should be the lowest common
version supported by your plugins.
## For Plugin Authors
This section provides guidance for upgrading plugins to CNI Spec Version 0.3.0.
### General guidance for all plugins (language agnostic)
To provide the smoothest upgrade path, **existing plugins should support
multiple versions of the CNI spec**. In particular, plugins with existing
installed bases should add support for CNI spec version 0.3.0 while maintaining
compatibility with older versions.
To do this, two changes are required. First, a plugin should advertise which
CNI spec versions it supports. It does this by responding to the `VERSION`
command with the following JSON data:
```json
{
"cniVersion": "0.3.0",
"supportedVersions": [ "0.1.0", "0.2.0", "0.3.0" ]
}
```
Second, for the `ADD` command, a plugin must respect the `cniVersion` field
provided in the [network configuration JSON](https://github.com/containernetworking/cni/blob/master/SPEC.md#network-configuration).
That field is a request for the plugin to return results of a particular format:
- If the `cniVersion` field is not present, then spec v0.2.0 should be assumed
and v0.2.0 format result JSON returned.
- If the plugin doesn't support the version, the plugin must error.
- Otherwise, the plugin must return a [CNI Result](https://github.com/containernetworking/cni/blob/master/SPEC.md#result)
in the format requested.
Result formats for older CNI spec versions are available in the
[git history for SPEC.md](https://github.com/containernetworking/cni/commits/master/SPEC.md).
For example, suppose a plugin, via its `VERSION` response, advertises CNI specification
support for v0.2.0 and v0.3.0. When it receives `cniVersion` key of `0.2.0`,
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)
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.
For plugin authors, the biggest change is that `types.Result` is now an
interface implemented by concrete struct types in the `types/current` and
`types/020` subpackages.
Internally, plugins should use the `types/current` structs, and convert
to or from specific versions when required. A typical plugin will only need
to do a single conversion. That is when it is about to complete and needs to
print the result JSON in the correct format to stdout. The library
function `types.PrintResult()` simplifies this by converting and printing in
a single call.
Additionally, the plugin should advertise which CNI Spec versions it supports
via the 3rd argument to `skel.PluginMain()`.
Here is some example code
```go
import (
"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"
)
func cmdAdd(args *skel.CmdArgs) error {
// determine spec version to use
var netConf struct {
types.NetConf
// other plugin-specific configuration goes here
}
err := json.Unmarshal(args.StdinData, &netConf)
cniVersion := netConf.CNIVersion
// plugin does its work...
// set up interfaces
// assign addresses, etc
// construct the result
result := &current.Result{
Interfaces: []*current.Interface{ ... },
IPs: []*current.IPs{ ... },
...
}
// print result to stdout, in the format defined by the requested cniVersion
return types.PrintResult(result, cniVersion)
}
func main() {
skel.PluginMain(cmdAdd, cmdDel, version.PluginSupports("0.1.0", "0.2.0", "0.3.0"))
}
```
Alternately, to use the result from a delegated IPAM plugin, the `result`
value might be formed like this:
```go
ipamResult, err := ipam.ExecAdd(netConf.IPAM.Type, args.StdinData)
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)
## For Runtime Authors
This section provides guidance for upgrading container runtimes to support
CNI Spec Version 0.3.0.
### General guidance for all runtimes (language agnostic)
#### Support multiple CNI spec versions
To provide the smoothest upgrade path and support the broadest range of CNI
plugins, **container runtimes should support multiple versions of the CNI spec**.
In particular, runtimes with existing installed bases should add support for CNI
spec version 0.3.0 while maintaining compatibility with older versions.
To support multiple versions of the CNI spec, runtimes should be able to
call both new and legacy plugins, and handle the results from either.
When calling a plugin, the runtime must request that the plugin respond in a
particular format by specifying the `cniVersion` field in the
[Network Configuration](https://github.com/containernetworking/cni/blob/master/SPEC.md#network-configuration)
JSON block. The plugin will then respond with
a [Result](https://github.com/containernetworking/cni/blob/master/SPEC.md#result)
in the format defined by that CNI spec version, and the runtime must parse
and handle this result.
#### Handle errors due to version incompatibility
Plugins may respond with error indicating that they don't support the requested
CNI version (see [Well-known Error Codes](https://github.com/containernetworking/cni/blob/master/SPEC.md#well-known-error-codes)),
e.g.
```json
{
"cniVersion": "0.2.0",
"code": 1,
"msg": "CNI version not supported"
}
```
In that case, the runtime may retry with a lower CNI spec version, or take
some other action.
#### (optional) Discover plugin version support
Runtimes may discover which CNI spec versions are supported by a plugin, by
calling the plugin with the `VERSION` command. The `VERSION` command was
added in CNI spec v0.2.0, so older plugins may not respect it. In the absence
of a successful response to `VERSION`, assume that the plugin only supports
CNI spec v0.1.0.
#### Handle missing data in v0.3.0 results
The Result for the `ADD` command in CNI spec version 0.3.0 includes a new field
`interfaces`. An IP address in the `ip` field may describe which interface
it is assigned to, by placing a numeric index in the `interface` subfield.
However, some plugins which are v0.3.0 compatible may nonetheless omit the
`interfaces` field and/or set the `interface` index value to `-1`. Runtimes
should gracefully handle this situation, unless they have good reason to rely
on the existence of the interface data. In that case, provide the user an
error message that helps diagnose the issue.
### Specific guidance for container runtimes written in Go
Container runtimes 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)
includes important changes to the Golang APIs. Runtimes using these APIs will
require some changes now, but should more-easily handle spec changes and
new features going forward.
For runtimes, the biggest changes to the Go libraries are in the `types` package.
It has been refactored to make working with versioned results simpler. The top-level
`types.Result` is now an opaque interface instead of a struct, and APIs exposed by
other packages, such as the high-level `libcni` package, have been updated to use
this interface. Concrete types are now per-version subpackages. The `types/current`
subpackage contains the latest (spec v0.3.0) types.
When up-converting older result types to spec v0.3.0, fields new in
spec v0.3.0 (like `interfaces`) may be empty. Conversely, when
down-converting v0.3.0 results to an older version, any data in those fields
will be lost.
| From | 0.1 | 0.2 | 0.3 |
|--------|-----|-----|-----|
| To 0.1 | ✔ | ✔ | x |
| To 0.2 | ✔ | ✔ | x |
| To 0.3 | ✴ | ✴ | ✔ |
Key:
> ✔ : lossless conversion <br>
> ✴ : higher-version output may have empty fields <br>
> x : lower-version output is missing some data <br>
A container runtime should use `current.NewResultFromResult()` to convert the
opaque `types.Result` to a concrete `current.Result` struct. It may then
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)
// upconvert result to the current 0.3.0 spec
result, err := current.NewResultFromResult(resultInterface)
// use the result fields ....
for _, ip := range result.IPs { ... }
```

View File

@ -1,44 +0,0 @@
# CNI Governance
This document defines project governance for the project.
## Voting
The CNI project employs "organization voting" to ensure no single organization can dominate the project.
Individuals not associated with or employed by a company or organization are allowed one organization vote.
Each company or organization (regardless of the number of maintainers associated with or employed by that company/organization) receives one organization vote.
In other words, if two maintainers are employed by Company X, two by Company Y, two by Company Z, and one maintainer is an un-affiliated individual, a total of four "organization votes" are possible; one for X, one for Y, one for Z, and one for the un-affiliated individual.
Any maintainer from an organization may cast the vote for that organization.
For formal votes, a specific statement of what is being voted on should be added to the relevant github issue or PR, and a link to that issue or PR added to the maintainers meeting agenda document.
Maintainers should indicate their yes/no vote on that issue or PR, and after a suitable period of time, the votes will be tallied and the outcome noted.
## Changes in Maintainership
New maintainers are proposed by an existing maintainer and are elected by a 2/3 majority organization vote.
Maintainers can be removed by a 2/3 majority organization vote.
## Approving PRs
Non-specification-related PRs may be merged after receiving at least two organization votes.
Changes to the CNI Specification also follow the normal PR approval process (eg, 2 organization votes), but any maintainer can request that the approval require a 2/3 majority organization vote.
## Github Project Administration
Maintainers will be added to the containernetworking GitHub organization and added to the GitHub cni-maintainers team, and made a GitHub maintainer of that team.
After 6 months a maintainer will be made an "owner" of the GitHub organization.
## Changes in Governance
All changes in Governance require a 2/3 majority organization vote.
## Other Changes
Unless specified above, all other changes to the project require a 2/3 majority organization vote.
Additionally, any maintainer may request that any change require a 2/3 majority organization vote.

View File

@ -1,6 +0,0 @@
Bryan Boreham <bryan@weave.works> (@bboreham)
Casey Callendrello <casey.callendrello@coreos.com> (@squeed)
Dan Williams <dcbw@redhat.com> (@dcbw)
Gabe Rosenhouse <grosenhouse@pivotal.io> (@rosenhouse)
Matt Dupre <matt@tigera.io> (@matthewdupre)
Stefan Junker <stefan.junker@coreos.com> (@steveeJ)

View File

@ -1,198 +0,0 @@
[![Linux Build Status](https://travis-ci.org/containernetworking/cni.svg?branch=master)](https://travis-ci.org/containernetworking/cni)
[![Windows Build Status](https://ci.appveyor.com/api/projects/status/wtrkou8oow7x533e/branch/master?svg=true)](https://ci.appveyor.com/project/cni-bot/cni/branch/master)
[![Coverage Status](https://coveralls.io/repos/github/containernetworking/cni/badge.svg?branch=master)](https://coveralls.io/github/containernetworking/cni?branch=master)
[![Slack Status](https://cryptic-tundra-43194.herokuapp.com/badge.svg)](https://cryptic-tundra-43194.herokuapp.com/)
![CNI Logo](logo.png)
---
# 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).
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).
---
# CNI - the Container Network Interface
## What is CNI?
CNI (_Container Network Interface_), a [Cloud Native Computing Foundation](https://cncf.io) project, consists of a specification and libraries for writing plugins to configure network interfaces in Linux containers, along with a number of supported plugins.
CNI concerns itself only with network connectivity of containers and removing allocated resources when the container is deleted.
Because of this focus, CNI has a wide range of support and the specification is simple to implement.
As well as the [specification](SPEC.md), this repository contains the Go source code of a [library for integrating CNI into applications](libcni) and an [example command-line tool](cnitool) for executing CNI plugins. A [separate repository contains reference plugins](https://github.com/containernetworking/plugins) and a template for making new plugins.
The template code makes it straight-forward to create a CNI plugin for an existing container networking project.
CNI also makes a good framework for creating a new container networking project from scratch.
## Why develop CNI?
Application containers on Linux are a rapidly evolving area, and within this area networking is not well addressed as it is highly environment-specific.
We believe that many container runtimes and orchestrators will seek to solve the same problem of making the network layer pluggable.
To avoid duplication, we think it is prudent to define a common interface between the network plugins and container execution: hence we put forward this specification, along with libraries for Go and a set of plugins.
## 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/)
- [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/)
### 3rd party plugins
- [Project Calico - a layer 3 virtual network](https://github.com/projectcalico/calico-cni)
- [Weave - a multi-host Docker network](https://github.com/weaveworks/weave)
- [Contiv Networking - policy networking for various use cases](https://github.com/contiv/netplugin)
- [SR-IOV](https://github.com/hustcat/sriov-cni)
- [Cilium - BPF & XDP for containers](https://github.com/cilium/cilium)
- [Infoblox - enterprise IP address management for containers](https://github.com/infobloxopen/cni-infoblox)
- [Multus - a Multi plugin](https://github.com/Intel-Corp/multus-cni)
- [Romana - Layer 3 CNI plugin supporting network policy for Kubernetes](https://github.com/romana/kube)
- [CNI-Genie - generic CNI network plugin](https://github.com/Huawei-PaaS/CNI-Genie)
- [Nuage CNI - Nuage Networks SDN plugin for network policy kubernetes support ](https://github.com/nuagenetworks/nuage-cni)
- [Silk - a CNI plugin designed for Cloud Foundry](https://github.com/cloudfoundry-incubator/silk)
- [Linen - a CNI plugin designed for overlay networks with Open vSwitch and fit in SDN/OpenFlow network environment](https://github.com/John-Lin/linen-cni)
- [Vhostuser - a Dataplane network plugin - Supports OVS-DPDK & VPP](https://github.com/intel/vhost-user-net-plugin)
- [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)
The CNI team also maintains some [core plugins in a separate repository](https://github.com/containernetworking/plugins).
## Contributing to CNI
We welcome contributions, including [bug reports](https://github.com/containernetworking/cni/issues), and code and documentation improvements.
If you intend to contribute to code or documentation, please read [CONTRIBUTING.md](CONTRIBUTING.md). Also see the [contact section](#contact) in this README.
## How do I use CNI?
### 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.
### Reference Plugins
The CNI project maintains a set of [reference plugins](https://github.com/containernetworking/plugins) that implement the CNI specification.
NOTE: the reference plugins used to live in this repository but have been split out into a [separate repository](https://github.com/containernetworking/plugins) as of May 2017.
### Running the plugins
After building and installing the [reference plugins](https://github.com/containernetworking/plugins), you can use the `priv-net-run.sh` and `docker-run.sh` scripts in the `scripts/` directory to exercise the plugins.
**note - priv-net-run.sh depends on `jq`**
Start out by creating a netconf file to describe a network:
```bash
$ mkdir -p /etc/cni/net.d
$ cat >/etc/cni/net.d/10-mynet.conf <<EOF
{
"cniVersion": "0.2.0",
"name": "mynet",
"type": "bridge",
"bridge": "cni0",
"isGateway": true,
"ipMasq": true,
"ipam": {
"type": "host-local",
"subnet": "10.22.0.0/16",
"routes": [
{ "dst": "0.0.0.0/0" }
]
}
}
EOF
$ cat >/etc/cni/net.d/99-loopback.conf <<EOF
{
"cniVersion": "0.2.0",
"type": "loopback"
}
EOF
```
The directory `/etc/cni/net.d` is the default location in which the scripts will look for net configurations.
Next, build the plugins:
```bash
$ cd $GOPATH/src/github.com/containernetworking/plugins
$ ./build.sh
```
Finally, execute a command (`ifconfig` in this example) in a private network namespace that has joined the `mynet` network:
```bash
$ CNI_PATH=$GOPATH/src/github.com/containernetworking/plugins/bin
$ cd $GOPATH/src/github.com/containernetworking/cni/scripts
$ sudo CNI_PATH=$CNI_PATH ./priv-net-run.sh ifconfig
eth0 Link encap:Ethernet HWaddr f2:c2:6f:54:b8:2b
inet addr:10.22.0.2 Bcast:0.0.0.0 Mask:255.255.0.0
inet6 addr: fe80::f0c2:6fff:fe54:b82b/64 Scope:Link
UP BROADCAST MULTICAST MTU:1500 Metric:1
RX packets:1 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:1 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:90 (90.0 B) TX bytes:0 (0.0 B)
lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
inet6 addr: ::1/128 Scope:Host
UP LOOPBACK RUNNING MTU:65536 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
```
The environment variable `CNI_PATH` tells the scripts and library where to look for plugin executables.
## Running a Docker container with network namespace set up by CNI plugins
Use the instructions in the previous section to define a netconf and build the plugins.
Next, docker-run.sh script wraps `docker run`, to execute the plugins prior to entering the container:
```bash
$ CNI_PATH=$GOPATH/src/github.com/containernetworking/plugins/bin
$ cd $GOPATH/src/github.com/containernetworking/cni/scripts
$ sudo CNI_PATH=$CNI_PATH ./docker-run.sh --rm busybox:latest ifconfig
eth0 Link encap:Ethernet HWaddr fa:60:70:aa:07:d1
inet addr:10.22.0.2 Bcast:0.0.0.0 Mask:255.255.0.0
inet6 addr: fe80::f860:70ff:feaa:7d1/64 Scope:Link
UP BROADCAST MULTICAST MTU:1500 Metric:1
RX packets:1 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:1 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:90 (90.0 B) TX bytes:0 (0.0 B)
lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
inet6 addr: ::1/128 Scope:Host
UP LOOPBACK RUNNING MTU:65536 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
```
## What might CNI do in the future?
CNI currently covers a wide range of needs for network configuration due to it simple model and API.
However, in the future CNI might want to branch out into other directions:
- Dynamic updates to existing network configuration
- Dynamic policies for network bandwidth and firewall rules
If these topics of are interest, please contact the team via the mailing list or IRC and find some like-minded people in the community to put a proposal together.
## Contact
For any questions about CNI, please reach out on the mailing list:
- Email: [cni-dev](https://groups.google.com/forum/#!forum/cni-dev)
- IRC: #[containernetworking](irc://irc.freenode.org:6667/#containernetworking) channel on freenode.org
- Slack: [containernetworking.slack.com](https://cryptic-tundra-43194.herokuapp.com)

View File

@ -1,34 +0,0 @@
# Release process
## Resulting artifacts
Creating a new release produces the following artifacts:
- Binaries (stored in the `release-<TAG>` directory) :
- `cni-<PLATFORM>-<VERSION>.tgz` binaries
- `cni-<VERSION>.tgz` binary (copy of amd64 platform binary)
- `sha1`, `sha256` and `sha512` files for the above files.
## Preparing for a release
1. Releases are performed by maintainers and should usually be discussed and planned at a maintainer meeting.
- Choose the version number. It should be prefixed with `v`, e.g. `v1.2.3`
- Take a quick scan through the PRs and issues to make sure there isn't anything crucial that _must_ be in the next release.
- Create a draft of the release note
- Discuss the level of testing that's needed and create a test plan if sensible
- Check what version of `go` is used in the build container, updating it if there's a new stable release.
## Creating the release artifacts
1. Make sure you are on the master branch and don't have any local uncommitted changes.
1. Create a signed tag for the release `git tag -s $VERSION` (Ensure that GPG keys are created and added to GitHub)
1. Run the release script from the root of the repository
- `scripts/release.sh`
- The script requires Docker and ensures that a consistent environment is used.
- The artifacts will now be present in the `release-<TAG>` directory.
1. Test these binaries according to the test plan.
## Publishing the release
1. Push the tag to git `git push origin <TAG>`
1. Create a release on Github, using the tag which was just pushed.
1. Attach all the artifacts from the release directory.
1. Add the release note to the release.
1. Announce the release on at least the CNI mailing, IRC and Slack.

View File

@ -1,23 +0,0 @@
# CNI Roadmap
This document defines a high level roadmap for CNI development.
The list below is not complete, and we advise to get the current project state from the [milestones defined in GitHub](https://github.com/containernetworking/cni/milestones).
## CNI Milestones
### [v0.6.0](https://github.com/containernetworking/cni/milestones/v0.6.0)
- Plugin composition functionality
- IPv6 support
- Strategy and tooling for backwards compatibility
- Integrate build artefact generation with CI
### [v1.0.0](https://github.com/containernetworking/cni/milestones/v1.0.0)
- More precise specification language
- GET action
- Conformance test suite for CNI plugins (both reference and 3rd party)
- Stable SPEC
- Complete test coverage
- Signed release binaries

View File

@ -1,739 +0,0 @@
# Container Network Interface Specification
## Version
This is CNI **spec** version **0.4.0-dev**. This spec contains **unreleased** changes.
Note that this is **independent from the version of the CNI library and plugins** in this repository (e.g. the versions of [releases](https://github.com/containernetworking/cni/releases)).
#### Released versions
Released versions of the spec are available as Git tags.
| tag | spec permalink | major changes |
| ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------- | --------------------------------- |
| [`spec-v0.3.1`](https://github.com/containernetworking/cni/releases/tag/spec-v0.3.1) | [spec at v0.3.1](https://github.com/containernetworking/cni/blob/spec-v0.3.1/SPEC.md) | none (typo fix only) |
| [`spec-v0.3.0`](https://github.com/containernetworking/cni/releases/tag/spec-v0.3.0) | [spec at v0.3.0](https://github.com/containernetworking/cni/blob/spec-v0.3.0/SPEC.md) | rich result type, plugin chaining |
| [`spec-v0.2.0`](https://github.com/containernetworking/cni/releases/tag/spec-v0.2.0) | [spec at v0.2.0](https://github.com/containernetworking/cni/blob/spec-v0.2.0/SPEC.md) | VERSION command |
| [`spec-v0.1.0`](https://github.com/containernetworking/cni/releases/tag/spec-v0.1.0) | [spec at v0.1.0](https://github.com/containernetworking/cni/blob/spec-v0.1.0/SPEC.md) | initial version |
*Do not rely on these tags being stable. In the future, we may change our mind about which particular commit is the right marker for a given historical spec version.*
## Overview
This document proposes a generic plugin-based networking solution for application containers on Linux, the _Container Networking Interface_, or _CNI_.
It is derived from the [rkt Networking Proposal][rkt-networking-proposal], which aimed to satisfy many of the [design considerations][rkt-networking-design] for networking in [rkt][rkt-github].
For the purposes of this proposal, we define two terms very specifically:
- _container_ can be considered synonymous with a [Linux _network namespace_][namespaces]. What unit this corresponds to depends on a particular container runtime implementation: for example, in implementations of the [App Container Spec][appc-github] like rkt, each _pod_ runs in a unique network namespace. In [Docker][docker], on the other hand, network namespaces generally exist for each separate Docker container.
- _network_ refers to a group of entities that are uniquely addressable that can communicate amongst each other. This could be either an individual container (as specified above), a machine, or some other network device (e.g. a router). Containers can be conceptually _added to_ or _removed from_ one or more networks.
This document aims to specify the interface between "runtimes" and "plugins". Whilst there are certain well known fields, runtimes may wish to pass additional information to plugins. These extentions are not part of this specification but are documented as [conventions](CONVENTIONS.md). The key words "must", "must not", "required", "shall", "shall not", "should", "should not", "recommended", "may" and "optional" are used as specified in [RFC 2119][rfc-2119].
[rkt-networking-proposal]: https://docs.google.com/a/coreos.com/document/d/1PUeV68q9muEmkHmRuW10HQ6cHgd4819_67pIxDRVNlM/edit#heading=h.ievko3xsjwxd
[rkt-networking-design]:
https://docs.google.com/a/coreos.com/document/d/1CTAL4gwqRofjxyp4tTkbgHtAwb2YCcP14UEbHNizd8g
[rkt-github]: https://github.com/coreos/rkt
[namespaces]: http://man7.org/linux/man-pages/man7/namespaces.7.html
[appc-github]: https://github.com/appc/spec
[docker]: https://docker.com
[rfc-2119]: https://www.ietf.org/rfc/rfc2119.txt
## General considerations
- The container runtime must create a new network namespace for the container before invoking any plugins.
- The runtime must then determine which networks this container should belong to, and for each network, which plugins must be executed.
- The network configuration is in JSON format and can easily be stored in a file. The network configuration includes mandatory fields such as "name" and "type" as well as plugin (type) specific ones. The network configuration allows for fields to change values between invocations. For this purpose there is an optional field "args" which must contain the varying information.
- The container runtime must add the container to each network by executing the corresponding plugins for each network sequentially.
- Upon completion of the container lifecycle, the runtime must execute the plugins in reverse order (relative to the order in which they were executed to add the container) to disconnect the container from the networks.
- The container runtime must not invoke parallel operations for the same container, but is allowed to invoke parallel operations for different containers.
- The container runtime must order ADD and DEL operations for a container, such that ADD is always eventually followed by a corresponding DEL. DEL may be followed by additional DELs but plugins should handle multiple DELs permissively (i.e. plugin DEL should be idempotent).
- A container must be uniquely identified by a ContainerID. Plugins that store state should do so using a primary key of `(network name, CNI_CONTAINERID, CNI_IFNAME)`.
- A runtime must not call ADD twice (without a corresponding DEL) for the same `(network name, container id, name of the interface inside the container)`. This implies that a given container ID may be added to a specific network more than once only if each addition is done with a different interface name.
## CNI Plugin
### Overview
Each CNI plugin must be implemented as an executable that is invoked by the container management system (e.g. rkt or Kubernetes).
A CNI plugin is responsible for inserting a network interface into the container network namespace (e.g. one end of a veth pair) and making any necessary changes on the host (e.g. attaching the other end of the veth into a bridge).
It should then assign the IP to the interface and setup the routes consistent with the IP Address Management section by invoking appropriate IPAM plugin.
### Parameters
The operations that CNI plugins must support are:
- `ADD`: Add container to network
- Parameters:
- **Container ID**. A unique plaintext identifier for a container, allocated by the runtime. Must not be empty.
- **Network namespace path**. This represents the path to the network namespace to be added, i.e. /proc/[pid]/ns/net or a bind-mount/link to it.
- **Network configuration**. This is a JSON document describing a network to which a container can be joined. The schema is described below.
- **Extra arguments**. This provides an alternative mechanism to allow simple configuration of CNI plugins on a per-container basis.
- **Name of the interface inside the container**. This is the name that should be assigned to the interface created inside the container (network namespace); consequently it must comply with the standard Linux restrictions on interface names.
- Result:
- **Interfaces list**. Depending on the plugin, this can include the sandbox (eg, container or hypervisor) interface name and/or the host interface name, the hardware addresses of each interface, and details about the sandbox (if any) the interface is in.
- **IP configuration assigned to each interface**. The IPv4 and/or IPv6 addresses, gateways, and routes assigned to sandbox and/or host interfaces.
- **DNS information**. Dictionary that includes DNS information for nameservers, domain, search domains and options.
- `DEL`: Delete container from network
- Parameters:
- **Container ID**, as defined above.
- **Network namespace path**, as defined above.
- **Network configuration**, as defined above.
- **Extra arguments**, as defined above.
- **Name of the interface inside the container**, as defined above.
- All parameters should be the same as those passed to the corresponding add operation.
- A delete operation should release all resources held by the supplied containerid in the configured network.
- If there was a known previous `ADD` or `GET` action for the container, the runtime MUST add a `prevResult` field to the configuration JSON of the plugin (or all plugins in a chain), which MUST be the `Result` of the immediately previous `ADD` or `GET` action in JSON format ([see below](#network-configuration-list-runtime-examples)).
- When `CNI_NETNS` and/or `prevResult` are not provided, the plugin should clean up as many resources as possible (e.g. releasing IPAM allocations) and return a successful response.
- If the runtime cached the `Result` of a previous `ADD` or `GET` response for a given container, it must delete that cached response on a successful `DEL` for that container.
- `GET`: Get container network configuration
- Parameters:
- **Container ID**, as defined for `ADD`.
- **Network namespace path**, as defined for `ADD`.
- **Network configuration**, as defined for `ADD`.
- **Extra arguments**, as defined for `ADD`.
- **Name of the interface inside the container**, as defined for `ADD`.
- Result:
- The plugin should return the same result as an `ADD` action for the same inputs.
- **Interfaces list**, as defined for `ADD`
- **IP configuration assigned to each interface**, as defined for `ADD`
- **DNS information**, as defined for `ADD`
- This action should return the same `Result` object as an `ADD` action for the same inputs. The result should not change over the lifetime of the container.
- The plugin should return an error if any general internal state is unexpected. For example, if the plugin's data storage is missing or corrupt, or its control plane is unavailable, it should return an error.
- The plugin should NOT return an error if its expected sandbox state (eg interfaces, IP addresses, routes, etc) is not found, as subsequent elements in the plugin's chain may alter sandbox state.
- A runtime may call `GET` at any time; but if `GET` is called for a container before an `ADD` or after a `DEL` for that container, the plugin should return error 3 to indicate the container is unknown (see [Well-known Error Codes](#well-known-error-codes) section).
- If the previous action for the container was `ADD` or `GET`, the runtime must add a `prevResult` field to the configuration JSON of the plugin (or all plugins in the chain), which must be the `Result` of that previous `ADD` or `GET` action in JSON format ([see below](#network-configuration-list-runtime-examples)).
- `VERSION`: Report version
- Parameters: NONE.
- Result: information about the CNI spec versions supported by the plugin
```
{
"cniVersion": "0.4.0", // the version of the CNI spec in use for this output
"supportedVersions": [ "0.1.0", "0.2.0", "0.3.0", "0.3.1", "0.4.0" ] // the list of CNI spec versions that this plugin supports
}
```
Runtimes must use the type of network (see [Network Configuration](#network-configuration) below) as the name of the executable to invoke.
Runtimes should then look for this executable in a list of predefined directories (the list of directories is not prescribed by this specification). Once found, it must invoke the executable using the following environment variables for argument passing:
- `CNI_COMMAND`: indicates the desired operation; `ADD`, `DEL`, `GET`, or `VERSION`.
- `CNI_CONTAINERID`: Container ID
- `CNI_NETNS`: Path to network namespace file
- `CNI_IFNAME`: Interface name to set up; if the plugin is unable to use this interface name it must return an error
- `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"
- `CNI_PATH`: List of paths to search for CNI plugin executables. Paths are separated by an OS-specific list separator; for example ':' on Linux and ';' on Windows
Network configuration in JSON format must be streamed to the plugin through stdin. This means it is not tied to a particular file on disk and may contain information which changes between invocations.
### Result
Note that IPAM plugins should return an abbreviated `Result` structure as described in [IP Allocation](#ip-allocation).
Plugins must indicate success with a return code of zero and the following JSON printed to stdout in the case of the ADD command. The `ips` and `dns` items should be the same output as was returned by the IPAM plugin (see [IP Allocation](#ip-allocation) for details) except that the plugin should fill in the `interface` indexes appropriately, which are missing from IPAM plugin output since IPAM plugins should be unaware of interfaces.
```
{
"cniVersion": "0.4.0",
"interfaces": [ (this key omitted by IPAM plugins)
{
"name": "<name>",
"mac": "<MAC address>", (required if L2 addresses are meaningful)
"sandbox": "<netns path or hypervisor identifier>" (required for container/hypervisor interfaces, empty/omitted for host interfaces)
}
],
"ips": [
{
"version": "<4-or-6>",
"address": "<ip-and-prefix-in-CIDR>",
"gateway": "<ip-address-of-the-gateway>", (optional)
"interface": <numeric index into 'interfaces' list>
},
...
],
"routes": [ (optional)
{
"dst": "<ip-and-prefix-in-cidr>",
"gw": "<ip-of-next-hop>" (optional)
},
...
]
"dns": {
"nameservers": <list-of-nameservers> (optional)
"domain": <name-of-local-domain> (optional)
"search": <list-of-additional-search-domains> (optional)
"options": <list-of-options> (optional)
}
}
```
`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).
`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.
- `mac` (string): the hardware address of the interface.
If L2 addresses are not meaningful for the plugin then this field is optional.
- `sandbox` (string): container/namespace-based environments should return the full filesystem path to the network namespace of that sandbox.
Hypervisor/VM-based plugins should return an ID unique to the virtualized sandbox the interface was created in.
This item must be provided for interfaces created or moved into a sandbox like a network namespace or a hypervisor/VM.
The `ips` field is a list of IP configuration information.
See the [IP well-known structure](#ips) 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.
The specification does not declare how this information must be processed by CNI consumers.
Examples include generating an `/etc/resolv.conf` file to be injected into the container filesystem or running a DNS forwarder on the host.
Errors must be indicated by a non-zero return code and the following JSON being printed to stdout:
```
{
"cniVersion": "0.4.0",
"code": <numeric-error-code>,
"msg": <short-error-message>,
"details": <long-error-message> (optional)
}
```
`cniVersion` specifies a [Semantic Version 2.0](http://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.
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.
- `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:
- `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
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.
### Example configurations
```json
{
"cniVersion": "0.4.0",
"name": "dbnet",
"type": "bridge",
// type (plugin) specific
"bridge": "cni0",
"ipam": {
"type": "host-local",
// ipam specific
"subnet": "10.1.0.0/16",
"gateway": "10.1.0.1"
},
"dns": {
"nameservers": [ "10.1.0.1" ]
}
}
```
```json
{
"cniVersion": "0.4.0",
"name": "pci",
"type": "ovs",
// type (plugin) specific
"bridge": "ovs0",
"vxlanID": 42,
"ipam": {
"type": "dhcp",
"routes": [ { "dst": "10.3.0.0/16" }, { "dst": "10.4.0.0/16" } ]
}
// args may be ignored by plugins
"args": {
"labels" : {
"appVersion" : "1.0"
}
}
}
```
```json
{
"cniVersion": "0.4.0",
"name": "wan",
"type": "macvlan",
// ipam specific
"ipam": {
"type": "dhcp",
"routes": [ { "dst": "10.0.0.0/8", "gw": "10.0.0.1" } ]
},
"dns": {
"nameservers": [ "10.0.0.1" ]
}
}
```
### Network Configuration Lists
Network configuration lists provide a mechanism to run multiple CNI plugins for a single container in a defined order, passing the result of each plugin to the next plugin.
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.
- `name` (string): Network name. This should be unique across all containers on the host (or other administrative domain).
- `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.
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.
#### 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.
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.
#### Example network configuration lists
```json
{
"cniVersion": "0.4.0",
"name": "dbnet",
"plugins": [
{
"type": "bridge",
// type (plugin) specific
"bridge": "cni0",
// args may be ignored by plugins
"args": {
"labels" : {
"appVersion" : "1.0"
}
},
"ipam": {
"type": "host-local",
// ipam specific
"subnet": "10.1.0.0/16",
"gateway": "10.1.0.1"
},
"dns": {
"nameservers": [ "10.1.0.1" ]
}
},
{
"type": "tuning",
"sysctl": {
"net.core.somaxconn": "500"
}
}
]
}
```
#### Network configuration list runtime examples
Given the network configuration list JSON [shown above](#example-network-configuration-lists) the container runtime would perform the following steps for the `ADD` action.
Note that the runtime adds the `cniVersion` and `name` fields from configuration list to the configuration JSON passed to each plugin, to ensure consistent versioning and names for all plugins in the list.
1) first call the `bridge` plugin with the following JSON:
```json
{
"cniVersion": "0.4.0",
"name": "dbnet",
"type": "bridge",
"bridge": "cni0",
"args": {
"labels" : {
"appVersion" : "1.0"
}
},
"ipam": {
"type": "host-local",
// ipam specific
"subnet": "10.1.0.0/16",
"gateway": "10.1.0.1"
},
"dns": {
"nameservers": [ "10.1.0.1" ]
}
}
```
2) next call the `tuning` plugin with the following JSON, including the `prevResult` field containing the JSON response from the `bridge` plugin:
```json
{
"cniVersion": "0.4.0",
"name": "dbnet",
"type": "tuning",
"sysctl": {
"net.core.somaxconn": "500"
},
"prevResult": {
"ips": [
{
"version": "4",
"address": "10.0.0.5/32",
"interface": 2
}
],
"interfaces": [
{
"name": "cni0",
"mac": "00:11:22:33:44:55",
},
{
"name": "veth3243",
"mac": "55:44:33:22:11:11",
},
{
"name": "eth0",
"mac": "99:88:77:66:55:44",
"sandbox": "/var/run/netns/blue",
}
],
"dns": {
"nameservers": [ "10.1.0.1" ]
}
}
}
```
Given the same network configuration JSON list, the container runtime would perform the following steps for the `GET` action.
1) first call the `bridge` plugin with the following JSON, including the `prevResult` field containing the JSON response from the `ADD` operation:
```json
{
"cniVersion": "0.4.0",
"name": "dbnet",
"type": "bridge",
"bridge": "cni0",
"args": {
"labels" : {
"appVersion" : "1.0"
}
},
"ipam": {
"type": "host-local",
// ipam specific
"subnet": "10.1.0.0/16",
"gateway": "10.1.0.1"
},
"dns": {
"nameservers": [ "10.1.0.1" ]
}
"prevResult": {
"ips": [
{
"version": "4",
"address": "10.0.0.5/32",
"interface": 2
}
],
"interfaces": [
{
"name": "cni0",
"mac": "00:11:22:33:44:55",
},
{
"name": "veth3243",
"mac": "55:44:33:22:11:11",
},
{
"name": "eth0",
"mac": "99:88:77:66:55:44",
"sandbox": "/var/run/netns/blue",
}
],
"dns": {
"nameservers": [ "10.1.0.1" ]
}
}
}
```
2) next call the `tuning` plugin with the following JSON, including the `prevResult` field containing the JSON response from the `bridge` plugin:
```json
{
"cniVersion": "0.4.0",
"name": "dbnet",
"type": "tuning",
"sysctl": {
"net.core.somaxconn": "500"
},
"prevResult": {
"ips": [
{
"version": "4",
"address": "10.0.0.5/32",
"interface": 2
}
],
"interfaces": [
{
"name": "cni0",
"mac": "00:11:22:33:44:55",
},
{
"name": "veth3243",
"mac": "55:44:33:22:11:11",
},
{
"name": "eth0",
"mac": "99:88:77:66:55:44",
"sandbox": "/var/run/netns/blue",
}
],
"dns": {
"nameservers": [ "10.1.0.1" ]
}
}
}
```
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.
1) first call the `tuning` plugin with the following JSON, including the `prevResult` field containing the JSON response from the `GET` action:
```json
{
"cniVersion": "0.4.0",
"name": "dbnet",
"type": "tuning",
"sysctl": {
"net.core.somaxconn": "500"
},
"prevResult": {
"ips": [
{
"version": "4",
"address": "10.0.0.5/32",
"interface": 2
}
],
"interfaces": [
{
"name": "cni0",
"mac": "00:11:22:33:44:55",
},
{
"name": "veth3243",
"mac": "55:44:33:22:11:11",
},
{
"name": "eth0",
"mac": "99:88:77:66:55:44",
"sandbox": "/var/run/netns/blue",
}
],
"dns": {
"nameservers": [ "10.1.0.1" ]
}
}
}
```
2) next call the `bridge` plugin with the following JSON, including the `prevResult` field containing the JSON response from the `GET` action:
```json
{
"cniVersion": "0.4.0",
"name": "dbnet",
"type": "bridge",
"bridge": "cni0",
"args": {
"labels" : {
"appVersion" : "1.0"
}
},
"ipam": {
"type": "host-local",
// ipam specific
"subnet": "10.1.0.0/16",
"gateway": "10.1.0.1"
},
"dns": {
"nameservers": [ "10.1.0.1" ]
},
"prevResult": {
"ips": [
{
"version": "4",
"address": "10.0.0.5/32",
"interface": 2
}
],
"interfaces": [
{
"name": "cni0",
"mac": "00:11:22:33:44:55",
},
{
"name": "veth3243",
"mac": "55:44:33:22:11:11",
},
{
"name": "eth0",
"mac": "99:88:77:66:55:44",
"sandbox": "/var/run/netns/blue",
}
],
"dns": {
"nameservers": [ "10.1.0.1" ]
}
}
}
```
### IP Allocation
As part of its operation, a CNI plugin is expected to assign (and maintain) an IP address to the interface and install any necessary routes relevant for that interface. This gives the CNI plugin great flexibility but also places a large burden on it. Many CNI plugins would need to have the same code to support several IP management schemes that users may desire (e.g. dhcp, host-local).
To lessen the burden and make IP management strategy be orthogonal to the type of CNI plugin, we define a second type of plugin -- IP Address Management Plugin (IPAM plugin). It is however the responsibility of the CNI plugin to invoke the IPAM plugin at the proper moment in its execution. The IPAM plugin must determine the interface IP/subnet, Gateway and Routes and return this information to the "main" plugin to apply. The IPAM plugin may obtain the information via a protocol (e.g. dhcp), data stored on a local filesystem, the "ipam" section of the Network Configuration file or a combination of the above.
#### IP Address Management (IPAM) Interface
Like CNI plugins, the IPAM plugins are invoked by running an executable. The executable is searched for in a predefined list of paths, indicated to the CNI plugin via `CNI_PATH`. The IPAM Plugin must receive all the same environment variables that were passed in to the CNI plugin. Just like the CNI plugin, IPAM plugins receive the network configuration via stdin.
Success must be indicated by a zero return code and the following JSON being printed to stdout (in the case of the ADD command):
```
{
"cniVersion": "0.4.0",
"ips": [
{
"version": "<4-or-6>",
"address": "<ip-and-prefix-in-CIDR>",
"gateway": "<ip-address-of-the-gateway>" (optional)
},
...
],
"routes": [ (optional)
{
"dst": "<ip-and-prefix-in-cidr>",
"gw": "<ip-of-next-hop>" (optional)
},
...
]
"dns": {
"nameservers": <list-of-nameservers> (optional)
"domain": <name-of-local-domain> (optional)
"search": <list-of-search-domains> (optional)
"options": <list-of-options> (optional)
}
}
```
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).
The `ips` field is a list of IP configuration information.
See the [IP well-known structure](#ips) 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.
Errors and logs are communicated in the same way as the CNI plugin. See [CNI Plugin Result](#result) section for details.
IPAM plugin examples:
- **host-local**: Select an unused (by other containers on the same host) IP within the specified range.
- **dhcp**: Use DHCP protocol to acquire and maintain a lease. The DHCP requests will be sent via the created container interface; therefore, the associated network must support broadcast.
#### Notes
- Routes are expected to be added with a 0 metric.
- A default route may be specified via "0.0.0.0/0". Since another network might have already configured the default route, the CNI plugin should be prepared to skip over its default route definition.
### Well-known Structures
#### IPs
```
"ips": [
{
"version": "<4-or-6>",
"address": "<ip-and-prefix-in-CIDR>",
"gateway": "<ip-address-of-the-gateway>", (optional)
"interface": <numeric index into 'interfaces' list> (not required for IPAM plugins)
},
...
]
```
The `ips` field is a list of IP configuration information determined by the plugin. Each item is a dictionary describing of IP configuration for a network interface.
IP configuration for multiple network interfaces and multiple IP configurations for a single interface may be returned as separate items in the `ips` list.
All properties known to the plugin should be provided, even if not strictly required.
- `version` (string): either "4" or "6" and corresponds to the IP version of the addresses in the entry.
All IP addresses and gateways provided must be valid for the given `version`.
- `address` (string): an IP address in CIDR notation (eg "192.168.1.3/24").
- `gateway` (string): the default gateway for this subnet, if one exists.
It does not instruct the CNI plugin to add any routes with this gateway: routes to add are specified separately via the `routes` field.
An example use of this value is for the CNI `bridge` plugin to add this IP address to the Linux bridge to make it a gateway.
- `interface` (uint): the index into the `interfaces` list for a [CNI Plugin Result](#result) indicating which interface this IP configuration should be applied to.
IPAM plugins should not return this key since they have no information about network interfaces.
#### Routes
```
"routes": [
{
"dst": "<ip-and-prefix-in-cidr>",
"gw": "<ip-of-next-hop>" (optional)
},
...
]
```
- 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).
#### DNS
```
"dns": {
"nameservers": <list-of-nameservers> (optional)
"domain": <name-of-local-domain> (optional)
"search": <list-of-additional-search-domains> (optional)
"options": <list-of-options> (optional)
}
```
The `dns` field contains a dictionary consisting of common DNS information.
- `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.
See [CNI Plugin Result](#result) section for more information.
## Well-known Error Codes
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).

View File

@ -1,22 +0,0 @@
# -*- mode: ruby -*-
# vi: set ft=ruby :
Vagrant.configure(2) do |config|
config.vm.box = "bento/ubuntu-16.04"
config.vm.synced_folder ".", "/go/src/github.com/containernetworking/cni"
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
echo 'export GOPATH=/go; export PATH=/usr/local/go/bin:$GOPATH/bin:$PATH' >> /root/.bashrc
eval `tail -n1 /root/.bashrc`
cd /go/src/github.com/containernetworking/cni
SHELL
end

View File

@ -1,47 +0,0 @@
# cnitool
`cnitool` is a simple program that executes a CNI configuration. It will
add or remove an interface in an already-created network namespace.
## Example invocation
First, install cnitool:
```
go install github.com/containernetworking/cni/cnitool
```
Then, check out and build the plugins. All commands should be run from this directory.
```
git clone https://github.com/containernetworking/plugins.git
cd plugins
./build.sh
```
Create a network configuration
```
echo '{"cniVersion":"0.3.1","name":"myptp","type":"ptp","ipMasq":true,"ipam":{"type":"host-local","subnet":"172.16.29.0/24","routes":[{"dst":"0.0.0.0/0"}]}}' | sudo tee /etc/cni/net.d/10-myptp.conf
```
Create a network namespace. This will be called `testing`:
```
sudo ip netns add testing
```
Add the container to the network:
```
sudo CNI_PATH=./bin cnitool add myptp /var/run/netns/testing
```
Test that it works:
```
sudo ip -n testing addr
sudo ip netns exec testing ping -c 1 4.2.2.2
```
And clean up:
```
sudo CNI_PATH=./bin cnitool del myptp /var/run/netns/testing
sudo ip netns del testing
```

View File

@ -1,135 +0,0 @@
// Copyright 2015 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 (
"crypto/sha512"
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/containernetworking/cni/libcni"
)
const (
EnvCNIPath = "CNI_PATH"
EnvNetDir = "NETCONFPATH"
EnvCapabilityArgs = "CAP_ARGS"
EnvCNIArgs = "CNI_ARGS"
DefaultNetDir = "/etc/cni/net.d"
CmdAdd = "add"
CmdDel = "del"
)
func parseArgs(args string) ([][2]string, error) {
var result [][2]string
pairs := strings.Split(args, ";")
for _, pair := range pairs {
kv := strings.Split(pair, "=")
if len(kv) != 2 || kv[0] == "" || kv[1] == "" {
return nil, fmt.Errorf("invalid CNI_ARGS pair %q", pair)
}
result = append(result, [2]string{kv[0], kv[1]})
}
return result, nil
}
func main() {
if len(os.Args) < 3 {
usage()
return
}
netdir := os.Getenv(EnvNetDir)
if netdir == "" {
netdir = DefaultNetDir
}
netconf, err := libcni.LoadConfList(netdir, os.Args[2])
if err != nil {
exit(err)
}
var capabilityArgs map[string]interface{}
capabilityArgsValue := os.Getenv(EnvCapabilityArgs)
if len(capabilityArgsValue) > 0 {
if err = json.Unmarshal([]byte(capabilityArgsValue), &capabilityArgs); err != nil {
exit(err)
}
}
var cniArgs [][2]string
args := os.Getenv(EnvCNIArgs)
if len(args) > 0 {
cniArgs, err = parseArgs(args)
if err != nil {
exit(err)
}
}
netns := os.Args[3]
netns, err = filepath.Abs(netns)
if err != nil {
exit(err)
}
// Generate the containerid by hashing the netns path
s := sha512.Sum512([]byte(netns))
containerID := fmt.Sprintf("cnitool-%x", s[:10])
cninet := libcni.NewCNIConfig(filepath.SplitList(os.Getenv(EnvCNIPath)), nil)
rt := &libcni.RuntimeConf{
ContainerID: containerID,
NetNS: netns,
IfName: "eth0",
Args: cniArgs,
CapabilityArgs: capabilityArgs,
}
switch os.Args[1] {
case CmdAdd:
result, err := cninet.AddNetworkList(netconf, rt)
if result != nil {
_ = result.Print()
}
exit(err)
case CmdDel:
exit(cninet.DelNetworkList(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 <net> <netns>\n", exe, CmdAdd)
fmt.Fprintf(os.Stderr, " %s %s <net> <netns>\n", exe, CmdDel)
os.Exit(1)
}
func exit(err error) {
if err != nil {
fmt.Fprintf(os.Stderr, "%s\n", err)
os.Exit(1)
}
os.Exit(0)
}

View File

@ -15,6 +15,7 @@
package libcni
import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
@ -57,20 +58,26 @@ 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
GetNetworkListCachedResult(net *NetworkConfigList, rt *RuntimeConf) (types.Result, 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 +127,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 +165,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 +222,52 @@ 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
if gtet, err := version.GreaterThanOrEqualTo(list.CNIVersion, "0.4.0"); err != nil {
return nil, err
} else if !gtet {
return nil, fmt.Errorf("configuration version %q does not support the GET command", list.CNIVersion)
}
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 c.addOrGetNetworkList("GET", cachedResult, list, rt)
}
func (c *CNIConfig) delNetwork(name, cniVersion string, net *NetworkConfig, prevResult types.Result, rt *RuntimeConf) error {
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 {
@ -285,11 +279,53 @@ 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("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 err
} else if !gtet {
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 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 nil
}
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 {
return err
}
newConf, err := buildOneConfig(name, cniVersion, net, prevResult, rt)
if err != nil {
return err
}
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 +334,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 +350,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 +389,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)
}
// =====

File diff suppressed because it is too large Load Diff

View File

@ -1,97 +0,0 @@
// 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 libcni_test
import (
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"github.com/containernetworking/cni/libcni"
"github.com/containernetworking/cni/pkg/version/legacy_examples"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/onsi/gomega/gexec"
)
var _ = Describe("Backwards compatibility", func() {
var cacheDirPath string
BeforeEach(func() {
var err error
cacheDirPath, err = ioutil.TempDir("", "cni_cachedir")
Expect(err).NotTo(HaveOccurred())
})
AfterEach(func() {
Expect(os.RemoveAll(cacheDirPath)).To(Succeed())
})
It("correctly handles the response from a legacy plugin", func() {
example := legacy_examples.V010
pluginPath, err := example.Build()
Expect(err).NotTo(HaveOccurred())
netConf, err := libcni.ConfFromBytes([]byte(fmt.Sprintf(
`{ "name": "old-thing", "type": "%s" }`, example.Name)))
Expect(err).NotTo(HaveOccurred())
runtimeConf := &libcni.RuntimeConf{
ContainerID: "some-container-id",
NetNS: "/some/netns/path",
IfName: "eth0",
CacheDir: cacheDirPath,
}
cniConfig := libcni.NewCNIConfig([]string{filepath.Dir(pluginPath)}, nil)
result, err := cniConfig.AddNetwork(netConf, runtimeConf)
Expect(err).NotTo(HaveOccurred())
Expect(result).To(Equal(legacy_examples.ExpectedResult))
Expect(os.RemoveAll(pluginPath)).To(Succeed())
})
It("correctly handles the request from a runtime with an older libcni", func() {
if runtime.GOOS == "windows" {
Skip("cannot build old runtime on windows")
}
example := legacy_examples.V010_Runtime
binPath, err := example.Build()
Expect(err).NotTo(HaveOccurred())
for _, configName := range example.NetConfs {
conf, err := example.GenerateNetConf(configName)
if err != nil {
Fail("Failed to generate config name " + configName + ": " + err.Error())
}
defer conf.Cleanup()
cmd := exec.Command(binPath, pluginDirs...)
cmd.Stdin = strings.NewReader(conf.Config)
session, err := gexec.Start(cmd, GinkgoWriter, GinkgoWriter)
Expect(err).NotTo(HaveOccurred())
Eventually(session).Should(gexec.Exit(0))
}
})
})

View File

@ -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{}

View File

@ -1,477 +0,0 @@
// 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 libcni_test
import (
"io/ioutil"
"os"
"path/filepath"
"github.com/containernetworking/cni/libcni"
"github.com/containernetworking/cni/pkg/types"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Loading configuration from disk", func() {
Describe("LoadConf", func() {
var (
configDir string
pluginConfig []byte
)
BeforeEach(func() {
var err error
configDir, err = ioutil.TempDir("", "plugin-conf")
Expect(err).NotTo(HaveOccurred())
pluginConfig = []byte(`{ "name": "some-plugin", "type": "foobar", "some-key": "some-value" }`)
Expect(ioutil.WriteFile(filepath.Join(configDir, "50-whatever.conf"), pluginConfig, 0600)).To(Succeed())
})
AfterEach(func() {
Expect(os.RemoveAll(configDir)).To(Succeed())
})
It("finds the network config file for the plugin of the given type", func() {
netConfig, err := libcni.LoadConf(configDir, "some-plugin")
Expect(err).NotTo(HaveOccurred())
Expect(netConfig).To(Equal(&libcni.NetworkConfig{
Network: &types.NetConf{
Name: "some-plugin",
Type: "foobar",
},
Bytes: pluginConfig,
}))
})
Context("when the config directory does not exist", func() {
BeforeEach(func() {
Expect(os.RemoveAll(configDir)).To(Succeed())
})
It("returns a useful error", func() {
_, err := libcni.LoadConf(configDir, "some-plugin")
Expect(err).To(MatchError(libcni.NoConfigsFoundError{Dir: configDir}))
})
})
Context("when the config file is .json extension instead of .conf", func() {
BeforeEach(func() {
Expect(os.Remove(configDir + "/50-whatever.conf")).To(Succeed())
pluginConfig = []byte(`{ "name": "some-plugin", "some-key": "some-value", "type": "foobar" }`)
Expect(ioutil.WriteFile(filepath.Join(configDir, "50-whatever.json"), pluginConfig, 0600)).To(Succeed())
})
It("finds the network config file for the plugin of the given type", func() {
netConfig, err := libcni.LoadConf(configDir, "some-plugin")
Expect(err).NotTo(HaveOccurred())
Expect(netConfig).To(Equal(&libcni.NetworkConfig{
Network: &types.NetConf{
Name: "some-plugin",
Type: "foobar",
},
Bytes: pluginConfig,
}))
})
})
Context("when there is no config for the desired plugin", func() {
It("returns a useful error", func() {
_, err := libcni.LoadConf(configDir, "some-other-plugin")
Expect(err).To(MatchError(ContainSubstring(`no net configuration with name "some-other-plugin" in`)))
})
})
Context("when a config file is malformed", func() {
BeforeEach(func() {
Expect(ioutil.WriteFile(filepath.Join(configDir, "00-bad.conf"), []byte(`{`), 0600)).To(Succeed())
})
It("returns a useful error", func() {
_, err := libcni.LoadConf(configDir, "some-plugin")
Expect(err).To(MatchError(`error parsing configuration: unexpected end of JSON input`))
})
})
Context("when the config is in a nested subdir", func() {
BeforeEach(func() {
subdir := filepath.Join(configDir, "subdir1", "subdir2")
Expect(os.MkdirAll(subdir, 0700)).To(Succeed())
pluginConfig = []byte(`{ "name": "deep", "some-key": "some-value" }`)
Expect(ioutil.WriteFile(filepath.Join(subdir, "90-deep.conf"), pluginConfig, 0600)).To(Succeed())
})
It("will not find the config", func() {
_, err := libcni.LoadConf(configDir, "deep")
Expect(err).To(MatchError(HavePrefix("no net configuration with name")))
})
})
})
Describe("Capabilities", func() {
var configDir string
BeforeEach(func() {
var err error
configDir, err = ioutil.TempDir("", "plugin-conf")
Expect(err).NotTo(HaveOccurred())
pluginConfig := []byte(`{ "name": "some-plugin", "type": "noop", "cniVersion": "0.3.1", "capabilities": { "portMappings": true, "somethingElse": true, "noCapability": false } }`)
Expect(ioutil.WriteFile(filepath.Join(configDir, "50-whatever.conf"), pluginConfig, 0600)).To(Succeed())
})
AfterEach(func() {
Expect(os.RemoveAll(configDir)).To(Succeed())
})
It("reads plugin capabilities from network config", func() {
netConfig, err := libcni.LoadConf(configDir, "some-plugin")
Expect(err).NotTo(HaveOccurred())
Expect(netConfig.Network.Capabilities).To(Equal(map[string]bool{
"portMappings": true,
"somethingElse": true,
"noCapability": false,
}))
})
})
Describe("ConfFromFile", func() {
Context("when the file cannot be opened", func() {
It("returns a useful error", func() {
_, err := libcni.ConfFromFile("/tmp/nope/not-here")
Expect(err).To(MatchError(HavePrefix(`error reading /tmp/nope/not-here: open /tmp/nope/not-here`)))
})
})
Context("when the file is missing 'type'", func() {
var fileName, configDir string
BeforeEach(func() {
var err error
configDir, err = ioutil.TempDir("", "plugin-conf")
Expect(err).NotTo(HaveOccurred())
fileName = filepath.Join(configDir, "50-whatever.conf")
pluginConfig := []byte(`{ "name": "some-plugin", "some-key": "some-value" }`)
Expect(ioutil.WriteFile(fileName, pluginConfig, 0600)).To(Succeed())
})
AfterEach(func() {
Expect(os.RemoveAll(configDir)).To(Succeed())
})
It("returns a useful error", func() {
_, err := libcni.ConfFromFile(fileName)
Expect(err).To(MatchError(`error parsing configuration: missing 'type'`))
})
})
})
Describe("ConfFromBytes", func() {
Context("when the config is missing 'type'", func() {
It("returns a useful error", func() {
_, err := libcni.ConfFromBytes([]byte(`{ "name": "some-plugin", "some-key": "some-value" }`))
Expect(err).To(MatchError(`error parsing configuration: missing 'type'`))
})
})
})
Describe("LoadConfList", func() {
var (
configDir string
configList []byte
)
BeforeEach(func() {
var err error
configDir, err = ioutil.TempDir("", "plugin-conf")
Expect(err).NotTo(HaveOccurred())
configList = []byte(`{
"name": "some-list",
"cniVersion": "0.2.0",
"plugins": [
{
"type": "host-local",
"subnet": "10.0.0.1/24"
},
{
"type": "bridge",
"mtu": 1400
},
{
"type": "port-forwarding",
"ports": {"20.0.0.1:8080": "80"}
}
]
}`)
Expect(ioutil.WriteFile(filepath.Join(configDir, "50-whatever.conflist"), configList, 0600)).To(Succeed())
})
AfterEach(func() {
Expect(os.RemoveAll(configDir)).To(Succeed())
})
It("finds the network config file for the plugin of the given type", 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",
Plugins: []*libcni.NetworkConfig{
{
Network: &types.NetConf{Type: "host-local"},
Bytes: []byte(`{"subnet":"10.0.0.1/24","type":"host-local"}`),
},
{
Network: &types.NetConf{Type: "bridge"},
Bytes: []byte(`{"mtu":1400,"type":"bridge"}`),
},
{
Network: &types.NetConf{Type: "port-forwarding"},
Bytes: []byte(`{"ports":{"20.0.0.1:8080":"80"},"type":"port-forwarding"}`),
},
},
Bytes: configList,
}))
})
Context("when there is a config file with the same name as the list", func() {
BeforeEach(func() {
configFile := []byte(`{
"name": "some-list",
"cniVersion": "0.2.0",
"type": "bridge"
}`)
Expect(ioutil.WriteFile(filepath.Join(configDir, "49-whatever.conf"), configFile, 0600)).To(Succeed())
})
It("Loads the config list first", func() {
netConfigList, err := libcni.LoadConfList(configDir, "some-list")
Expect(err).NotTo(HaveOccurred())
Expect(len(netConfigList.Plugins)).To(Equal(3))
})
It("falls back to the config file", func() {
Expect(os.Remove(filepath.Join(configDir, "50-whatever.conflist"))).To(Succeed())
netConfigList, err := libcni.LoadConfList(configDir, "some-list")
Expect(err).NotTo(HaveOccurred())
Expect(len(netConfigList.Plugins)).To(Equal(1))
Expect(netConfigList.Plugins[0].Network.Type).To(Equal("bridge"))
})
})
Context("when the config directory does not exist", func() {
BeforeEach(func() {
Expect(os.RemoveAll(configDir)).To(Succeed())
})
It("returns a useful error", func() {
_, err := libcni.LoadConfList(configDir, "some-plugin")
Expect(err).To(MatchError(libcni.NoConfigsFoundError{Dir: configDir}))
})
})
Context("when there is no config for the desired plugin list", func() {
It("returns a useful error", func() {
_, err := libcni.LoadConfList(configDir, "some-other-plugin")
Expect(err).To(MatchError(libcni.NotFoundError{Dir: configDir, Name: "some-other-plugin"}))
})
})
Context("when a config file is malformed", func() {
BeforeEach(func() {
Expect(ioutil.WriteFile(filepath.Join(configDir, "00-bad.conflist"), []byte(`{`), 0600)).To(Succeed())
})
It("returns a useful error", func() {
_, err := libcni.LoadConfList(configDir, "some-plugin")
Expect(err).To(MatchError(`error parsing configuration list: unexpected end of JSON input`))
})
})
Context("when the config is in a nested subdir", func() {
BeforeEach(func() {
subdir := filepath.Join(configDir, "subdir1", "subdir2")
Expect(os.MkdirAll(subdir, 0700)).To(Succeed())
configList = []byte(`{
"name": "deep",
"cniVersion": "0.2.0",
"plugins": [
{
"type": "host-local",
"subnet": "10.0.0.1/24"
},
]
}`)
Expect(ioutil.WriteFile(filepath.Join(subdir, "90-deep.conflist"), configList, 0600)).To(Succeed())
})
It("will not find the config", func() {
_, err := libcni.LoadConfList(configDir, "deep")
Expect(err).To(MatchError(HavePrefix("no net configuration with name")))
})
})
})
Describe("ConfListFromFile", func() {
Context("when the file cannot be opened", func() {
It("returns a useful error", func() {
_, err := libcni.ConfListFromFile("/tmp/nope/not-here")
Expect(err).To(MatchError(HavePrefix(`error reading /tmp/nope/not-here: open /tmp/nope/not-here`)))
})
})
})
Describe("InjectConf", func() {
var testNetConfig *libcni.NetworkConfig
BeforeEach(func() {
testNetConfig = &libcni.NetworkConfig{Network: &types.NetConf{Name: "some-plugin", Type: "foobar"},
Bytes: []byte(`{ "name": "some-plugin", "type": "foobar" }`)}
})
Context("when function parameters are incorrect", func() {
It("returns unmarshal error", func() {
conf := &libcni.NetworkConfig{Network: &types.NetConf{Name: "some-plugin"},
Bytes: []byte(`{ cc cc cc}`)}
_, err := libcni.InjectConf(conf, map[string]interface{}{"": nil})
Expect(err).To(MatchError(HavePrefix(`unmarshal existing network bytes`)))
})
It("returns key error", func() {
_, err := libcni.InjectConf(testNetConfig, map[string]interface{}{"": nil})
Expect(err).To(MatchError(HavePrefix(`keys cannot be empty`)))
})
It("returns newValue error", func() {
_, err := libcni.InjectConf(testNetConfig, map[string]interface{}{"test": nil})
Expect(err).To(MatchError(HavePrefix(`key 'test' value must not be nil`)))
})
})
Context("when new string value added", func() {
It("adds the new key & value to the config", func() {
newPluginConfig := []byte(`{"name":"some-plugin","test":"test","type":"foobar"}`)
resultConfig, err := libcni.InjectConf(testNetConfig, map[string]interface{}{"test": "test"})
Expect(err).NotTo(HaveOccurred())
Expect(resultConfig).To(Equal(&libcni.NetworkConfig{
Network: &types.NetConf{
Name: "some-plugin",
Type: "foobar",
},
Bytes: newPluginConfig,
}))
})
It("adds the new value for exiting key", func() {
newPluginConfig := []byte(`{"name":"some-plugin","test":"changedValue","type":"foobar"}`)
resultConfig, err := libcni.InjectConf(testNetConfig, map[string]interface{}{"test": "test"})
Expect(err).NotTo(HaveOccurred())
resultConfig, err = libcni.InjectConf(resultConfig, map[string]interface{}{"test": "changedValue"})
Expect(err).NotTo(HaveOccurred())
Expect(resultConfig).To(Equal(&libcni.NetworkConfig{
Network: &types.NetConf{
Name: "some-plugin",
Type: "foobar",
},
Bytes: newPluginConfig,
}))
})
It("adds existing key & value", func() {
newPluginConfig := []byte(`{"name":"some-plugin","test":"test","type":"foobar"}`)
resultConfig, err := libcni.InjectConf(testNetConfig, map[string]interface{}{"test": "test"})
Expect(err).NotTo(HaveOccurred())
resultConfig, err = libcni.InjectConf(resultConfig, map[string]interface{}{"test": "test"})
Expect(err).NotTo(HaveOccurred())
Expect(resultConfig).To(Equal(&libcni.NetworkConfig{
Network: &types.NetConf{
Name: "some-plugin",
Type: "foobar",
},
Bytes: newPluginConfig,
}))
})
It("adds sub-fields of NetworkConfig.Network to the config", func() {
expectedPluginConfig := []byte(`{"dns":{"domain":"local","nameservers":["server1","server2"]},"name":"some-plugin","type":"bridge"}`)
servers := []string{"server1", "server2"}
newDNS := &types.DNS{Nameservers: servers, Domain: "local"}
// inject DNS
resultConfig, err := libcni.InjectConf(testNetConfig, map[string]interface{}{"dns": newDNS})
Expect(err).NotTo(HaveOccurred())
// inject type
resultConfig, err = libcni.InjectConf(resultConfig, map[string]interface{}{"type": "bridge"})
Expect(err).NotTo(HaveOccurred())
Expect(resultConfig).To(Equal(&libcni.NetworkConfig{
Network: &types.NetConf{Name: "some-plugin", Type: "bridge", DNS: types.DNS{Nameservers: servers, Domain: "local"}},
Bytes: expectedPluginConfig,
}))
})
})
})
})
var _ = Describe("ConfListFromConf", func() {
var testNetConfig *libcni.NetworkConfig
BeforeEach(func() {
pb := []byte(`{"name":"some-plugin","cniVersion":"0.3.1", "type":"foobar"}`)
tc, err := libcni.ConfFromBytes(pb)
Expect(err).NotTo(HaveOccurred())
testNetConfig = tc
})
It("correctly upconverts a NetworkConfig to a NetworkConfigList", func() {
ncl, err := libcni.ConfListFromConf(testNetConfig)
Expect(err).NotTo(HaveOccurred())
bytes := ncl.Bytes
// null out the json - we don't care about the exact marshalling
ncl.Bytes = nil
ncl.Plugins[0].Bytes = nil
testNetConfig.Bytes = nil
Expect(ncl).To(Equal(&libcni.NetworkConfigList{
Name: "some-plugin",
CNIVersion: "0.3.1",
Plugins: []*libcni.NetworkConfig{testNetConfig},
}))
//Test that the json unmarshals to the same data
ncl2, err := libcni.ConfListFromBytes(bytes)
Expect(err).NotTo(HaveOccurred())
ncl2.Bytes = nil
ncl2.Plugins[0].Bytes = nil
Expect(ncl2).To(Equal(ncl))
})
})

View File

@ -1,61 +0,0 @@
// 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 libcni_test
import (
"encoding/json"
"path/filepath"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/onsi/gomega/gexec"
"testing"
)
func TestLibcni(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Libcni Suite")
}
var pluginPackages = map[string]string{
"noop": "github.com/containernetworking/cni/plugins/test/noop",
}
var pluginPaths map[string]string
var pluginDirs []string // array of plugin dirs
var _ = SynchronizedBeforeSuite(func() []byte {
paths := map[string]string{}
for name, packagePath := range pluginPackages {
execPath, err := gexec.Build(packagePath)
Expect(err).NotTo(HaveOccurred())
paths[name] = execPath
}
crossNodeData, err := json.Marshal(paths)
Expect(err).NotTo(HaveOccurred())
return crossNodeData
}, func(crossNodeData []byte) {
Expect(json.Unmarshal(crossNodeData, &pluginPaths)).To(Succeed())
for _, pluginPath := range pluginPaths {
pluginDirs = append(pluginDirs, filepath.Dir(pluginPath))
}
})
var _ = SynchronizedAfterSuite(func() {}, func() {
gexec.CleanupBuildArtifacts()
})

Binary file not shown.

Before

(image error) Size: 11 KiB

View File

@ -15,6 +15,7 @@
package invoke
import (
"fmt"
"os"
"strings"
)
@ -22,6 +23,8 @@ import (
type CNIArgs interface {
// For use with os/exec; i.e., return nil to inherit the
// environment from this process
// For use in delegation; inherit the environment from this
// process and allow overrides
AsEnv() []string
}
@ -57,17 +60,17 @@ func (args *Args) AsEnv() []string {
pluginArgsStr = stringify(args.PluginArgs)
}
// Ensure that the custom values are first, so any value present in
// the process environment won't override them.
env = append([]string{
"CNI_COMMAND=" + args.Command,
"CNI_CONTAINERID=" + args.ContainerID,
"CNI_NETNS=" + args.NetNS,
"CNI_ARGS=" + pluginArgsStr,
"CNI_IFNAME=" + args.IfName,
"CNI_PATH=" + args.Path,
}, env...)
return env
// Duplicated values which come first will be overrided, so we must put the
// custom values in the end to avoid being overrided by the process environments.
env = append(env,
"CNI_COMMAND="+args.Command,
"CNI_CONTAINERID="+args.ContainerID,
"CNI_NETNS="+args.NetNS,
"CNI_ARGS="+pluginArgsStr,
"CNI_IFNAME="+args.IfName,
"CNI_PATH="+args.Path,
)
return dedupEnv(env)
}
// taken from rkt/networking/net_plugin.go
@ -80,3 +83,46 @@ func stringify(pluginArgs [][2]string) string {
return strings.Join(entries, ";")
}
// DelegateArgs implements the CNIArgs interface
// used for delegation to inherit from environments
// and allow some overrides like CNI_COMMAND
var _ CNIArgs = &DelegateArgs{}
type DelegateArgs struct {
Command string
}
func (d *DelegateArgs) AsEnv() []string {
env := os.Environ()
// The custom values should come in the end to override the existing
// process environment of the same key.
env = append(env,
"CNI_COMMAND="+d.Command,
)
return dedupEnv(env)
}
// dedupEnv returns a copy of env with any duplicates removed, in favor of later values.
// Items not of the normal environment "key=value" form are preserved unchanged.
func dedupEnv(env []string) []string {
out := make([]string, 0, len(env))
envMap := map[string]string{}
for _, kv := range env {
// find the first "=" in environment, if not, just keep it
eq := strings.Index(kv, "=")
if eq < 0 {
out = append(out, kv)
continue
}
envMap[kv[:eq]] = kv[eq+1:]
}
for k, v := range envMap {
out = append(out, fmt.Sprintf("%s=%s", k, v))
}
return out
}

View File

@ -1,60 +0,0 @@
// 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 invoke_test
import (
"os"
"github.com/containernetworking/cni/pkg/invoke"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Args", func() {
Describe("AsEnv", func() {
It("places the CNI_ environment variables ahead of any ambient variables", func() {
args := invoke.Args{
Command: "ADD",
ContainerID: "some-container-id",
NetNS: "/some/netns/path",
PluginArgs: [][2]string{
{"KEY1", "VALUE1"},
{"KEY2", "VALUE2"},
},
IfName: "eth7",
Path: "/some/cni/path",
}
const numCNIEnvVars = 6
latentVars := os.Environ()
cniEnv := args.AsEnv()
Expect(cniEnv).To(HaveLen(len(latentVars) + numCNIEnvVars))
Expect(cniEnv[0:numCNIEnvVars]).To(Equal([]string{
"CNI_COMMAND=ADD",
"CNI_CONTAINERID=some-container-id",
"CNI_NETNS=/some/netns/path",
"CNI_ARGS=KEY1=VALUE1;KEY2=VALUE2",
"CNI_IFNAME=eth7",
"CNI_PATH=/some/cni/path",
}))
for i := range latentVars {
Expect(cniEnv[numCNIEnvVars+i]).To(Equal(latentVars[i]))
}
})
})
})

View File

@ -15,14 +15,14 @@
package invoke
import (
"fmt"
"context"
"os"
"path/filepath"
"github.com/containernetworking/cni/pkg/types"
)
func delegateAddOrGet(command, delegatePlugin string, netconf []byte, exec Exec) (types.Result, error) {
func delegateCommon(delegatePlugin string, exec Exec) (string, Exec, error) {
if exec == nil {
exec = defaultExec
}
@ -30,46 +30,51 @@ func delegateAddOrGet(command, delegatePlugin string, netconf []byte, exec Exec)
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(delegatePlugin, exec)
if err != nil {
return nil, err
}
return delegateAddOrGet("ADD", delegatePlugin, netconf, exec)
// DelegateAdd will override the original "CNI_COMMAND" env from process with ADD
return ExecPluginWithResult(ctx, pluginPath, netconf, delegateArgs("ADD"), 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")
}
return delegateAddOrGet("GET", delegatePlugin, netconf, exec)
}
// 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 DelegateCheck(ctx context.Context, delegatePlugin string, netconf []byte, exec Exec) error {
pluginPath, realExec, err := delegateCommon(delegatePlugin, exec)
if err != nil {
return err
}
return ExecPluginWithoutResult(pluginPath, netconf, ArgsFromEnv(), exec)
// DelegateCheck will override the original CNI_COMMAND env from process with CHECK
return ExecPluginWithoutResult(ctx, pluginPath, netconf, delegateArgs("CHECK"), realExec)
}
// DelegateDel calls the given delegate plugin with the CNI DEL action and
// JSON configuration
func DelegateDel(ctx context.Context, delegatePlugin string, netconf []byte, exec Exec) error {
pluginPath, realExec, err := delegateCommon(delegatePlugin, exec)
if err != nil {
return err
}
// DelegateDel will override the original CNI_COMMAND env from process with DEL
return ExecPluginWithoutResult(ctx, pluginPath, netconf, delegateArgs("DEL"), realExec)
}
// return CNIArgs used by delegation
func delegateArgs(action string) *DelegateArgs {
return &DelegateArgs{
Command: action,
}
}

View File

@ -1,201 +0,0 @@
// 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 invoke_test
import (
"encoding/json"
"io/ioutil"
"net"
"os"
"path/filepath"
"github.com/containernetworking/cni/pkg/invoke"
"github.com/containernetworking/cni/pkg/types/current"
"github.com/containernetworking/cni/plugins/test/noop/debug"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Delegate", func() {
var (
pluginName string
netConf []byte
debugFileName string
debugBehavior *debug.Debug
expectedResult *current.Result
)
BeforeEach(func() {
netConf, _ = json.Marshal(map[string]string{
"name": "delegate-test",
"cniVersion": "0.4.0",
})
expectedResult = &current.Result{
CNIVersion: "0.4.0",
IPs: []*current.IPConfig{
{
Version: "4",
Address: net.IPNet{
IP: net.ParseIP("10.1.2.3"),
Mask: net.CIDRMask(24, 32),
},
},
},
}
expectedResultBytes, _ := json.Marshal(expectedResult)
debugFile, err := ioutil.TempFile("", "cni_debug")
Expect(err).NotTo(HaveOccurred())
Expect(debugFile.Close()).To(Succeed())
debugFileName = debugFile.Name()
debugBehavior = &debug.Debug{
ReportResult: string(expectedResultBytes),
}
Expect(debugBehavior.WriteDebug(debugFileName)).To(Succeed())
pluginName = "noop"
os.Setenv("CNI_ARGS", "DEBUG="+debugFileName)
os.Setenv("CNI_PATH", filepath.Dir(pathToPlugin))
os.Setenv("CNI_NETNS", "/tmp/some/netns/path")
os.Setenv("CNI_IFNAME", "eth7")
os.Setenv("CNI_CONTAINERID", "container")
})
AfterEach(func() {
os.RemoveAll(debugFileName)
for _, k := range []string{"CNI_COMMAND", "CNI_ARGS", "CNI_PATH", "CNI_NETNS", "CNI_IFNAME"} {
os.Unsetenv(k)
}
})
Describe("DelegateAdd", func() {
BeforeEach(func() {
os.Setenv("CNI_COMMAND", "ADD")
})
It("finds and execs the named plugin", func() {
result, err := invoke.DelegateAdd(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("ADD"))
Expect(pluginInvocation.CmdArgs.IfName).To(Equal("eth7"))
})
Context("if the delegation isn't part of an existing ADD command", func() {
BeforeEach(func() {
os.Setenv("CNI_COMMAND", "NOPE")
})
It("aborts and returns a useful error", func() {
_, err := invoke.DelegateAdd(pluginName, netConf, nil)
Expect(err).To(MatchError("CNI_COMMAND is not ADD"))
})
})
Context("when the plugin cannot be found", func() {
BeforeEach(func() {
pluginName = "non-existent-plugin"
})
It("returns a useful error", func() {
_, err := invoke.DelegateAdd(pluginName, netConf, nil)
Expect(err).To(MatchError(HavePrefix("failed to find plugin")))
})
})
})
Describe("DelegateGet", func() {
BeforeEach(func() {
os.Setenv("CNI_COMMAND", "GET")
})
It("finds and execs the named plugin", func() {
result, err := invoke.DelegateGet(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.CmdArgs.IfName).To(Equal("eth7"))
})
Context("if the delegation isn't part of an existing GET 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"))
})
})
Context("when the plugin cannot be found", func() {
BeforeEach(func() {
pluginName = "non-existent-plugin"
})
It("returns a useful error", func() {
_, err := invoke.DelegateGet(pluginName, netConf, nil)
Expect(err).To(MatchError(HavePrefix("failed to find plugin")))
})
})
})
Describe("DelegateDel", func() {
BeforeEach(func() {
os.Setenv("CNI_COMMAND", "DEL")
})
It("finds and execs the named plugin", func() {
err := invoke.DelegateDel(pluginName, netConf, nil)
Expect(err).NotTo(HaveOccurred())
pluginInvocation, err := debug.ReadDebug(debugFileName)
Expect(err).NotTo(HaveOccurred())
Expect(pluginInvocation.Command).To(Equal("DEL"))
Expect(pluginInvocation.CmdArgs.IfName).To(Equal("eth7"))
})
Context("if the delegation isn't part of an existing DEL command", func() {
BeforeEach(func() {
os.Setenv("CNI_COMMAND", "NOPE")
})
It("aborts and returns a useful error", func() {
err := invoke.DelegateDel(pluginName, netConf, nil)
Expect(err).To(MatchError("CNI_COMMAND is not DEL"))
})
})
Context("when the plugin cannot be found", func() {
BeforeEach(func() {
pluginName = "non-existent-plugin"
})
It("returns a useful error", func() {
err := invoke.DelegateDel(pluginName, netConf, nil)
Expect(err).To(MatchError(HavePrefix("failed to find plugin")))
})
})
})
})

View File

@ -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

View File

@ -1,176 +0,0 @@
// 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 invoke_test
import (
"encoding/json"
"errors"
"github.com/containernetworking/cni/pkg/invoke"
"github.com/containernetworking/cni/pkg/invoke/fakes"
"github.com/containernetworking/cni/pkg/types/current"
"github.com/containernetworking/cni/pkg/version"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Executing a plugin, unit tests", func() {
var (
pluginExec invoke.Exec
rawExec *fakes.RawExec
versionDecoder *fakes.VersionDecoder
pluginPath string
netconf []byte
cniargs *fakes.CNIArgs
)
BeforeEach(func() {
rawExec = &fakes.RawExec{}
rawExec.ExecPluginCall.Returns.ResultBytes = []byte(`{ "ips": [ { "version": "4", "address": "1.2.3.4/24" } ] }`)
versionDecoder = &fakes.VersionDecoder{}
versionDecoder.DecodeCall.Returns.PluginInfo = version.PluginSupports("0.42.0")
pluginExec = &struct {
*fakes.RawExec
*fakes.VersionDecoder
}{
RawExec: rawExec,
VersionDecoder: versionDecoder,
}
pluginPath = "/some/plugin/path"
netconf = []byte(`{ "some": "stdin", "cniVersion": "0.3.1" }`)
cniargs = &fakes.CNIArgs{}
cniargs.AsEnvCall.Returns.Env = []string{"SOME=ENV"}
})
Describe("returning a result", func() {
It("unmarshals the result bytes into the Result type", func() {
r, err := invoke.ExecPluginWithResult(pluginPath, netconf, cniargs, pluginExec)
Expect(err).NotTo(HaveOccurred())
result, err := current.GetResult(r)
Expect(err).NotTo(HaveOccurred())
Expect(len(result.IPs)).To(Equal(1))
Expect(result.IPs[0].Address.IP.String()).To(Equal("1.2.3.4"))
})
It("passes its arguments through to the rawExec", func() {
invoke.ExecPluginWithResult(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"}))
})
Context("when the rawExec fails", func() {
BeforeEach(func() {
rawExec.ExecPluginCall.Returns.Error = errors.New("banana")
})
It("returns the error", func() {
_, err := invoke.ExecPluginWithResult(pluginPath, netconf, cniargs, pluginExec)
Expect(err).To(MatchError("banana"))
})
})
It("returns an error using the default exec interface", 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)
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)
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"}))
})
Context("when the rawExec fails", func() {
BeforeEach(func() {
rawExec.ExecPluginCall.Returns.Error = errors.New("banana")
})
It("returns the error", func() {
err := invoke.ExecPluginWithoutResult(pluginPath, netconf, cniargs, pluginExec)
Expect(err).To(MatchError("banana"))
})
})
It("returns an error using the default exec interface", 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)
Expect(err).To(HaveOccurred())
})
})
Describe("discovering the plugin version", func() {
BeforeEach(func() {
rawExec.ExecPluginCall.Returns.ResultBytes = []byte(`{ "some": "version-info" }`)
})
It("execs the plugin with the command VERSION", func() {
invoke.GetVersionInfo(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()})
Expect(rawExec.ExecPluginCall.Received.StdinData).To(MatchJSON(expectedStdin))
})
It("decodes and returns the version info", func() {
versionInfo, err := invoke.GetVersionInfo(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" }`))
})
Context("when the rawExec fails", func() {
BeforeEach(func() {
rawExec.ExecPluginCall.Returns.Error = errors.New("banana")
})
It("returns the error", func() {
_, err := invoke.GetVersionInfo(pluginPath, pluginExec)
Expect(err).To(MatchError("banana"))
})
})
Context("when the plugin is too old to recognize the VERSION command", func() {
BeforeEach(func() {
rawExec.ExecPluginCall.Returns.Error = errors.New("unknown CNI_COMMAND: VERSION")
})
It("interprets the error as a 0.1.0 version", func() {
versionInfo, err := invoke.GetVersionInfo(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)
env := rawExec.ExecPluginCall.Received.Environ
Expect(env).To(ContainElement("CNI_NETNS=dummy"))
Expect(env).To(ContainElement("CNI_IFNAME=dummy"))
Expect(env).To(ContainElement("CNI_PATH=dummy"))
})
})
})
})

Some files were not shown because too many files have changed in this diff Show More