Remove everything except for plugins in preparation for import to plugins repo
This commit is contained in:
parent
1a9288c3c0
commit
4e0349a7e7
4
.gitignore
vendored
4
.gitignore
vendored
@ -1,4 +0,0 @@
|
||||
bin/
|
||||
gopath/
|
||||
*.sw[ponm]
|
||||
.vagrant
|
41
.travis.yml
41
.travis.yml
@ -1,41 +0,0 @@
|
||||
language: go
|
||||
sudo: required
|
||||
dist: trusty
|
||||
|
||||
go:
|
||||
- 1.7.x
|
||||
- 1.8.x
|
||||
|
||||
env:
|
||||
global:
|
||||
- TOOLS_CMD=golang.org/x/tools/cmd
|
||||
- PATH=$GOROOT/bin:$PATH
|
||||
- GO15VENDOREXPERIMENT=1
|
||||
matrix:
|
||||
- TARGET=amd64
|
||||
- TARGET=arm
|
||||
- TARGET=arm64
|
||||
- TARGET=ppc64le
|
||||
- TARGET=s390x
|
||||
|
||||
matrix:
|
||||
fast_finish: true
|
||||
|
||||
install:
|
||||
- go get ${TOOLS_CMD}/cover
|
||||
- go get github.com/modocache/gover
|
||||
- go get github.com/mattn/goveralls
|
||||
|
||||
script:
|
||||
- >
|
||||
if [ "${TARGET}" == "amd64" ]; then
|
||||
GOARCH="${TARGET}" ./test.sh;
|
||||
else
|
||||
GOARCH="${TARGET}" ./build.sh;
|
||||
fi
|
||||
|
||||
notifications:
|
||||
email: false
|
||||
|
||||
git:
|
||||
depth: 9999999
|
125
CONTRIBUTING.md
125
CONTRIBUTING.md
@ -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.
|
102
CONVENTIONS.md
102
CONVENTIONS.md
@ -1,102 +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.
|
||||
|
||||
## Plugin specific fields
|
||||
[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 is expected that it will 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.
|
||||
|
||||
| Area | Purpose | Spec and Example | Runtime implementations | Plugin Implementations |
|
||||
| ----- | ------- | ---------------- | ----------------------- | --------------------- |
|
||||
| port mappings | Pass mapping from ports on the host to ports in the container network namespace. | Operators can ask runtimes to pass port mapping information to plugins, by setting the following in the CNI config <pre>"capabilities": {"portMappings": true} </pre> Runtimes should fill in the actual port mappings when the config is passed to plugins. It should be placed in a new section of the config "runtimeConfig" e.g. <pre>"runtimeConfig": {<br /> "portMappings" : [<br /> { "hostPort": 8080, "containerPort": 80, "protocol": "tcp" },<br /> { "hostPort": 8000, "containerPort": 8001, "protocol": "udp" }<br /> ]<br />}</pre> | none | none |
|
||||
|
||||
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"}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## "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.1.0` 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 whithout 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 |
|
||||
|
||||
## 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).|
|
||||
|
36
DCO
36
DCO
@ -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.
|
@ -1,42 +0,0 @@
|
||||
# bridge plugin
|
||||
|
||||
## Overview
|
||||
|
||||
With bridge plugin, all containers (on the same host) are plugged into a bridge (virtual switch) that resides in the host network namespace.
|
||||
The containers receive one end of the veth pair with the other end connected to the bridge.
|
||||
An IP address is only assigned to one end of the veth pair -- one residing in the container.
|
||||
The bridge itself can also be assigned an IP address, turning it into a gateway for the containers.
|
||||
Alternatively, the bridge can function purely in L2 mode and would need to be bridged to the host network interface (if other than container-to-container communication on the same host is desired).
|
||||
|
||||
The network configuration specifies the name of the bridge to be used.
|
||||
If the bridge is missing, the plugin will create one on first use and, if gateway mode is used, assign it an IP that was returned by IPAM plugin via the gateway field.
|
||||
|
||||
## Example configuration
|
||||
```
|
||||
{
|
||||
"name": "mynet",
|
||||
"type": "bridge",
|
||||
"bridge": "mynet0",
|
||||
"isDefaultGateway": true,
|
||||
"forceAddress": false,
|
||||
"ipMasq": true,
|
||||
"hairpinMode": true,
|
||||
"ipam": {
|
||||
"type": "host-local",
|
||||
"subnet": "10.10.0.0/16"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Network configuration reference
|
||||
|
||||
* `name` (string, required): the name of the network.
|
||||
* `type` (string, required): "bridge".
|
||||
* `bridge` (string, optional): name of the bridge to use/create. Defaults to "cni0".
|
||||
* `isGateway` (boolean, optional): assign an IP address to the bridge. Defaults to false.
|
||||
* `isDefaultGateway` (boolean, optional): Sets isGateway to true and makes the assigned IP the default route. Defaults to false.
|
||||
* `forceAddress` (boolean, optional): Indicates if a new IP address should be set if the previous value has been changed. Defaults to false.
|
||||
* `ipMasq` (boolean, optional): set up IP Masquerade on the host for traffic originating from this network and destined outside of it. Defaults to false.
|
||||
* `mtu` (integer, optional): explicitly set MTU to the specified value. Defaults to the value chosen by the kernel.
|
||||
* `hairpinMode` (boolean, optional): set hairpin mode for interfaces on the bridge. Defaults to false.
|
||||
* `ipam` (dictionary, required): IPAM configuration to be used for this network.
|
@ -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.
|
@ -1,35 +0,0 @@
|
||||
# dhcp plugin
|
||||
|
||||
## Overview
|
||||
|
||||
With dhcp plugin the containers can get an IP allocated by a DHCP server already running on your network.
|
||||
This can be especially useful with plugin types such as [macvlan](https://github.com/containernetworking/cni/blob/master/Documentation/macvlan.md).
|
||||
Because a DHCP lease must be periodically renewed for the duration of container lifetime, a separate daemon is required to be running.
|
||||
The same plugin binary can also be run in the daemon mode.
|
||||
|
||||
## Operation
|
||||
To use the dhcp IPAM plugin, first launch the dhcp daemon:
|
||||
|
||||
```
|
||||
# Make sure the unix socket has been removed
|
||||
$ rm -f /run/cni/dhcp.sock
|
||||
$ ./dhcp daemon
|
||||
```
|
||||
|
||||
Alternatively, you can use systemd socket activation protocol.
|
||||
Be sure that the .socket file uses /run/cni/dhcp.sock as the socket path.
|
||||
|
||||
With the daemon running, containers using the dhcp plugin can be launched.
|
||||
|
||||
## Example configuration
|
||||
|
||||
```
|
||||
{
|
||||
"ipam": {
|
||||
"type": "dhcp",
|
||||
}
|
||||
}
|
||||
|
||||
## Network configuration reference
|
||||
|
||||
* `type` (string, required): "dhcp"
|
@ -1,88 +0,0 @@
|
||||
# flannel plugin
|
||||
|
||||
## Overview
|
||||
This plugin is designed to work in conjunction with [flannel](https://github.com/coreos/flannel), a network fabric for containers.
|
||||
When flannel daemon is started, it outputs a `/run/flannel/subnet.env` file that looks like this:
|
||||
```
|
||||
FLANNEL_NETWORK=10.1.0.0/16
|
||||
FLANNEL_SUBNET=10.1.17.1/24
|
||||
FLANNEL_MTU=1472
|
||||
FLANNEL_IPMASQ=true
|
||||
```
|
||||
|
||||
This information reflects the attributes of flannel network on the host.
|
||||
The flannel CNI plugin uses this information to configure another CNI plugin, such as bridge plugin.
|
||||
|
||||
## Operation
|
||||
Given the following network configuration file and the contents of `/run/flannel/subnet.env` above,
|
||||
```
|
||||
{
|
||||
"name": "mynet",
|
||||
"type": "flannel"
|
||||
}
|
||||
```
|
||||
the flannel plugin will generate another network configuration file:
|
||||
```
|
||||
{
|
||||
"name": "mynet",
|
||||
"type": "bridge",
|
||||
"mtu": 1472,
|
||||
"ipMasq": false,
|
||||
"isGateway": true,
|
||||
"ipam": {
|
||||
"type": "host-local",
|
||||
"subnet": "10.1.17.0/24"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
It will then invoke the bridge plugin, passing it the generated configuration.
|
||||
|
||||
As can be seen from above, the flannel plugin, by default, will delegate to the bridge plugin.
|
||||
If additional configuration values need to be passed to the bridge plugin, it can be done so via the `delegate` field:
|
||||
```
|
||||
{
|
||||
"name": "mynet",
|
||||
"type": "flannel",
|
||||
"delegate": {
|
||||
"bridge": "mynet0",
|
||||
"mtu": 1400
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This supplies a configuration parameter to the bridge plugin -- the created bridge will now be named `mynet0`.
|
||||
Notice that `mtu` has also been specified and this value will not be overwritten by flannel plugin.
|
||||
|
||||
Additionally, the `delegate` field can be used to select a different kind of plugin altogether.
|
||||
To use `ipvlan` instead of `bridge`, the following configuration can be specified:
|
||||
|
||||
```
|
||||
{
|
||||
"name": "mynet",
|
||||
"type": "flannel",
|
||||
"delegate": {
|
||||
"type": "ipvlan",
|
||||
"master": "eth0"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Network configuration reference
|
||||
|
||||
* `name` (string, required): the name of the network
|
||||
* `type` (string, required): "flannel"
|
||||
* `subnetFile` (string, optional): full path to the subnet file written out by flanneld. Defaults to /run/flannel/subnet.env
|
||||
* `dataDir` (string, optional): path to directory where plugin will store generated network configuration files. Defaults to `/var/lib/cni/flannel`
|
||||
* `delegate` (dictionary, optional): specifies configuration options for the delegated plugin.
|
||||
|
||||
flannel plugin will always set the following fields in the delegated plugin configuration:
|
||||
|
||||
* `name`: value of its "name" field.
|
||||
* `ipam`: "host-local" type will be used with "subnet" set to `$FLANNEL_SUBNET`.
|
||||
|
||||
flannel plugin will set the following fields in the delegated plugin configuration if they are not present:
|
||||
* `ipMasq`: the inverse of `$FLANNEL_IPMASQ`
|
||||
* `mtu`: `$FLANNEL_MTU`
|
||||
|
||||
Additionally, for the bridge plugin, `isGateway` will be set to `true`, if not present.
|
@ -1,82 +0,0 @@
|
||||
# host-local IP address management plugin
|
||||
|
||||
host-local IPAM allocates IPv4 and IPv6 addresses out of a specified address range. Optionally,
|
||||
it can include a DNS configuration from a `resolv.conf` file on the host.
|
||||
|
||||
## Overview
|
||||
|
||||
host-local IPAM plugin allocates IPv4 addresses out of a specified address range.
|
||||
It stores the state locally on the host filesystem, therefore ensuring uniqueness of IP addresses on a single host.
|
||||
|
||||
## Example configurations
|
||||
|
||||
IPv4:
|
||||
```json
|
||||
{
|
||||
"ipam": {
|
||||
"type": "host-local",
|
||||
"subnet": "10.10.0.0/16",
|
||||
"rangeStart": "10.10.1.20",
|
||||
"rangeEnd": "10.10.3.50",
|
||||
"gateway": "10.10.0.254",
|
||||
"routes": [
|
||||
{ "dst": "0.0.0.0/0" },
|
||||
{ "dst": "192.168.0.0/16", "gw": "10.10.5.1" }
|
||||
],
|
||||
"dataDir": "/var/my-orchestrator/container-ipam-state"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
IPv6:
|
||||
```json
|
||||
{
|
||||
"ipam": {
|
||||
"type": "host-local",
|
||||
"subnet": "3ffe:ffff:0:01ff::/64",
|
||||
"rangeStart": "3ffe:ffff:0:01ff::0010",
|
||||
"rangeEnd": "3ffe:ffff:0:01ff::0020",
|
||||
"routes": [
|
||||
{ "dst": "3ffe:ffff:0:01ff::1/64" }
|
||||
],
|
||||
"resolvConf": "/etc/resolv.conf"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
We can test it out on the command-line:
|
||||
|
||||
```bash
|
||||
$ export CNI_COMMAND=ADD
|
||||
$ export CNI_CONTAINERID=f81d4fae-7dec-11d0-a765-00a0c91e6bf6
|
||||
$ echo '{ "name": "default", "ipam": { "type": "host-local", "subnet": "203.0.113.0/24" } }' | ./host-local
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"ip4": {
|
||||
"ip": "203.0.113.1/24"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Network configuration reference
|
||||
|
||||
* `type` (string, required): "host-local".
|
||||
* `subnet` (string, required): CIDR block to allocate out of.
|
||||
* `rangeStart` (string, optional): IP inside of "subnet" from which to start allocating addresses. Defaults to ".2" IP inside of the "subnet" block.
|
||||
* `rangeEnd` (string, optional): IP inside of "subnet" with which to end allocating addresses. Defaults to ".254" IP inside of the "subnet" block.
|
||||
* `gateway` (string, optional): IP inside of "subnet" to designate as the gateway. Defaults to ".1" IP inside of the "subnet" block.
|
||||
* `routes` (string, optional): list of routes to add to the container namespace. Each route is a dictionary with "dst" and optional "gw" fields. If "gw" is omitted, value of "gateway" will be used.
|
||||
* `resolvConf` (string, optional): Path to a `resolv.conf` on the host to parse and return as the DNS configuration
|
||||
* `dataDir` (string, optional): Path to a directory to use for maintaining state, e.g. which IPs have been allocated to which containers
|
||||
|
||||
|
||||
## Supported arguments
|
||||
The following [CNI_ARGS](https://github.com/containernetworking/cni/blob/master/SPEC.md#parameters) are supported:
|
||||
|
||||
* `ip`: request a specific IP address from the subnet. If it's not available, the plugin will exit with an error
|
||||
|
||||
## Files
|
||||
|
||||
Allocated IP addresses are stored as files in `/var/lib/cni/networks/$NETWORK_NAME`. The prefix can be customized with the `dataDir` option listed above.
|
@ -1,40 +0,0 @@
|
||||
# ipvlan plugin
|
||||
|
||||
## Overview
|
||||
|
||||
ipvlan is a new [addition](https://lwn.net/Articles/620087/) to the Linux kernel.
|
||||
Like its cousin macvlan, it virtualizes the host interface.
|
||||
However unlike macvlan which generates a new MAC address for each interface, ipvlan devices all share the same MAC.
|
||||
The kernel driver inspects the IP address of each packet when making a decision about which virtual interface should process the packet.
|
||||
|
||||
Because all ipvlan interfaces share the MAC address with the host interface, DHCP can only be used in conjunction with ClientID (currently not supported by DHCP plugin).
|
||||
|
||||
## Example configuration
|
||||
|
||||
```
|
||||
{
|
||||
"name": "mynet",
|
||||
"type": "ipvlan",
|
||||
"master": "eth0",
|
||||
"ipam": {
|
||||
"type": "host-local",
|
||||
"subnet": "10.1.2.0/24"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Network configuration reference
|
||||
|
||||
* `name` (string, required): the name of the network.
|
||||
* `type` (string, required): "ipvlan".
|
||||
* `master` (string, required): name of the host interface to enslave.
|
||||
* `mode` (string, optional): one of "l2", "l3". Defaults to "l2".
|
||||
* `mtu` (integer, optional): explicitly set MTU to the specified value. Defaults to the value chosen by the kernel.
|
||||
* `ipam` (dictionary, required): IPAM configuration to be used for this network.
|
||||
|
||||
## Notes
|
||||
|
||||
* `ipvlan` does not allow virtual interfaces to communicate with the master interface.
|
||||
Therefore the container will not be able to reach the host via `ipvlan` interface.
|
||||
Be sure to also have container join a network that provides connectivity to the host (e.g. `ptp`).
|
||||
* A single master interface can not be enslaved by both `macvlan` and `ipvlan`.
|
@ -1,34 +0,0 @@
|
||||
# macvlan plugin
|
||||
|
||||
## Overview
|
||||
|
||||
[macvlan](http://backreference.org/2014/03/20/some-notes-on-macvlanmacvtap/) functions like a switch that is already connected to the host interface.
|
||||
A host interface gets "enslaved" with the virtual interfaces sharing the physical device but having distinct MAC addresses.
|
||||
Since each macvlan interface has its own MAC address, it makes it easy to use with existing DHCP servers already present on the network.
|
||||
|
||||
## Example configuration
|
||||
|
||||
```
|
||||
{
|
||||
"name": "mynet",
|
||||
"type": "macvlan",
|
||||
"master": "eth0",
|
||||
"ipam": {
|
||||
"type": "dhcp"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Network configuration reference
|
||||
|
||||
* `name` (string, required): the name of the network
|
||||
* `type` (string, required): "macvlan"
|
||||
* `master` (string, required): name of the host interface to enslave
|
||||
* `mode` (string, optional): one of "bridge", "private", "vepa", "passthrough". Defaults to "bridge".
|
||||
* `mtu` (integer, optional): explicitly set MTU to the specified value. Defaults to the value chosen by the kernel.
|
||||
* `ipam` (dictionary, required): IPAM configuration to be used for this network.
|
||||
|
||||
## Notes
|
||||
|
||||
* If are testing on a laptop, please remember that most wireless cards do not support being enslaved by macvlan.
|
||||
* A single master interface can not be enslaved by both `macvlan` and `ipvlan`.
|
@ -1,32 +0,0 @@
|
||||
# ptp plugin
|
||||
|
||||
## Overview
|
||||
The ptp plugin creates a point-to-point link between a container and the host by using a veth device.
|
||||
One end of the veth pair is placed inside a container and the other end resides on the host.
|
||||
The host-local IPAM plugin can be used to allocate an IP address to the container.
|
||||
The traffic of the container interface will be routed through the interface of the host.
|
||||
|
||||
## Example network configuration
|
||||
|
||||
```
|
||||
{
|
||||
"name": "mynet",
|
||||
"type": "ptp",
|
||||
"ipam": {
|
||||
"type": "host-local",
|
||||
"subnet": "10.1.1.0/24"
|
||||
},
|
||||
"dns": {
|
||||
"nameservers": [ "10.1.1.1", "8.8.8.8" ]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Network configuration reference
|
||||
|
||||
* `name` (string, required): the name of the network
|
||||
* `type` (string, required): "ptp"
|
||||
* `ipMasq` (boolean, optional): set up IP Masquerade on the host for traffic originating from this network and destined outside of it. Defaults to false.
|
||||
* `mtu` (integer, optional): explicitly set MTU to the specified value. Defaults to value chosen by the kernel.
|
||||
* `ipam` (dictionary, required): IPAM configuration to be used for this network.
|
||||
* `dns` (dictionary, optional): DNS information to return as described in the [Result](/SPEC.md#result).
|
@ -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 := ¤t.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 { ... }
|
||||
```
|
@ -1,34 +0,0 @@
|
||||
# tuning plugin
|
||||
|
||||
## Overview
|
||||
|
||||
This plugin can change some system controls (sysctls) in the network namespace.
|
||||
It does not create any network interfaces and therefore does not bring connectivity by itself.
|
||||
It is only useful when used in addition to other plugins.
|
||||
|
||||
## Operation
|
||||
The following network configuration file
|
||||
```
|
||||
{
|
||||
"name": "mytuning",
|
||||
"type": "tuning",
|
||||
"sysctl": {
|
||||
"net.core.somaxconn": "500"
|
||||
}
|
||||
}
|
||||
```
|
||||
will set /proc/sys/net/core/somaxconn to 500.
|
||||
Other sysctls can be modified as long as they belong to the network namespace (`/proc/sys/net/*`).
|
||||
|
||||
A successful result would simply be:
|
||||
```
|
||||
{ }
|
||||
```
|
||||
|
||||
## Network sysctls documentation
|
||||
|
||||
Some network sysctls are documented in the Linux sources:
|
||||
|
||||
- [Documentation/sysctl/net.txt](https://www.kernel.org/doc/Documentation/sysctl/net.txt)
|
||||
- [Documentation/networking/ip-sysctl.txt](https://www.kernel.org/doc/Documentation/networking/ip-sysctl.txt)
|
||||
- [Documentation/networking/](https://www.kernel.org/doc/Documentation/networking/)
|
194
Godeps/Godeps.json
generated
194
Godeps/Godeps.json
generated
@ -1,194 +0,0 @@
|
||||
{
|
||||
"ImportPath": "github.com/containernetworking/cni",
|
||||
"GoVersion": "go1.6",
|
||||
"GodepVersion": "v79",
|
||||
"Packages": [
|
||||
"./..."
|
||||
],
|
||||
"Deps": [
|
||||
{
|
||||
"ImportPath": "github.com/coreos/go-iptables/iptables",
|
||||
"Comment": "v0.1.0",
|
||||
"Rev": "fbb73372b87f6e89951c2b6b31470c2c9d5cfae3"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/coreos/go-systemd/activation",
|
||||
"Comment": "v2-53-g2688e91",
|
||||
"Rev": "2688e91251d9d8e404e86dd8f096e23b2f086958"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/d2g/dhcp4",
|
||||
"Rev": "f0e4d29ff0231dce36e250b2ed9ff08412584bca"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/d2g/dhcp4client",
|
||||
"Rev": "bed07e1bc5b85f69c6f0fd73393aa35ec68ed892"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/ginkgo",
|
||||
"Comment": "v1.2.0-29-g7f8ab55",
|
||||
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/ginkgo/config",
|
||||
"Comment": "v1.2.0-29-g7f8ab55",
|
||||
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/ginkgo/extensions/table",
|
||||
"Comment": "v1.2.0-29-g7f8ab55",
|
||||
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/ginkgo/internal/codelocation",
|
||||
"Comment": "v1.2.0-29-g7f8ab55",
|
||||
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/ginkgo/internal/containernode",
|
||||
"Comment": "v1.2.0-29-g7f8ab55",
|
||||
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/ginkgo/internal/failer",
|
||||
"Comment": "v1.2.0-29-g7f8ab55",
|
||||
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/ginkgo/internal/leafnodes",
|
||||
"Comment": "v1.2.0-29-g7f8ab55",
|
||||
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/ginkgo/internal/remote",
|
||||
"Comment": "v1.2.0-29-g7f8ab55",
|
||||
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/ginkgo/internal/spec",
|
||||
"Comment": "v1.2.0-29-g7f8ab55",
|
||||
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/ginkgo/internal/specrunner",
|
||||
"Comment": "v1.2.0-29-g7f8ab55",
|
||||
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/ginkgo/internal/suite",
|
||||
"Comment": "v1.2.0-29-g7f8ab55",
|
||||
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/ginkgo/internal/testingtproxy",
|
||||
"Comment": "v1.2.0-29-g7f8ab55",
|
||||
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/ginkgo/internal/writer",
|
||||
"Comment": "v1.2.0-29-g7f8ab55",
|
||||
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/ginkgo/reporters",
|
||||
"Comment": "v1.2.0-29-g7f8ab55",
|
||||
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/ginkgo/reporters/stenographer",
|
||||
"Comment": "v1.2.0-29-g7f8ab55",
|
||||
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/ginkgo/types",
|
||||
"Comment": "v1.2.0-29-g7f8ab55",
|
||||
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/gomega",
|
||||
"Comment": "v1.0-71-g2152b45",
|
||||
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/gomega/format",
|
||||
"Comment": "v1.0-71-g2152b45",
|
||||
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/gomega/gbytes",
|
||||
"Comment": "v1.0-71-g2152b45",
|
||||
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/gomega/gexec",
|
||||
"Comment": "v1.0-71-g2152b45",
|
||||
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/gomega/internal/assertion",
|
||||
"Comment": "v1.0-71-g2152b45",
|
||||
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/gomega/internal/asyncassertion",
|
||||
"Comment": "v1.0-71-g2152b45",
|
||||
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/gomega/internal/oraclematcher",
|
||||
"Comment": "v1.0-71-g2152b45",
|
||||
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/gomega/internal/testingtsupport",
|
||||
"Comment": "v1.0-71-g2152b45",
|
||||
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/gomega/matchers",
|
||||
"Comment": "v1.0-71-g2152b45",
|
||||
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/gomega/matchers/support/goraph/bipartitegraph",
|
||||
"Comment": "v1.0-71-g2152b45",
|
||||
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/gomega/matchers/support/goraph/edge",
|
||||
"Comment": "v1.0-71-g2152b45",
|
||||
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/gomega/matchers/support/goraph/node",
|
||||
"Comment": "v1.0-71-g2152b45",
|
||||
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/gomega/matchers/support/goraph/util",
|
||||
"Comment": "v1.0-71-g2152b45",
|
||||
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/gomega/types",
|
||||
"Comment": "v1.0-71-g2152b45",
|
||||
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/vishvananda/netlink",
|
||||
"Rev": "b71e0bb214aebd980216cb2516e8bd7bca9e9672"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/vishvananda/netlink/nl",
|
||||
"Rev": "b71e0bb214aebd980216cb2516e8bd7bca9e9672"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/vishvananda/netns",
|
||||
"Rev": "8ba1072b58e0c2a240eb5f6120165c7776c3e7b8"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/sys/unix",
|
||||
"Rev": "076b546753157f758b316e59bcb51e6807c04057"
|
||||
}
|
||||
]
|
||||
}
|
5
Godeps/Readme
generated
5
Godeps/Readme
generated
@ -1,5 +0,0 @@
|
||||
This directory tree is generated automatically by godep.
|
||||
|
||||
Please do not edit.
|
||||
|
||||
See https://github.com/tools/godep for more information.
|
202
LICENSE
202
LICENSE
@ -1,202 +0,0 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright {yyyy} {name of copyright owner}
|
||||
|
||||
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.
|
||||
|
@ -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)
|
||||
Stefan Junker <stefan.junker@coreos.com> (@steveeJ)
|
||||
Tom Denham <tom@tigera.io> (@tomdee)
|
187
README.md
187
README.md
@ -1,187 +0,0 @@
|
||||
[](https://travis-ci.org/containernetworking/cni)
|
||||
[](https://coveralls.io/github/containernetworking/cni?branch=master)
|
||||
[](https://cryptic-tundra-43194.herokuapp.com/)
|
||||
|
||||
---
|
||||
|
||||
# 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 *April 12th* at *3:00pm UTC* [Add to Calendar](https://www.worldtimebuddy.com/?qm=1&lid=100,5,2643743,5391959&h=100&date=2017-4-12&sln=15-16).
|
||||
|
||||
---
|
||||
|
||||
# CNI - the Container Network Interface
|
||||
|
||||
## What is CNI?
|
||||
|
||||
The CNI (_Container Network Interface_) 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, an example command-line tool, a template for making new plugins, and the supported 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)
|
||||
- [Kurma - container runtime](http://kurma.io/)
|
||||
- [Kubernetes - a system to simplify container operations](http://kubernetes.io/docs/admin/network-plugins/)
|
||||
- [Cloud Foundry - a platform for cloud applications](https://github.com/cloudfoundry-incubator/netman-release)
|
||||
- [Mesos - a distributed systems kernel](https://github.com/apache/mesos/blob/master/docs/cni.md)
|
||||
|
||||
### 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)
|
||||
|
||||
The CNI team also maintains some [core plugins](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
|
||||
|
||||
CNI requires Go 1.5+ to build.
|
||||
|
||||
Go 1.5 users will need to set GO15VENDOREXPERIMENT=1 to get vendored
|
||||
dependencies. This flag is set by default in 1.6.
|
||||
|
||||
### Included Plugins
|
||||
|
||||
This repository includes a number of common plugins in the `plugins/` directory.
|
||||
Please see the [Documentation/](Documentation/) directory for documentation about particular plugins.
|
||||
|
||||
### Running the plugins
|
||||
|
||||
The scripts/ directory contains two scripts, `priv-net-run.sh` and `docker-run.sh`, that can be used 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
|
||||
$ ./build.sh
|
||||
```
|
||||
|
||||
Finally, execute a command (`ifconfig` in this example) in a private network namespace that has joined the `mynet` network:
|
||||
|
||||
```bash
|
||||
$ CNI_PATH=`pwd`/bin
|
||||
$ cd 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=`pwd`/bin
|
||||
$ cd 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)
|
33
ROADMAP.md
33
ROADMAP.md
@ -1,33 +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.2.0](https://github.com/containernetworking/cni/milestones/v0.2.0)
|
||||
|
||||
* Signed release binaries
|
||||
* Introduction of a testing strategy/framework
|
||||
|
||||
### [v0.3.0](https://github.com/containernetworking/cni/milestones/v0.3.0)
|
||||
|
||||
* Further increase test coverage
|
||||
* Simpler default route handling in bridge plugin
|
||||
* Clarify project description, documentation and contribution guidelines
|
||||
|
||||
### [v0.4.0](https://github.com/containernetworking/cni/milestones/v0.4.0)
|
||||
|
||||
* Further increase test coverage
|
||||
* Simpler bridging of host interface
|
||||
* Improve IPAM allocator predictability
|
||||
* Allow in- and output of arbitrary K/V pairs for plugins
|
||||
|
||||
### [v1.0.0](https://github.com/containernetworking/cni/milestones/v1.0.0)
|
||||
|
||||
- Plugin composition functionality
|
||||
- IPv6 support
|
||||
- Stable SPEC
|
||||
- Strategy and tooling for backwards compatibility
|
||||
- Complete test coverage
|
||||
- Integrate build artefact generation with CI
|
535
SPEC.md
535
SPEC.md
@ -1,535 +0,0 @@
|
||||
# Container Networking Interface Specification
|
||||
|
||||
## Version
|
||||
This is CNI **spec** version **0.3.1**.
|
||||
|
||||
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)).
|
||||
|
||||
## 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.
|
||||
|
||||
[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
|
||||
|
||||
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).
|
||||
|
||||
## General considerations
|
||||
|
||||
The intention is for the container runtime to first create a new network namespace for the container.
|
||||
It then determines which networks this container should belong to and for each network, which plugin 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 should contain the varying information.
|
||||
The container runtime sequentially sets up the networks by executing the corresponding plugin for each network.
|
||||
Upon completion of the container lifecycle, the runtime executes the plugins in reverse order (relative to the order in which they were added) to disconnect them from the networks.
|
||||
|
||||
## CNI Plugin
|
||||
|
||||
### Overview
|
||||
|
||||
Each CNI plugin is implemented as an executable that is invoked by the container management system (e.g. rkt or Docker).
|
||||
|
||||
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 other end of veth into a bridge).
|
||||
It should then assign the IP to the interface and setup the routes consistent with IP Address Management section by invoking appropriate IPAM plugin.
|
||||
|
||||
### Parameters
|
||||
|
||||
The operations that the CNI plugin needs to support are:
|
||||
|
||||
|
||||
- Add container to network
|
||||
- Parameters:
|
||||
- **Version**. The version of CNI spec that the caller is using (container management system or the invoking plugin).
|
||||
- **Container ID**. This is optional but recommended, and should be unique across an administrative domain while the container is live (it may be reused in the future). For example, an environment with an IPAM system may require that each container is allocated a unique ID and that each IP allocation can thus be correlated back to a particular container. As another example, in appc implementations this would be the _pod ID_.
|
||||
- **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.
|
||||
|
||||
- Delete container from network
|
||||
- Parameters:
|
||||
- **Version**. The version of CNI spec that the caller is using (container management system or the invoking plugin).
|
||||
- **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.
|
||||
|
||||
- Report version
|
||||
- Parameters: NONE.
|
||||
- Result: information about the CNI spec versions supported by the plugin
|
||||
|
||||
```
|
||||
{
|
||||
"cniVersion": "0.3.1", // the version of the CNI spec in use for this output
|
||||
"supportedVersions": [ "0.1.0", "0.2.0", "0.3.0", "0.3.1" ] // the list of CNI spec versions that this plugin supports
|
||||
}
|
||||
```
|
||||
|
||||
The executable command-line API uses the type of network (see [Network Configuration](#network-configuration) below) as the name of the executable to invoke.
|
||||
It will then look for this executable in a list of predefined directories. Once found, it will invoke the executable using the following environment variables for argument passing:
|
||||
- `CNI_COMMAND`: indicates the desired operation; `ADD`, `DEL` or `VERSION`.
|
||||
- `CNI_CONTAINERID`: Container ID
|
||||
- `CNI_NETNS`: Path to network namespace file
|
||||
- `CNI_IFNAME`: Interface name to set up; plugin must honor this interface name or 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 is streamed to the plugin through stdin. This means it is not tied to a particular file on disk and can contain information which changes between invocations.
|
||||
|
||||
|
||||
### Result
|
||||
|
||||
Note that IPAM plugins return an abbreviated `Result` structure as described in [IP Allocation](#ip-allocation).
|
||||
|
||||
Success is indicated by 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.3.1",
|
||||
"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.
|
||||
`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 are indicated by a non-zero return code and the following JSON being printed to stdout:
|
||||
```
|
||||
{
|
||||
"cniVersion": "0.3.1",
|
||||
"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 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 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 may be ignored by plugins.
|
||||
|
||||
### Example configurations
|
||||
|
||||
```json
|
||||
{
|
||||
"cniVersion": "0.3.1",
|
||||
"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.3.1",
|
||||
"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.3.1",
|
||||
"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 ADD action, plugins SHOULD echo the contents of the `prevResult` field to their stdout to allow subsequent plugins (and the runtime) to receive the result, unless they wish to modify or suppress a previous result.
|
||||
Plugins are allowed to modify or suppress all or part of a `prevResult`.
|
||||
However, plugins that support a version of the CNI specification that includes the `prevResult` field MUST handle `prevResult` by either passing it through, modifying it, or suppressing it explicitly.
|
||||
It is a violation of this specification to be unaware of the `prevResult` field.
|
||||
|
||||
The runtime MUST also execute each plugin in the list with the same environment.
|
||||
|
||||
For the DEL action, the runtime MUST execute the plugins in reverse-order.
|
||||
|
||||
#### 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.3.1",
|
||||
"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.3.1",
|
||||
"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.3.1",
|
||||
"name": "dbnet",
|
||||
"type": "tuning",
|
||||
"sysctl": {
|
||||
"net.core.somaxconn": "500"
|
||||
},
|
||||
"prevResult": {
|
||||
"ips": [
|
||||
{
|
||||
"version": "4",
|
||||
"address": "10.0.0.5/32",
|
||||
"interface": 0
|
||||
}
|
||||
],
|
||||
"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 no `prevResult` field is required as the DEL action does not return any result.
|
||||
Also note that plugins are executed in reverse order from the ADD action.
|
||||
|
||||
1) first call the `tuning` plugin with the following JSON:
|
||||
|
||||
```json
|
||||
{
|
||||
"cniVersion": "0.3.1",
|
||||
"name": "dbnet",
|
||||
"type": "tuning",
|
||||
"sysctl": {
|
||||
"net.core.somaxconn": "500"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
2) next call the `bridge` plugin with the following JSON:
|
||||
|
||||
```json
|
||||
{
|
||||
"cniVersion": "0.3.1",
|
||||
"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" ]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 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 is expected to 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 receives all the same environment variables that were passed in to the CNI plugin. Just like the CNI plugin, IPAM receives the network configuration via stdin.
|
||||
|
||||
Success is indicated by a zero return code and the following JSON being printed to stdout (in the case of the ADD command):
|
||||
|
||||
```
|
||||
{
|
||||
"cniVersion": "0.3.1",
|
||||
"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 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 plugin.
|
||||
|
||||
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
|
||||
- `1` - Incompatible CNI version
|
||||
- `2` - Unsupported field in network configuration. The error message must contain the key and value of the unsupported field.
|
20
Vagrantfile
vendored
20
Vagrantfile
vendored
@ -1,20 +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 golang git
|
||||
echo "export GOPATH=/go" >> /root/.bashrc
|
||||
export GOPATH=/go
|
||||
go get github.com/tools/godep
|
||||
cd /go/src/github.com/containernetworking/cni
|
||||
/go/bin/godep restore
|
||||
SHELL
|
||||
end
|
29
build.sh
29
build.sh
@ -1,29 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
ORG_PATH="github.com/containernetworking"
|
||||
REPO_PATH="${ORG_PATH}/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 GOPATH=${PWD}/gopath
|
||||
|
||||
echo "Building API"
|
||||
go build "$@" ${REPO_PATH}/libcni
|
||||
|
||||
echo "Building reference CLI"
|
||||
go build -o ${PWD}/bin/cnitool "$@" ${REPO_PATH}/cnitool
|
||||
|
||||
echo "Building plugins"
|
||||
PLUGINS="plugins/meta/* plugins/main/* plugins/ipam/* plugins/test/*"
|
||||
for d in $PLUGINS; do
|
||||
if [ -d $d ]; then
|
||||
plugin=$(basename $d)
|
||||
echo " " $plugin
|
||||
go build -o ${PWD}/bin/$plugin "$@" ${REPO_PATH}/$d
|
||||
fi
|
||||
done
|
100
cnitool/cni.go
100
cnitool/cni.go
@ -1,100 +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 (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/containernetworking/cni/libcni"
|
||||
)
|
||||
|
||||
const (
|
||||
EnvCNIPath = "CNI_PATH"
|
||||
EnvNetDir = "NETCONFPATH"
|
||||
EnvCapabilityArgs = "CAP_ARGS"
|
||||
|
||||
DefaultNetDir = "/etc/cni/net.d"
|
||||
|
||||
CmdAdd = "add"
|
||||
CmdDel = "del"
|
||||
)
|
||||
|
||||
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{}
|
||||
args := os.Getenv(EnvCapabilityArgs)
|
||||
if len(args) > 0 {
|
||||
if err = json.Unmarshal([]byte(args), &capabilityArgs); err != nil {
|
||||
exit(err)
|
||||
}
|
||||
}
|
||||
|
||||
netns := os.Args[3]
|
||||
|
||||
cninet := &libcni.CNIConfig{
|
||||
Path: filepath.SplitList(os.Getenv(EnvCNIPath)),
|
||||
}
|
||||
|
||||
rt := &libcni.RuntimeConf{
|
||||
ContainerID: "cni",
|
||||
NetNS: netns,
|
||||
IfName: "eth0",
|
||||
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)
|
||||
}
|
219
libcni/api.go
219
libcni/api.go
@ -1,219 +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 libcni
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/invoke"
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/containernetworking/cni/pkg/version"
|
||||
)
|
||||
|
||||
type RuntimeConf struct {
|
||||
ContainerID string
|
||||
NetNS string
|
||||
IfName string
|
||||
Args [][2]string
|
||||
// A dictionary of capability-specific data passed by the runtime
|
||||
// to plugins as top-level keys in the 'runtimeConfig' dictionary
|
||||
// of the plugin's stdin data. libcni will ensure that only keys
|
||||
// in this map which match the capabilities of the plugin are passed
|
||||
// to the plugin
|
||||
CapabilityArgs map[string]interface{}
|
||||
}
|
||||
|
||||
type NetworkConfig struct {
|
||||
Network *types.NetConf
|
||||
Bytes []byte
|
||||
}
|
||||
|
||||
type NetworkConfigList struct {
|
||||
Name string
|
||||
CNIVersion string
|
||||
Plugins []*NetworkConfig
|
||||
Bytes []byte
|
||||
}
|
||||
|
||||
type CNI interface {
|
||||
AddNetworkList(net *NetworkConfigList, rt *RuntimeConf) (types.Result, error)
|
||||
DelNetworkList(net *NetworkConfigList, rt *RuntimeConf) error
|
||||
|
||||
AddNetwork(net *NetworkConfig, rt *RuntimeConf) (types.Result, error)
|
||||
DelNetwork(net *NetworkConfig, rt *RuntimeConf) error
|
||||
}
|
||||
|
||||
type CNIConfig struct {
|
||||
Path []string
|
||||
}
|
||||
|
||||
// CNIConfig implements the CNI interface
|
||||
var _ CNI = &CNIConfig{}
|
||||
|
||||
func buildOneConfig(list *NetworkConfigList, orig *NetworkConfig, prevResult types.Result, rt *RuntimeConf) (*NetworkConfig, error) {
|
||||
var err error
|
||||
|
||||
inject := map[string]interface{}{
|
||||
"name": list.Name,
|
||||
"cniVersion": list.CNIVersion,
|
||||
}
|
||||
// Add previous plugin result
|
||||
if prevResult != nil {
|
||||
inject["prevResult"] = prevResult
|
||||
}
|
||||
|
||||
// Ensure every config uses the same name and version
|
||||
orig, err = InjectConf(orig, inject)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return injectRuntimeConfig(orig, rt)
|
||||
}
|
||||
|
||||
// This function takes a libcni RuntimeConf structure and injects values into
|
||||
// a "runtimeConfig" dictionary in the CNI network configuration JSON that
|
||||
// will be passed to the plugin on stdin.
|
||||
//
|
||||
// Only "capabilities arguments" passed by the runtime are currently injected.
|
||||
// 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
|
||||
// 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.
|
||||
func injectRuntimeConfig(orig *NetworkConfig, rt *RuntimeConf) (*NetworkConfig, error) {
|
||||
var err error
|
||||
|
||||
rc := make(map[string]interface{})
|
||||
for capability, supported := range orig.Network.Capabilities {
|
||||
if !supported {
|
||||
continue
|
||||
}
|
||||
if data, ok := rt.CapabilityArgs[capability]; ok {
|
||||
rc[capability] = data
|
||||
}
|
||||
}
|
||||
|
||||
if len(rc) > 0 {
|
||||
orig, err = InjectConf(orig, map[string]interface{}{"runtimeConfig": rc})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return orig, nil
|
||||
}
|
||||
|
||||
// AddNetworkList executes a sequence of plugins with the ADD command
|
||||
func (c *CNIConfig) AddNetworkList(list *NetworkConfigList, rt *RuntimeConf) (types.Result, error) {
|
||||
var prevResult types.Result
|
||||
for _, net := range list.Plugins {
|
||||
pluginPath, err := invoke.FindInPath(net.Network.Type, c.Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
newConf, err := buildOneConfig(list, net, prevResult, rt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
prevResult, err = invoke.ExecPluginWithResult(pluginPath, newConf.Bytes, c.args("ADD", rt))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return prevResult, nil
|
||||
}
|
||||
|
||||
// DelNetworkList executes a sequence of plugins with the DEL command
|
||||
func (c *CNIConfig) DelNetworkList(list *NetworkConfigList, rt *RuntimeConf) error {
|
||||
for i := len(list.Plugins) - 1; i >= 0; i-- {
|
||||
net := list.Plugins[i]
|
||||
|
||||
pluginPath, err := invoke.FindInPath(net.Network.Type, c.Path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
newConf, err := buildOneConfig(list, net, nil, rt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := invoke.ExecPluginWithoutResult(pluginPath, newConf.Bytes, c.args("DEL", rt)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddNetwork executes the plugin with the ADD command
|
||||
func (c *CNIConfig) AddNetwork(net *NetworkConfig, rt *RuntimeConf) (types.Result, error) {
|
||||
pluginPath, err := invoke.FindInPath(net.Network.Type, c.Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
net, err = injectRuntimeConfig(net, rt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return invoke.ExecPluginWithResult(pluginPath, net.Bytes, c.args("ADD", rt))
|
||||
}
|
||||
|
||||
// DelNetwork executes the plugin with the DEL command
|
||||
func (c *CNIConfig) DelNetwork(net *NetworkConfig, rt *RuntimeConf) error {
|
||||
pluginPath, err := invoke.FindInPath(net.Network.Type, c.Path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
net, err = injectRuntimeConfig(net, rt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return invoke.ExecPluginWithoutResult(pluginPath, net.Bytes, c.args("DEL", rt))
|
||||
}
|
||||
|
||||
// GetVersionInfo reports which versions of the CNI spec are supported by
|
||||
// the given plugin.
|
||||
func (c *CNIConfig) GetVersionInfo(pluginType string) (version.PluginInfo, error) {
|
||||
pluginPath, err := invoke.FindInPath(pluginType, c.Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return invoke.GetVersionInfo(pluginPath)
|
||||
}
|
||||
|
||||
// =====
|
||||
func (c *CNIConfig) args(action string, rt *RuntimeConf) *invoke.Args {
|
||||
return &invoke.Args{
|
||||
Command: action,
|
||||
ContainerID: rt.ContainerID,
|
||||
NetNS: rt.NetNS,
|
||||
PluginArgs: rt.Args,
|
||||
IfName: rt.IfName,
|
||||
Path: strings.Join(c.Path, string(os.PathListSeparator)),
|
||||
}
|
||||
}
|
@ -1,577 +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"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/containernetworking/cni/libcni"
|
||||
"github.com/containernetworking/cni/pkg/skel"
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/containernetworking/cni/pkg/types/current"
|
||||
noop_debug "github.com/containernetworking/cni/plugins/test/noop/debug"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
type pluginInfo struct {
|
||||
debugFilePath string
|
||||
debug *noop_debug.Debug
|
||||
config string
|
||||
stdinData []byte
|
||||
}
|
||||
|
||||
type portMapping struct {
|
||||
HostPort int `json:"hostPort"`
|
||||
ContainerPort int `json:"containerPort"`
|
||||
Protocol string `json:"protocol"`
|
||||
}
|
||||
|
||||
func stringInList(s string, list []string) bool {
|
||||
for _, item := range list {
|
||||
if s == item {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func newPluginInfo(configValue, prevResult string, injectDebugFilePath bool, result string, runtimeConfig map[string]interface{}, capabilities []string) pluginInfo {
|
||||
debugFile, err := ioutil.TempFile("", "cni_debug")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(debugFile.Close()).To(Succeed())
|
||||
debugFilePath := debugFile.Name()
|
||||
|
||||
debug := &noop_debug.Debug{
|
||||
ReportResult: result,
|
||||
}
|
||||
Expect(debug.WriteDebug(debugFilePath)).To(Succeed())
|
||||
|
||||
// config is what would be in the plugin's on-disk configuration
|
||||
// without runtime injected keys
|
||||
config := fmt.Sprintf(`{"type": "noop", "some-key": "%s"`, configValue)
|
||||
if prevResult != "" {
|
||||
config += fmt.Sprintf(`, "prevResult": %s`, prevResult)
|
||||
}
|
||||
if injectDebugFilePath {
|
||||
config += fmt.Sprintf(`, "debugFile": "%s"`, debugFilePath)
|
||||
}
|
||||
if len(capabilities) > 0 {
|
||||
config += `, "capabilities": {`
|
||||
for i, c := range capabilities {
|
||||
if i > 0 {
|
||||
config += ", "
|
||||
}
|
||||
config += fmt.Sprintf(`"%s": true`, c)
|
||||
}
|
||||
config += "}"
|
||||
}
|
||||
config += "}"
|
||||
|
||||
// stdinData is what the runtime should pass to the plugin's stdin,
|
||||
// including injected keys like 'name', 'cniVersion', and 'runtimeConfig'
|
||||
newConfig := make(map[string]interface{})
|
||||
err = json.Unmarshal([]byte(config), &newConfig)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
newConfig["name"] = "some-list"
|
||||
newConfig["cniVersion"] = "0.3.1"
|
||||
|
||||
// Only include standard runtime config and capability args that this plugin advertises
|
||||
newRuntimeConfig := make(map[string]interface{})
|
||||
for key, value := range runtimeConfig {
|
||||
if stringInList(key, capabilities) {
|
||||
newRuntimeConfig[key] = value
|
||||
}
|
||||
}
|
||||
if len(newRuntimeConfig) > 0 {
|
||||
newConfig["runtimeConfig"] = newRuntimeConfig
|
||||
}
|
||||
|
||||
stdinData, err := json.Marshal(newConfig)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
return pluginInfo{
|
||||
debugFilePath: debugFilePath,
|
||||
debug: debug,
|
||||
config: config,
|
||||
stdinData: stdinData,
|
||||
}
|
||||
}
|
||||
|
||||
var _ = Describe("Invoking plugins", func() {
|
||||
Describe("Capabilities", func() {
|
||||
var (
|
||||
debugFilePath string
|
||||
debug *noop_debug.Debug
|
||||
pluginConfig []byte
|
||||
cniConfig libcni.CNIConfig
|
||||
runtimeConfig *libcni.RuntimeConf
|
||||
netConfig *libcni.NetworkConfig
|
||||
)
|
||||
|
||||
BeforeEach(func() {
|
||||
debugFile, err := ioutil.TempFile("", "cni_debug")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(debugFile.Close()).To(Succeed())
|
||||
debugFilePath = debugFile.Name()
|
||||
|
||||
debug = &noop_debug.Debug{}
|
||||
Expect(debug.WriteDebug(debugFilePath)).To(Succeed())
|
||||
|
||||
pluginConfig = []byte(`{ "type": "noop", "cniVersion": "0.3.1", "capabilities": { "portMappings": true, "somethingElse": true, "noCapability": false } }`)
|
||||
netConfig, err = libcni.ConfFromBytes(pluginConfig)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
cniConfig = libcni.CNIConfig{Path: []string{filepath.Dir(pluginPaths["noop"])}}
|
||||
|
||||
runtimeConfig = &libcni.RuntimeConf{
|
||||
ContainerID: "some-container-id",
|
||||
NetNS: "/some/netns/path",
|
||||
IfName: "some-eth0",
|
||||
Args: [][2]string{{"DEBUG", debugFilePath}},
|
||||
CapabilityArgs: map[string]interface{}{
|
||||
"portMappings": []portMapping{
|
||||
{HostPort: 8080, ContainerPort: 80, Protocol: "tcp"},
|
||||
},
|
||||
"somethingElse": []string{"foobar", "baz"},
|
||||
"noCapability": true,
|
||||
"notAdded": []bool{true, false},
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
Expect(os.RemoveAll(debugFilePath)).To(Succeed())
|
||||
})
|
||||
|
||||
It("adds correct runtime config for capabilities to stdin", func() {
|
||||
_, err := cniConfig.AddNetwork(netConfig, runtimeConfig)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
debug, err = noop_debug.ReadDebug(debugFilePath)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(debug.Command).To(Equal("ADD"))
|
||||
|
||||
conf := make(map[string]interface{})
|
||||
err = json.Unmarshal(debug.CmdArgs.StdinData, &conf)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// We expect runtimeConfig keys only for portMappings and somethingElse
|
||||
rawRc := conf["runtimeConfig"]
|
||||
rc, ok := rawRc.(map[string]interface{})
|
||||
Expect(ok).To(Equal(true))
|
||||
expectedKeys := []string{"portMappings", "somethingElse"}
|
||||
Expect(len(rc)).To(Equal(len(expectedKeys)))
|
||||
for _, key := range expectedKeys {
|
||||
_, ok := rc[key]
|
||||
Expect(ok).To(Equal(true))
|
||||
}
|
||||
})
|
||||
|
||||
It("adds no runtimeConfig when the plugin advertises no used capabilities", func() {
|
||||
// Replace CapabilityArgs with ones we know the plugin
|
||||
// doesn't support
|
||||
runtimeConfig.CapabilityArgs = map[string]interface{}{
|
||||
"portMappings22": []portMapping{
|
||||
{HostPort: 8080, ContainerPort: 80, Protocol: "tcp"},
|
||||
},
|
||||
"somethingElse22": []string{"foobar", "baz"},
|
||||
}
|
||||
|
||||
_, err := cniConfig.AddNetwork(netConfig, runtimeConfig)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
debug, err = noop_debug.ReadDebug(debugFilePath)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(debug.Command).To(Equal("ADD"))
|
||||
|
||||
conf := make(map[string]interface{})
|
||||
err = json.Unmarshal(debug.CmdArgs.StdinData, &conf)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// No intersection of plugin capabilities and CapabilityArgs,
|
||||
// so plugin should not receive a "runtimeConfig" key
|
||||
_, ok := conf["runtimeConfig"]
|
||||
Expect(ok).Should(BeFalse())
|
||||
})
|
||||
})
|
||||
|
||||
Describe("Invoking a single plugin", func() {
|
||||
var (
|
||||
debugFilePath string
|
||||
debug *noop_debug.Debug
|
||||
cniBinPath string
|
||||
pluginConfig string
|
||||
cniConfig libcni.CNIConfig
|
||||
netConfig *libcni.NetworkConfig
|
||||
runtimeConfig *libcni.RuntimeConf
|
||||
|
||||
expectedCmdArgs skel.CmdArgs
|
||||
)
|
||||
|
||||
BeforeEach(func() {
|
||||
debugFile, err := ioutil.TempFile("", "cni_debug")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(debugFile.Close()).To(Succeed())
|
||||
debugFilePath = debugFile.Name()
|
||||
|
||||
debug = &noop_debug.Debug{
|
||||
ReportResult: `{ "ips": [{ "version": "4", "address": "10.1.2.3/24" }], "dns": {} }`,
|
||||
}
|
||||
Expect(debug.WriteDebug(debugFilePath)).To(Succeed())
|
||||
|
||||
portMappings := []portMapping{
|
||||
{HostPort: 8080, ContainerPort: 80, Protocol: "tcp"},
|
||||
}
|
||||
|
||||
cniBinPath = filepath.Dir(pluginPaths["noop"])
|
||||
pluginConfig = `{ "type": "noop", "some-key": "some-value", "cniVersion": "0.3.1", "capabilities": { "portMappings": true } }`
|
||||
cniConfig = libcni.CNIConfig{Path: []string{cniBinPath}}
|
||||
netConfig = &libcni.NetworkConfig{
|
||||
Network: &types.NetConf{
|
||||
Type: "noop",
|
||||
Capabilities: map[string]bool{
|
||||
"portMappings": true,
|
||||
},
|
||||
},
|
||||
Bytes: []byte(pluginConfig),
|
||||
}
|
||||
runtimeConfig = &libcni.RuntimeConf{
|
||||
ContainerID: "some-container-id",
|
||||
NetNS: "/some/netns/path",
|
||||
IfName: "some-eth0",
|
||||
Args: [][2]string{{"DEBUG", debugFilePath}},
|
||||
CapabilityArgs: map[string]interface{}{
|
||||
"portMappings": portMappings,
|
||||
},
|
||||
}
|
||||
|
||||
// inject runtime args into the expected plugin config
|
||||
conf := make(map[string]interface{})
|
||||
err = json.Unmarshal([]byte(pluginConfig), &conf)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
conf["runtimeConfig"] = map[string]interface{}{
|
||||
"portMappings": portMappings,
|
||||
}
|
||||
newBytes, err := json.Marshal(conf)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
expectedCmdArgs = skel.CmdArgs{
|
||||
ContainerID: "some-container-id",
|
||||
Netns: "/some/netns/path",
|
||||
IfName: "some-eth0",
|
||||
Args: "DEBUG=" + debugFilePath,
|
||||
Path: cniBinPath,
|
||||
StdinData: newBytes,
|
||||
}
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
Expect(os.RemoveAll(debugFilePath)).To(Succeed())
|
||||
})
|
||||
|
||||
Describe("AddNetwork", func() {
|
||||
It("executes the plugin with command ADD", func() {
|
||||
r, err := cniConfig.AddNetwork(netConfig, runtimeConfig)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
result, err := current.GetResult(r)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
Expect(result).To(Equal(¤t.Result{
|
||||
CNIVersion: current.ImplementedSpecVersion,
|
||||
IPs: []*current.IPConfig{
|
||||
{
|
||||
Version: "4",
|
||||
Address: net.IPNet{
|
||||
IP: net.ParseIP("10.1.2.3"),
|
||||
Mask: net.IPv4Mask(255, 255, 255, 0),
|
||||
},
|
||||
},
|
||||
},
|
||||
}))
|
||||
|
||||
debug, err := noop_debug.ReadDebug(debugFilePath)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(debug.Command).To(Equal("ADD"))
|
||||
Expect(debug.CmdArgs).To(Equal(expectedCmdArgs))
|
||||
Expect(string(debug.CmdArgs.StdinData)).To(ContainSubstring("\"portMappings\":"))
|
||||
})
|
||||
|
||||
Context("when finding the plugin fails", func() {
|
||||
BeforeEach(func() {
|
||||
netConfig.Network.Type = "does-not-exist"
|
||||
})
|
||||
|
||||
It("returns the error", func() {
|
||||
_, err := cniConfig.AddNetwork(netConfig, runtimeConfig)
|
||||
Expect(err).To(MatchError(ContainSubstring(`failed to find plugin "does-not-exist"`)))
|
||||
})
|
||||
})
|
||||
|
||||
Context("when the plugin errors", func() {
|
||||
BeforeEach(func() {
|
||||
debug.ReportError = "plugin error: banana"
|
||||
Expect(debug.WriteDebug(debugFilePath)).To(Succeed())
|
||||
})
|
||||
It("unmarshals and returns the error", func() {
|
||||
result, err := cniConfig.AddNetwork(netConfig, runtimeConfig)
|
||||
Expect(result).To(BeNil())
|
||||
Expect(err).To(MatchError("plugin error: banana"))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Describe("DelNetwork", func() {
|
||||
It("executes the plugin with command DEL", func() {
|
||||
err := cniConfig.DelNetwork(netConfig, runtimeConfig)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
debug, err := noop_debug.ReadDebug(debugFilePath)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(debug.Command).To(Equal("DEL"))
|
||||
Expect(debug.CmdArgs).To(Equal(expectedCmdArgs))
|
||||
Expect(string(debug.CmdArgs.StdinData)).To(ContainSubstring("\"portMappings\":"))
|
||||
})
|
||||
|
||||
Context("when finding the plugin fails", func() {
|
||||
BeforeEach(func() {
|
||||
netConfig.Network.Type = "does-not-exist"
|
||||
})
|
||||
|
||||
It("returns the error", func() {
|
||||
err := cniConfig.DelNetwork(netConfig, runtimeConfig)
|
||||
Expect(err).To(MatchError(ContainSubstring(`failed to find plugin "does-not-exist"`)))
|
||||
})
|
||||
})
|
||||
|
||||
Context("when the plugin errors", func() {
|
||||
BeforeEach(func() {
|
||||
debug.ReportError = "plugin error: banana"
|
||||
Expect(debug.WriteDebug(debugFilePath)).To(Succeed())
|
||||
})
|
||||
It("unmarshals and returns the error", func() {
|
||||
err := cniConfig.DelNetwork(netConfig, runtimeConfig)
|
||||
Expect(err).To(MatchError("plugin error: banana"))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Describe("GetVersionInfo", func() {
|
||||
It("executes the plugin with the command VERSION", func() {
|
||||
versionInfo, err := cniConfig.GetVersionInfo("noop")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
Expect(versionInfo).NotTo(BeNil())
|
||||
Expect(versionInfo.SupportedVersions()).To(Equal([]string{
|
||||
"0.-42.0", "0.1.0", "0.2.0", "0.3.0", "0.3.1",
|
||||
}))
|
||||
})
|
||||
|
||||
Context("when finding the plugin fails", func() {
|
||||
It("returns the error", func() {
|
||||
_, err := cniConfig.GetVersionInfo("does-not-exist")
|
||||
Expect(err).To(MatchError(ContainSubstring(`failed to find plugin "does-not-exist"`)))
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Describe("Invoking a plugin list", func() {
|
||||
var (
|
||||
plugins []pluginInfo
|
||||
cniBinPath string
|
||||
cniConfig libcni.CNIConfig
|
||||
netConfigList *libcni.NetworkConfigList
|
||||
runtimeConfig *libcni.RuntimeConf
|
||||
|
||||
expectedCmdArgs skel.CmdArgs
|
||||
)
|
||||
|
||||
BeforeEach(func() {
|
||||
var err error
|
||||
|
||||
capabilityArgs := map[string]interface{}{
|
||||
"portMappings": []portMapping{
|
||||
{HostPort: 8080, ContainerPort: 80, Protocol: "tcp"},
|
||||
},
|
||||
"otherCapability": 33,
|
||||
}
|
||||
|
||||
cniBinPath = filepath.Dir(pluginPaths["noop"])
|
||||
cniConfig = libcni.CNIConfig{Path: []string{cniBinPath}}
|
||||
runtimeConfig = &libcni.RuntimeConf{
|
||||
ContainerID: "some-container-id",
|
||||
NetNS: "/some/netns/path",
|
||||
IfName: "some-eth0",
|
||||
Args: [][2]string{{"FOO", "BAR"}},
|
||||
CapabilityArgs: capabilityArgs,
|
||||
}
|
||||
|
||||
expectedCmdArgs = skel.CmdArgs{
|
||||
ContainerID: runtimeConfig.ContainerID,
|
||||
Netns: runtimeConfig.NetNS,
|
||||
IfName: runtimeConfig.IfName,
|
||||
Args: "FOO=BAR",
|
||||
Path: cniBinPath,
|
||||
}
|
||||
|
||||
rc := map[string]interface{}{
|
||||
"containerId": runtimeConfig.ContainerID,
|
||||
"netNs": runtimeConfig.NetNS,
|
||||
"ifName": runtimeConfig.IfName,
|
||||
"args": map[string]string{
|
||||
"FOO": "BAR",
|
||||
},
|
||||
"portMappings": capabilityArgs["portMappings"],
|
||||
"otherCapability": capabilityArgs["otherCapability"],
|
||||
}
|
||||
|
||||
ipResult := `{"dns":{},"ips":[{"version": "4", "address": "10.1.2.3/24"}]}`
|
||||
plugins = make([]pluginInfo, 3, 3)
|
||||
plugins[0] = newPluginInfo("some-value", "", true, ipResult, rc, []string{"portMappings", "otherCapability"})
|
||||
plugins[1] = newPluginInfo("some-other-value", ipResult, true, "PASSTHROUGH", rc, []string{"otherCapability"})
|
||||
plugins[2] = newPluginInfo("yet-another-value", ipResult, true, "INJECT-DNS", rc, []string{})
|
||||
|
||||
configList := []byte(fmt.Sprintf(`{
|
||||
"name": "some-list",
|
||||
"cniVersion": "0.3.1",
|
||||
"plugins": [
|
||||
%s,
|
||||
%s,
|
||||
%s
|
||||
]
|
||||
}`, plugins[0].config, plugins[1].config, plugins[2].config))
|
||||
|
||||
netConfigList, err = libcni.ConfListFromBytes(configList)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
for _, p := range plugins {
|
||||
Expect(os.RemoveAll(p.debugFilePath)).To(Succeed())
|
||||
}
|
||||
})
|
||||
|
||||
Describe("AddNetworkList", func() {
|
||||
It("executes all plugins with command ADD and returns an intermediate result", func() {
|
||||
r, err := cniConfig.AddNetworkList(netConfigList, runtimeConfig)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
result, err := current.GetResult(r)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
Expect(result).To(Equal(¤t.Result{
|
||||
CNIVersion: current.ImplementedSpecVersion,
|
||||
// IP4 added by first plugin
|
||||
IPs: []*current.IPConfig{
|
||||
{
|
||||
Version: "4",
|
||||
Address: net.IPNet{
|
||||
IP: net.ParseIP("10.1.2.3"),
|
||||
Mask: net.IPv4Mask(255, 255, 255, 0),
|
||||
},
|
||||
},
|
||||
},
|
||||
// DNS injected by last plugin
|
||||
DNS: types.DNS{
|
||||
Nameservers: []string{"1.2.3.4"},
|
||||
},
|
||||
}))
|
||||
|
||||
for i := 0; i < len(plugins); i++ {
|
||||
debug, err := noop_debug.ReadDebug(plugins[i].debugFilePath)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(debug.Command).To(Equal("ADD"))
|
||||
|
||||
// Must explicitly match JSON due to dict element ordering
|
||||
Expect(debug.CmdArgs.StdinData).To(MatchJSON(plugins[i].stdinData))
|
||||
debug.CmdArgs.StdinData = nil
|
||||
Expect(debug.CmdArgs).To(Equal(expectedCmdArgs))
|
||||
}
|
||||
})
|
||||
|
||||
Context("when finding the plugin fails", func() {
|
||||
BeforeEach(func() {
|
||||
netConfigList.Plugins[1].Network.Type = "does-not-exist"
|
||||
})
|
||||
|
||||
It("returns the error", func() {
|
||||
_, err := cniConfig.AddNetworkList(netConfigList, runtimeConfig)
|
||||
Expect(err).To(MatchError(ContainSubstring(`failed to find plugin "does-not-exist"`)))
|
||||
})
|
||||
})
|
||||
|
||||
Context("when the second plugin errors", func() {
|
||||
BeforeEach(func() {
|
||||
plugins[1].debug.ReportError = "plugin error: banana"
|
||||
Expect(plugins[1].debug.WriteDebug(plugins[1].debugFilePath)).To(Succeed())
|
||||
})
|
||||
It("unmarshals and returns the error", func() {
|
||||
result, err := cniConfig.AddNetworkList(netConfigList, runtimeConfig)
|
||||
Expect(result).To(BeNil())
|
||||
Expect(err).To(MatchError("plugin error: banana"))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Describe("DelNetworkList", func() {
|
||||
It("executes all the plugins in reverse order with command DEL", func() {
|
||||
err := cniConfig.DelNetworkList(netConfigList, runtimeConfig)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
for i := 0; i < len(plugins); i++ {
|
||||
debug, err := noop_debug.ReadDebug(plugins[i].debugFilePath)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(debug.Command).To(Equal("DEL"))
|
||||
|
||||
// Must explicitly match JSON due to dict element ordering
|
||||
Expect(debug.CmdArgs.StdinData).To(MatchJSON(plugins[i].stdinData))
|
||||
debug.CmdArgs.StdinData = nil
|
||||
Expect(debug.CmdArgs).To(Equal(expectedCmdArgs))
|
||||
}
|
||||
})
|
||||
|
||||
Context("when finding the plugin fails", func() {
|
||||
BeforeEach(func() {
|
||||
netConfigList.Plugins[1].Network.Type = "does-not-exist"
|
||||
})
|
||||
|
||||
It("returns the error", func() {
|
||||
err := cniConfig.DelNetworkList(netConfigList, runtimeConfig)
|
||||
Expect(err).To(MatchError(ContainSubstring(`failed to find plugin "does-not-exist"`)))
|
||||
})
|
||||
})
|
||||
|
||||
Context("when the plugin errors", func() {
|
||||
BeforeEach(func() {
|
||||
plugins[1].debug.ReportError = "plugin error: banana"
|
||||
Expect(plugins[1].debug.WriteDebug(plugins[1].debugFilePath)).To(Succeed())
|
||||
})
|
||||
It("unmarshals and returns the error", func() {
|
||||
err := cniConfig.DelNetworkList(netConfigList, runtimeConfig)
|
||||
Expect(err).To(MatchError("plugin error: banana"))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
})
|
@ -1,83 +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"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"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() {
|
||||
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",
|
||||
}
|
||||
|
||||
cniConfig := &libcni.CNIConfig{Path: []string{filepath.Dir(pluginPath)}}
|
||||
|
||||
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() {
|
||||
// We need to be root (or have CAP_SYS_ADMIN...)
|
||||
if os.Geteuid() != 0 {
|
||||
Fail("must be run as root")
|
||||
}
|
||||
|
||||
example := legacy_examples.V010_Runtime
|
||||
|
||||
binPath, err := example.Build()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
for _, configName := range example.NetConfs {
|
||||
configStr, ok := legacy_examples.NetConfs[configName]
|
||||
if !ok {
|
||||
Fail("Invalid config name " + configName)
|
||||
}
|
||||
|
||||
cmd := exec.Command(binPath, pluginDirs...)
|
||||
cmd.Stdin = strings.NewReader(configStr)
|
||||
|
||||
session, err := gexec.Start(cmd, GinkgoWriter, GinkgoWriter)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
Eventually(session).Should(gexec.Exit(0))
|
||||
}
|
||||
})
|
||||
})
|
256
libcni/conf.go
256
libcni/conf.go
@ -1,256 +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 libcni
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
)
|
||||
|
||||
type NotFoundError struct {
|
||||
Dir string
|
||||
Name string
|
||||
}
|
||||
|
||||
func (e NotFoundError) Error() string {
|
||||
return fmt.Sprintf(`no net configuration with name "%s" in %s`, e.Name, e.Dir)
|
||||
}
|
||||
|
||||
type NoConfigsFoundError struct {
|
||||
Dir string
|
||||
}
|
||||
|
||||
func (e NoConfigsFoundError) Error() string {
|
||||
return fmt.Sprintf(`no net configurations found in %s`, e.Dir)
|
||||
}
|
||||
|
||||
func ConfFromBytes(bytes []byte) (*NetworkConfig, error) {
|
||||
conf := &NetworkConfig{Bytes: bytes}
|
||||
if err := json.Unmarshal(bytes, &conf.Network); err != nil {
|
||||
return nil, fmt.Errorf("error parsing configuration: %s", err)
|
||||
}
|
||||
return conf, nil
|
||||
}
|
||||
|
||||
func ConfFromFile(filename string) (*NetworkConfig, error) {
|
||||
bytes, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error reading %s: %s", filename, err)
|
||||
}
|
||||
return ConfFromBytes(bytes)
|
||||
}
|
||||
|
||||
func ConfListFromBytes(bytes []byte) (*NetworkConfigList, error) {
|
||||
rawList := make(map[string]interface{})
|
||||
if err := json.Unmarshal(bytes, &rawList); err != nil {
|
||||
return nil, fmt.Errorf("error parsing configuration list: %s", err)
|
||||
}
|
||||
|
||||
rawName, ok := rawList["name"]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("error parsing configuration list: no name")
|
||||
}
|
||||
name, ok := rawName.(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("error parsing configuration list: invalid name type %T", rawName)
|
||||
}
|
||||
|
||||
var cniVersion string
|
||||
rawVersion, ok := rawList["cniVersion"]
|
||||
if ok {
|
||||
cniVersion, ok = rawVersion.(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("error parsing configuration list: invalid cniVersion type %T", rawVersion)
|
||||
}
|
||||
}
|
||||
|
||||
list := &NetworkConfigList{
|
||||
Name: name,
|
||||
CNIVersion: cniVersion,
|
||||
Bytes: bytes,
|
||||
}
|
||||
|
||||
var plugins []interface{}
|
||||
plug, ok := rawList["plugins"]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("error parsing configuration list: no 'plugins' key")
|
||||
}
|
||||
plugins, ok = plug.([]interface{})
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("error parsing configuration list: invalid 'plugins' type %T", plug)
|
||||
}
|
||||
if len(plugins) == 0 {
|
||||
return nil, fmt.Errorf("error parsing configuration list: no plugins in list")
|
||||
}
|
||||
|
||||
for i, conf := range plugins {
|
||||
newBytes, err := json.Marshal(conf)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to marshal plugin config %d: %v", i, err)
|
||||
}
|
||||
netConf, err := ConfFromBytes(newBytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to parse plugin config %d: %v", i, err)
|
||||
}
|
||||
list.Plugins = append(list.Plugins, netConf)
|
||||
}
|
||||
|
||||
return list, nil
|
||||
}
|
||||
|
||||
func ConfListFromFile(filename string) (*NetworkConfigList, error) {
|
||||
bytes, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error reading %s: %s", filename, err)
|
||||
}
|
||||
return ConfListFromBytes(bytes)
|
||||
}
|
||||
|
||||
func ConfFiles(dir string, extensions []string) ([]string, error) {
|
||||
// In part, adapted from rkt/networking/podenv.go#listFiles
|
||||
files, err := ioutil.ReadDir(dir)
|
||||
switch {
|
||||
case err == nil: // break
|
||||
case os.IsNotExist(err):
|
||||
return nil, nil
|
||||
default:
|
||||
return nil, err
|
||||
}
|
||||
|
||||
confFiles := []string{}
|
||||
for _, f := range files {
|
||||
if f.IsDir() {
|
||||
continue
|
||||
}
|
||||
fileExt := filepath.Ext(f.Name())
|
||||
for _, ext := range extensions {
|
||||
if fileExt == ext {
|
||||
confFiles = append(confFiles, filepath.Join(dir, f.Name()))
|
||||
}
|
||||
}
|
||||
}
|
||||
return confFiles, nil
|
||||
}
|
||||
|
||||
func LoadConf(dir, name string) (*NetworkConfig, error) {
|
||||
files, err := ConfFiles(dir, []string{".conf", ".json"})
|
||||
switch {
|
||||
case err != nil:
|
||||
return nil, err
|
||||
case len(files) == 0:
|
||||
return nil, NoConfigsFoundError{Dir: dir}
|
||||
}
|
||||
sort.Strings(files)
|
||||
|
||||
for _, confFile := range files {
|
||||
conf, err := ConfFromFile(confFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if conf.Network.Name == name {
|
||||
return conf, nil
|
||||
}
|
||||
}
|
||||
return nil, NotFoundError{dir, name}
|
||||
}
|
||||
|
||||
func LoadConfList(dir, name string) (*NetworkConfigList, error) {
|
||||
files, err := ConfFiles(dir, []string{".conflist"})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sort.Strings(files)
|
||||
|
||||
for _, confFile := range files {
|
||||
conf, err := ConfListFromFile(confFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if conf.Name == name {
|
||||
return conf, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Try and load a network configuration file (instead of list)
|
||||
// from the same name, then upconvert.
|
||||
singleConf, err := LoadConf(dir, name)
|
||||
if err != nil {
|
||||
// A little extra logic so the error makes sense
|
||||
if _, ok := err.(NoConfigsFoundError); len(files) != 0 && ok {
|
||||
// Config lists found but no config files found
|
||||
return nil, NotFoundError{dir, name}
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
return ConfListFromConf(singleConf)
|
||||
}
|
||||
|
||||
func InjectConf(original *NetworkConfig, newValues map[string]interface{}) (*NetworkConfig, error) {
|
||||
config := make(map[string]interface{})
|
||||
err := json.Unmarshal(original.Bytes, &config)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unmarshal existing network bytes: %s", err)
|
||||
}
|
||||
|
||||
for key, value := range newValues {
|
||||
if key == "" {
|
||||
return nil, fmt.Errorf("keys cannot be empty")
|
||||
}
|
||||
|
||||
if value == nil {
|
||||
return nil, fmt.Errorf("key '%s' value must not be nil", key)
|
||||
}
|
||||
|
||||
config[key] = value
|
||||
}
|
||||
|
||||
newBytes, err := json.Marshal(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ConfFromBytes(newBytes)
|
||||
}
|
||||
|
||||
// ConfListFromConf "upconverts" a network config in to a NetworkConfigList,
|
||||
// with the single network as the only entry in the list.
|
||||
func ConfListFromConf(original *NetworkConfig) (*NetworkConfigList, error) {
|
||||
// Re-deserialize the config's json, then make a raw map configlist.
|
||||
// This may seem a bit strange, but it's to make the Bytes fields
|
||||
// actually make sense. Otherwise, the generated json is littered with
|
||||
// golang default values.
|
||||
|
||||
rawConfig := make(map[string]interface{})
|
||||
if err := json.Unmarshal(original.Bytes, &rawConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rawConfigList := map[string]interface{}{
|
||||
"name": original.Network.Name,
|
||||
"cniVersion": original.Network.CNIVersion,
|
||||
"plugins": []interface{}{rawConfig},
|
||||
}
|
||||
|
||||
b, err := json.Marshal(rawConfigList)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ConfListFromBytes(b)
|
||||
}
|
@ -1,431 +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", "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"},
|
||||
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" }`)
|
||||
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"},
|
||||
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`)))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
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{configDir, "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"},
|
||||
Bytes: []byte(`{ "name": "some-plugin" }`)}
|
||||
})
|
||||
|
||||
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"}`)
|
||||
|
||||
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"},
|
||||
Bytes: newPluginConfig,
|
||||
}))
|
||||
})
|
||||
|
||||
It("adds the new value for exiting key", func() {
|
||||
newPluginConfig := []byte(`{"name":"some-plugin","test":"changedValue"}`)
|
||||
|
||||
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"},
|
||||
Bytes: newPluginConfig,
|
||||
}))
|
||||
})
|
||||
|
||||
It("adds existing key & value", func() {
|
||||
newPluginConfig := []byte(`{"name":"some-plugin","test":"test"}`)
|
||||
|
||||
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"},
|
||||
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" }`)
|
||||
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))
|
||||
})
|
||||
|
||||
})
|
@ -1,67 +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"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
. "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 plugins = map[string]string{
|
||||
"noop": "github.com/containernetworking/cni/plugins/test/noop",
|
||||
"ptp": "github.com/containernetworking/cni/plugins/main/ptp",
|
||||
"host-local": "github.com/containernetworking/cni/plugins/ipam/host-local",
|
||||
}
|
||||
|
||||
var pluginPaths map[string]string
|
||||
var pluginDirs []string // array of plugin dirs
|
||||
|
||||
var _ = SynchronizedBeforeSuite(func() []byte {
|
||||
dirs := make([]string, 0, len(plugins))
|
||||
|
||||
for name, packagePath := range plugins {
|
||||
execPath, err := gexec.Build(packagePath)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
dirs = append(dirs, fmt.Sprintf("%s=%s", name, execPath))
|
||||
}
|
||||
|
||||
return []byte(strings.Join(dirs, ":"))
|
||||
}, func(crossNodeData []byte) {
|
||||
pluginPaths = make(map[string]string)
|
||||
for _, str := range strings.Split(string(crossNodeData), ":") {
|
||||
kvs := strings.SplitN(str, "=", 2)
|
||||
if len(kvs) != 2 {
|
||||
Fail("Invalid inter-node data...")
|
||||
}
|
||||
pluginPaths[kvs[0]] = kvs[1]
|
||||
pluginDirs = append(pluginDirs, filepath.Dir(kvs[1]))
|
||||
}
|
||||
})
|
||||
|
||||
var _ = SynchronizedAfterSuite(func() {}, func() {
|
||||
gexec.CleanupBuildArtifacts()
|
||||
})
|
@ -1,79 +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 invoke
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type CNIArgs interface {
|
||||
// For use with os/exec; i.e., return nil to inherit the
|
||||
// environment from this process
|
||||
AsEnv() []string
|
||||
}
|
||||
|
||||
type inherited struct{}
|
||||
|
||||
var inheritArgsFromEnv inherited
|
||||
|
||||
func (_ *inherited) AsEnv() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func ArgsFromEnv() CNIArgs {
|
||||
return &inheritArgsFromEnv
|
||||
}
|
||||
|
||||
type Args struct {
|
||||
Command string
|
||||
ContainerID string
|
||||
NetNS string
|
||||
PluginArgs [][2]string
|
||||
PluginArgsStr string
|
||||
IfName string
|
||||
Path string
|
||||
}
|
||||
|
||||
// Args implements the CNIArgs interface
|
||||
var _ CNIArgs = &Args{}
|
||||
|
||||
func (args *Args) AsEnv() []string {
|
||||
env := os.Environ()
|
||||
pluginArgsStr := args.PluginArgsStr
|
||||
if pluginArgsStr == "" {
|
||||
pluginArgsStr = stringify(args.PluginArgs)
|
||||
}
|
||||
|
||||
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 env
|
||||
}
|
||||
|
||||
// taken from rkt/networking/net_plugin.go
|
||||
func stringify(pluginArgs [][2]string) string {
|
||||
entries := make([]string, len(pluginArgs))
|
||||
|
||||
for i, kv := range pluginArgs {
|
||||
entries[i] = strings.Join(kv[:], "=")
|
||||
}
|
||||
|
||||
return strings.Join(entries, ";")
|
||||
}
|
@ -1,53 +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
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
)
|
||||
|
||||
func DelegateAdd(delegatePlugin string, netconf []byte) (types.Result, error) {
|
||||
if os.Getenv("CNI_COMMAND") != "ADD" {
|
||||
return nil, fmt.Errorf("CNI_COMMAND is not ADD")
|
||||
}
|
||||
|
||||
paths := filepath.SplitList(os.Getenv("CNI_PATH"))
|
||||
|
||||
pluginPath, err := FindInPath(delegatePlugin, paths)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ExecPluginWithResult(pluginPath, netconf, ArgsFromEnv())
|
||||
}
|
||||
|
||||
func DelegateDel(delegatePlugin string, netconf []byte) error {
|
||||
if os.Getenv("CNI_COMMAND") != "DEL" {
|
||||
return fmt.Errorf("CNI_COMMAND is not DEL")
|
||||
}
|
||||
|
||||
paths := filepath.SplitList(os.Getenv("CNI_PATH"))
|
||||
|
||||
pluginPath, err := FindInPath(delegatePlugin, paths)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ExecPluginWithoutResult(pluginPath, netconf, ArgsFromEnv())
|
||||
}
|
@ -1,95 +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 invoke
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/containernetworking/cni/pkg/version"
|
||||
)
|
||||
|
||||
func ExecPluginWithResult(pluginPath string, netconf []byte, args CNIArgs) (types.Result, error) {
|
||||
return defaultPluginExec.WithResult(pluginPath, netconf, args)
|
||||
}
|
||||
|
||||
func ExecPluginWithoutResult(pluginPath string, netconf []byte, args CNIArgs) error {
|
||||
return defaultPluginExec.WithoutResult(pluginPath, netconf, args)
|
||||
}
|
||||
|
||||
func GetVersionInfo(pluginPath string) (version.PluginInfo, error) {
|
||||
return defaultPluginExec.GetVersionInfo(pluginPath)
|
||||
}
|
||||
|
||||
var defaultPluginExec = &PluginExec{
|
||||
RawExec: &RawExec{Stderr: os.Stderr},
|
||||
VersionDecoder: &version.PluginDecoder{},
|
||||
}
|
||||
|
||||
type PluginExec struct {
|
||||
RawExec interface {
|
||||
ExecPlugin(pluginPath string, stdinData []byte, environ []string) ([]byte, error)
|
||||
}
|
||||
VersionDecoder interface {
|
||||
Decode(jsonBytes []byte) (version.PluginInfo, error)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *PluginExec) WithResult(pluginPath string, netconf []byte, args CNIArgs) (types.Result, error) {
|
||||
stdoutBytes, err := e.RawExec.ExecPlugin(pluginPath, netconf, args.AsEnv())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Plugin must return result in same version as specified in netconf
|
||||
versionDecoder := &version.ConfigDecoder{}
|
||||
confVersion, err := versionDecoder.Decode(netconf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return version.NewResult(confVersion, stdoutBytes)
|
||||
}
|
||||
|
||||
func (e *PluginExec) WithoutResult(pluginPath string, netconf []byte, args CNIArgs) error {
|
||||
_, err := e.RawExec.ExecPlugin(pluginPath, netconf, args.AsEnv())
|
||||
return err
|
||||
}
|
||||
|
||||
// GetVersionInfo returns the version information available about the plugin.
|
||||
// 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 (e *PluginExec) GetVersionInfo(pluginPath string) (version.PluginInfo, error) {
|
||||
args := &Args{
|
||||
Command: "VERSION",
|
||||
|
||||
// set fake values required by plugins built against an older version of skel
|
||||
NetNS: "dummy",
|
||||
IfName: "dummy",
|
||||
Path: "dummy",
|
||||
}
|
||||
stdin := []byte(fmt.Sprintf(`{"cniVersion":%q}`, version.Current()))
|
||||
stdoutBytes, err := e.RawExec.ExecPlugin(pluginPath, stdin, args.AsEnv())
|
||||
if err != nil {
|
||||
if err.Error() == "unknown CNI_COMMAND: VERSION" {
|
||||
return version.PluginSupports("0.1.0"), nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return e.VersionDecoder.Decode(stdoutBytes)
|
||||
}
|
@ -1,157 +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.PluginExec
|
||||
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 = &invoke.PluginExec{
|
||||
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 := pluginExec.WithResult(pluginPath, netconf, cniargs)
|
||||
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() {
|
||||
pluginExec.WithResult(pluginPath, netconf, cniargs)
|
||||
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 := pluginExec.WithResult(pluginPath, netconf, cniargs)
|
||||
Expect(err).To(MatchError("banana"))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Describe("without returning a result", func() {
|
||||
It("passes its arguments through to the rawExec", func() {
|
||||
pluginExec.WithoutResult(pluginPath, netconf, cniargs)
|
||||
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 := pluginExec.WithoutResult(pluginPath, netconf, cniargs)
|
||||
Expect(err).To(MatchError("banana"))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
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() {
|
||||
pluginExec.GetVersionInfo(pluginPath)
|
||||
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 := pluginExec.GetVersionInfo(pluginPath)
|
||||
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 := pluginExec.GetVersionInfo(pluginPath)
|
||||
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 := pluginExec.GetVersionInfo(pluginPath)
|
||||
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() {
|
||||
pluginExec.GetVersionInfo(pluginPath)
|
||||
|
||||
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"))
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
@ -1,27 +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 fakes
|
||||
|
||||
type CNIArgs struct {
|
||||
AsEnvCall struct {
|
||||
Returns struct {
|
||||
Env []string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (a *CNIArgs) AsEnv() []string {
|
||||
return a.AsEnvCall.Returns.Env
|
||||
}
|
@ -1,36 +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 fakes
|
||||
|
||||
type RawExec struct {
|
||||
ExecPluginCall struct {
|
||||
Received struct {
|
||||
PluginPath string
|
||||
StdinData []byte
|
||||
Environ []string
|
||||
}
|
||||
Returns struct {
|
||||
ResultBytes []byte
|
||||
Error error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (e *RawExec) ExecPlugin(pluginPath string, stdinData []byte, environ []string) ([]byte, error) {
|
||||
e.ExecPluginCall.Received.PluginPath = pluginPath
|
||||
e.ExecPluginCall.Received.StdinData = stdinData
|
||||
e.ExecPluginCall.Received.Environ = environ
|
||||
return e.ExecPluginCall.Returns.ResultBytes, e.ExecPluginCall.Returns.Error
|
||||
}
|
@ -1,34 +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 fakes
|
||||
|
||||
import "github.com/containernetworking/cni/pkg/version"
|
||||
|
||||
type VersionDecoder struct {
|
||||
DecodeCall struct {
|
||||
Received struct {
|
||||
JSONBytes []byte
|
||||
}
|
||||
Returns struct {
|
||||
PluginInfo version.PluginInfo
|
||||
Error error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (e *VersionDecoder) Decode(jsonData []byte) (version.PluginInfo, error) {
|
||||
e.DecodeCall.Received.JSONBytes = jsonData
|
||||
return e.DecodeCall.Returns.PluginInfo, e.DecodeCall.Returns.Error
|
||||
}
|
@ -1,43 +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 invoke
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// FindInPath returns the full path of the plugin by searching in the provided path
|
||||
func FindInPath(plugin string, paths []string) (string, error) {
|
||||
if plugin == "" {
|
||||
return "", fmt.Errorf("no plugin name provided")
|
||||
}
|
||||
|
||||
if len(paths) == 0 {
|
||||
return "", fmt.Errorf("no paths provided")
|
||||
}
|
||||
|
||||
for _, path := range paths {
|
||||
for _, fe := range ExecutableFileExtensions {
|
||||
fullpath := filepath.Join(path, plugin) + fe
|
||||
if fi, err := os.Stat(fullpath); err == nil && fi.Mode().IsRegular() {
|
||||
return fullpath, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("failed to find plugin %q in path %s", plugin, paths)
|
||||
}
|
@ -1,103 +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 (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/invoke"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("FindInPath", func() {
|
||||
var (
|
||||
multiplePaths []string
|
||||
pluginName string
|
||||
plugin2NameWithExt string
|
||||
plugin2NameWithoutExt string
|
||||
pluginDir string
|
||||
anotherTempDir string
|
||||
)
|
||||
|
||||
BeforeEach(func() {
|
||||
tempDir, err := ioutil.TempDir("", "cni-find")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
plugin, err := ioutil.TempFile(tempDir, "a-cni-plugin")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
plugin2Name := "a-plugin-with-extension" + invoke.ExecutableFileExtensions[0]
|
||||
plugin2, err := os.Create(filepath.Join(tempDir, plugin2Name))
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
anotherTempDir, err = ioutil.TempDir("", "nothing-here")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
multiplePaths = []string{anotherTempDir, tempDir}
|
||||
pluginDir, pluginName = filepath.Split(plugin.Name())
|
||||
_, plugin2NameWithExt = filepath.Split(plugin2.Name())
|
||||
plugin2NameWithoutExt = strings.Split(plugin2NameWithExt, ".")[0]
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
os.RemoveAll(pluginDir)
|
||||
os.RemoveAll(anotherTempDir)
|
||||
})
|
||||
|
||||
Context("when multiple paths are provided", func() {
|
||||
It("returns only the path to the plugin", func() {
|
||||
pluginPath, err := invoke.FindInPath(pluginName, multiplePaths)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(pluginPath).To(Equal(filepath.Join(pluginDir, pluginName)))
|
||||
})
|
||||
})
|
||||
|
||||
Context("when a plugin name without its file name extension is provided", func() {
|
||||
It("returns the path to the plugin, including its extension", func() {
|
||||
pluginPath, err := invoke.FindInPath(plugin2NameWithoutExt, multiplePaths)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(pluginPath).To(Equal(filepath.Join(pluginDir, plugin2NameWithExt)))
|
||||
})
|
||||
})
|
||||
|
||||
Context("when an error occurs", func() {
|
||||
Context("when no paths are provided", func() {
|
||||
It("returns an error noting no paths were provided", func() {
|
||||
_, err := invoke.FindInPath(pluginName, []string{})
|
||||
Expect(err).To(MatchError("no paths provided"))
|
||||
})
|
||||
})
|
||||
|
||||
Context("when no plugin is provided", func() {
|
||||
It("returns an error noting the plugin name wasn't found", func() {
|
||||
_, err := invoke.FindInPath("", multiplePaths)
|
||||
Expect(err).To(MatchError("no plugin name provided"))
|
||||
})
|
||||
})
|
||||
|
||||
Context("when the plugin cannot be found", func() {
|
||||
It("returns an error noting the path", func() {
|
||||
pathsWithNothing := []string{anotherTempDir}
|
||||
_, err := invoke.FindInPath(pluginName, pathsWithNothing)
|
||||
Expect(err).To(MatchError(fmt.Sprintf("failed to find plugin %q in path %s", pluginName, pathsWithNothing)))
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
@ -1,107 +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 (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/invoke"
|
||||
"github.com/containernetworking/cni/pkg/version"
|
||||
"github.com/containernetworking/cni/pkg/version/testhelpers"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/ginkgo/extensions/table"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("GetVersion, integration tests", func() {
|
||||
var (
|
||||
pluginDir string
|
||||
pluginPath string
|
||||
)
|
||||
|
||||
BeforeEach(func() {
|
||||
pluginDir, err := ioutil.TempDir("", "plugins")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
pluginPath = filepath.Join(pluginDir, "test-plugin")
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
Expect(os.RemoveAll(pluginDir)).To(Succeed())
|
||||
})
|
||||
|
||||
DescribeTable("correctly reporting plugin versions",
|
||||
func(gitRef string, pluginSource string, expectedVersions version.PluginInfo) {
|
||||
Expect(testhelpers.BuildAt([]byte(pluginSource), gitRef, pluginPath)).To(Succeed())
|
||||
versionInfo, err := invoke.GetVersionInfo(pluginPath)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
Expect(versionInfo.SupportedVersions()).To(ConsistOf(expectedVersions.SupportedVersions()))
|
||||
},
|
||||
|
||||
Entry("historical: before VERSION was introduced",
|
||||
git_ref_v010, plugin_source_no_custom_versions,
|
||||
version.PluginSupports("0.1.0"),
|
||||
),
|
||||
|
||||
Entry("historical: when VERSION was introduced but plugins couldn't customize it",
|
||||
git_ref_v020_no_custom_versions, plugin_source_no_custom_versions,
|
||||
version.PluginSupports("0.1.0", "0.2.0"),
|
||||
),
|
||||
|
||||
Entry("historical: when plugins started reporting their own version list",
|
||||
git_ref_v020_custom_versions, plugin_source_v020_custom_versions,
|
||||
version.PluginSupports("0.2.0", "0.999.0"),
|
||||
),
|
||||
|
||||
// this entry tracks the current behavior. Before you change it, ensure
|
||||
// that its previous behavior is captured in the most recent "historical" entry
|
||||
Entry("current",
|
||||
"HEAD", plugin_source_v020_custom_versions,
|
||||
version.PluginSupports("0.2.0", "0.999.0"),
|
||||
),
|
||||
)
|
||||
})
|
||||
|
||||
// a 0.2.0 plugin that can report its own versions
|
||||
const plugin_source_v020_custom_versions = `package main
|
||||
|
||||
import (
|
||||
"github.com/containernetworking/cni/pkg/skel"
|
||||
"github.com/containernetworking/cni/pkg/version"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func c(_ *skel.CmdArgs) error { fmt.Println("{}"); return nil }
|
||||
|
||||
func main() { skel.PluginMain(c, c, version.PluginSupports("0.2.0", "0.999.0")) }
|
||||
`
|
||||
const git_ref_v020_custom_versions = "bf31ed15"
|
||||
|
||||
// a minimal 0.1.0 / 0.2.0 plugin that cannot report it's own version support
|
||||
const plugin_source_no_custom_versions = `package main
|
||||
|
||||
import "github.com/containernetworking/cni/pkg/skel"
|
||||
import "fmt"
|
||||
|
||||
func c(_ *skel.CmdArgs) error { fmt.Println("{}"); return nil }
|
||||
|
||||
func main() { skel.PluginMain(c, c) }
|
||||
`
|
||||
|
||||
const git_ref_v010 = "2c482f4"
|
||||
const git_ref_v020_no_custom_versions = "349d66d"
|
@ -1,45 +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 (
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/onsi/gomega/gexec"
|
||||
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestInvoke(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "Invoke Suite")
|
||||
}
|
||||
|
||||
const packagePath = "github.com/containernetworking/cni/plugins/test/noop"
|
||||
|
||||
var pathToPlugin string
|
||||
|
||||
var _ = SynchronizedBeforeSuite(func() []byte {
|
||||
var err error
|
||||
pathToPlugin, err = gexec.Build(packagePath)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return []byte(pathToPlugin)
|
||||
}, func(crossNodeData []byte) {
|
||||
pathToPlugin = string(crossNodeData)
|
||||
})
|
||||
|
||||
var _ = SynchronizedAfterSuite(func() {}, func() {
|
||||
gexec.CleanupBuildArtifacts()
|
||||
})
|
@ -1,20 +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.
|
||||
|
||||
// +build darwin dragonfly freebsd linux netbsd opensbd solaris
|
||||
|
||||
package invoke
|
||||
|
||||
// Valid file extensions for plugin executables.
|
||||
var ExecutableFileExtensions = []string{""}
|
@ -1,18 +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
|
||||
|
||||
// Valid file extensions for plugin executables.
|
||||
var ExecutableFileExtensions = []string{".exe", ""}
|
@ -1,63 +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
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os/exec"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
)
|
||||
|
||||
type RawExec struct {
|
||||
Stderr io.Writer
|
||||
}
|
||||
|
||||
func (e *RawExec) ExecPlugin(pluginPath string, stdinData []byte, environ []string) ([]byte, error) {
|
||||
stdout := &bytes.Buffer{}
|
||||
|
||||
c := exec.Cmd{
|
||||
Env: environ,
|
||||
Path: pluginPath,
|
||||
Args: []string{pluginPath},
|
||||
Stdin: bytes.NewBuffer(stdinData),
|
||||
Stdout: stdout,
|
||||
Stderr: e.Stderr,
|
||||
}
|
||||
if err := c.Run(); err != nil {
|
||||
return nil, pluginErr(err, stdout.Bytes())
|
||||
}
|
||||
|
||||
return stdout.Bytes(), nil
|
||||
}
|
||||
|
||||
func pluginErr(err error, output []byte) error {
|
||||
if _, ok := err.(*exec.ExitError); ok {
|
||||
emsg := types.Error{}
|
||||
if perr := json.Unmarshal(output, &emsg); perr != nil {
|
||||
return fmt.Errorf("netplugin failed but error parsing its diagnostic message %q: %v", string(output), perr)
|
||||
}
|
||||
details := ""
|
||||
if emsg.Details != "" {
|
||||
details = fmt.Sprintf("; %v", emsg.Details)
|
||||
}
|
||||
return fmt.Errorf("%v%v", emsg.Msg, details)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
@ -1,123 +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 (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/invoke"
|
||||
|
||||
noop_debug "github.com/containernetworking/cni/plugins/test/noop/debug"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("RawExec", func() {
|
||||
var (
|
||||
debugFileName string
|
||||
debug *noop_debug.Debug
|
||||
environ []string
|
||||
stdin []byte
|
||||
execer *invoke.RawExec
|
||||
)
|
||||
|
||||
const reportResult = `{ "some": "result" }`
|
||||
|
||||
BeforeEach(func() {
|
||||
debugFile, err := ioutil.TempFile("", "cni_debug")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(debugFile.Close()).To(Succeed())
|
||||
debugFileName = debugFile.Name()
|
||||
|
||||
debug = &noop_debug.Debug{
|
||||
ReportResult: reportResult,
|
||||
ReportStderr: "some stderr message",
|
||||
}
|
||||
Expect(debug.WriteDebug(debugFileName)).To(Succeed())
|
||||
|
||||
environ = []string{
|
||||
"CNI_COMMAND=ADD",
|
||||
"CNI_CONTAINERID=some-container-id",
|
||||
"CNI_ARGS=DEBUG=" + debugFileName,
|
||||
"CNI_NETNS=/some/netns/path",
|
||||
"CNI_PATH=/some/bin/path",
|
||||
"CNI_IFNAME=some-eth0",
|
||||
}
|
||||
stdin = []byte(`{"some":"stdin-json", "cniVersion": "0.3.1"}`)
|
||||
execer = &invoke.RawExec{}
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
Expect(os.Remove(debugFileName)).To(Succeed())
|
||||
})
|
||||
|
||||
It("runs the plugin with the given stdin and environment", func() {
|
||||
_, err := execer.ExecPlugin(pathToPlugin, stdin, environ)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
debug, err := noop_debug.ReadDebug(debugFileName)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(debug.Command).To(Equal("ADD"))
|
||||
Expect(debug.CmdArgs.StdinData).To(Equal(stdin))
|
||||
Expect(debug.CmdArgs.Netns).To(Equal("/some/netns/path"))
|
||||
})
|
||||
|
||||
It("returns the resulting stdout as bytes", func() {
|
||||
resultBytes, err := execer.ExecPlugin(pathToPlugin, stdin, environ)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
Expect(resultBytes).To(BeEquivalentTo(reportResult))
|
||||
})
|
||||
|
||||
Context("when the Stderr writer is set", func() {
|
||||
var stderrBuffer *bytes.Buffer
|
||||
|
||||
BeforeEach(func() {
|
||||
stderrBuffer = &bytes.Buffer{}
|
||||
execer.Stderr = stderrBuffer
|
||||
})
|
||||
|
||||
It("forwards any stderr bytes to the Stderr writer", func() {
|
||||
_, err := execer.ExecPlugin(pathToPlugin, stdin, environ)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
Expect(stderrBuffer.String()).To(Equal("some stderr message"))
|
||||
})
|
||||
})
|
||||
|
||||
Context("when the plugin errors", func() {
|
||||
BeforeEach(func() {
|
||||
debug.ReportError = "banana"
|
||||
Expect(debug.WriteDebug(debugFileName)).To(Succeed())
|
||||
})
|
||||
|
||||
It("wraps and returns the error", func() {
|
||||
_, err := execer.ExecPlugin(pathToPlugin, stdin, environ)
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err).To(MatchError("banana"))
|
||||
})
|
||||
})
|
||||
|
||||
Context("when the system is unable to execute the plugin", func() {
|
||||
It("returns the error", func() {
|
||||
_, err := execer.ExecPlugin("/tmp/some/invalid/plugin/path", stdin, environ)
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err).To(MatchError(ContainSubstring("/tmp/some/invalid/plugin/path")))
|
||||
})
|
||||
})
|
||||
})
|
@ -1,51 +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 ip
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"net"
|
||||
)
|
||||
|
||||
// NextIP returns IP incremented by 1
|
||||
func NextIP(ip net.IP) net.IP {
|
||||
i := ipToInt(ip)
|
||||
return intToIP(i.Add(i, big.NewInt(1)))
|
||||
}
|
||||
|
||||
// PrevIP returns IP decremented by 1
|
||||
func PrevIP(ip net.IP) net.IP {
|
||||
i := ipToInt(ip)
|
||||
return intToIP(i.Sub(i, big.NewInt(1)))
|
||||
}
|
||||
|
||||
func ipToInt(ip net.IP) *big.Int {
|
||||
if v := ip.To4(); v != nil {
|
||||
return big.NewInt(0).SetBytes(v)
|
||||
}
|
||||
return big.NewInt(0).SetBytes(ip.To16())
|
||||
}
|
||||
|
||||
func intToIP(i *big.Int) net.IP {
|
||||
return net.IP(i.Bytes())
|
||||
}
|
||||
|
||||
// Network masks off the host portion of the IP
|
||||
func Network(ipn *net.IPNet) *net.IPNet {
|
||||
return &net.IPNet{
|
||||
IP: ipn.IP.Mask(ipn.Mask),
|
||||
Mask: ipn.Mask,
|
||||
}
|
||||
}
|
@ -1,27 +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 ip_test
|
||||
|
||||
import (
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestIp(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "Ip Suite")
|
||||
}
|
@ -1,31 +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 ip
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
func EnableIP4Forward() error {
|
||||
return echo1("/proc/sys/net/ipv4/ip_forward")
|
||||
}
|
||||
|
||||
func EnableIP6Forward() error {
|
||||
return echo1("/proc/sys/net/ipv6/conf/all/forwarding")
|
||||
}
|
||||
|
||||
func echo1(f string) error {
|
||||
return ioutil.WriteFile(f, []byte("1"), 0644)
|
||||
}
|
@ -1,66 +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 ip
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/coreos/go-iptables/iptables"
|
||||
)
|
||||
|
||||
// SetupIPMasq installs iptables rules to masquerade traffic
|
||||
// coming from ipn and going outside of it
|
||||
func SetupIPMasq(ipn *net.IPNet, chain string, comment string) error {
|
||||
ipt, err := iptables.New()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to locate iptables: %v", err)
|
||||
}
|
||||
|
||||
if err = ipt.NewChain("nat", chain); err != nil {
|
||||
if err.(*iptables.Error).ExitStatus() != 1 {
|
||||
// TODO(eyakubovich): assumes exit status 1 implies chain exists
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err = ipt.AppendUnique("nat", chain, "-d", ipn.String(), "-j", "ACCEPT", "-m", "comment", "--comment", comment); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = ipt.AppendUnique("nat", chain, "!", "-d", "224.0.0.0/4", "-j", "MASQUERADE", "-m", "comment", "--comment", comment); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ipt.AppendUnique("nat", "POSTROUTING", "-s", ipn.String(), "-j", chain, "-m", "comment", "--comment", comment)
|
||||
}
|
||||
|
||||
// TeardownIPMasq undoes the effects of SetupIPMasq
|
||||
func TeardownIPMasq(ipn *net.IPNet, chain string, comment string) error {
|
||||
ipt, err := iptables.New()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to locate iptables: %v", err)
|
||||
}
|
||||
|
||||
if err = ipt.Delete("nat", "POSTROUTING", "-s", ipn.String(), "-j", chain, "-m", "comment", "--comment", comment); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = ipt.ClearChain("nat", chain); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ipt.DeleteChain("nat", chain)
|
||||
}
|
219
pkg/ip/link.go
219
pkg/ip/link.go
@ -1,219 +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 ip
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/ns"
|
||||
"github.com/containernetworking/cni/pkg/utils/hwaddr"
|
||||
"github.com/vishvananda/netlink"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrLinkNotFound = errors.New("link not found")
|
||||
)
|
||||
|
||||
func makeVethPair(name, peer string, mtu int) (netlink.Link, error) {
|
||||
veth := &netlink.Veth{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: name,
|
||||
Flags: net.FlagUp,
|
||||
MTU: mtu,
|
||||
},
|
||||
PeerName: peer,
|
||||
}
|
||||
if err := netlink.LinkAdd(veth); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return veth, nil
|
||||
}
|
||||
|
||||
func peerExists(name string) bool {
|
||||
if _, err := netlink.LinkByName(name); err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func makeVeth(name string, mtu int) (peerName string, veth netlink.Link, err error) {
|
||||
for i := 0; i < 10; i++ {
|
||||
peerName, err = RandomVethName()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
veth, err = makeVethPair(name, peerName, mtu)
|
||||
switch {
|
||||
case err == nil:
|
||||
return
|
||||
|
||||
case os.IsExist(err):
|
||||
if peerExists(peerName) {
|
||||
continue
|
||||
}
|
||||
err = fmt.Errorf("container veth name provided (%v) already exists", name)
|
||||
return
|
||||
|
||||
default:
|
||||
err = fmt.Errorf("failed to make veth pair: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// should really never be hit
|
||||
err = fmt.Errorf("failed to find a unique veth name")
|
||||
return
|
||||
}
|
||||
|
||||
// RandomVethName returns string "veth" with random prefix (hashed from entropy)
|
||||
func RandomVethName() (string, error) {
|
||||
entropy := make([]byte, 4)
|
||||
_, err := rand.Reader.Read(entropy)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to generate random veth name: %v", err)
|
||||
}
|
||||
|
||||
// NetworkManager (recent versions) will ignore veth devices that start with "veth"
|
||||
return fmt.Sprintf("veth%x", entropy), nil
|
||||
}
|
||||
|
||||
func RenameLink(curName, newName string) error {
|
||||
link, err := netlink.LinkByName(curName)
|
||||
if err == nil {
|
||||
err = netlink.LinkSetName(link, newName)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func ifaceFromNetlinkLink(l netlink.Link) net.Interface {
|
||||
a := l.Attrs()
|
||||
return net.Interface{
|
||||
Index: a.Index,
|
||||
MTU: a.MTU,
|
||||
Name: a.Name,
|
||||
HardwareAddr: a.HardwareAddr,
|
||||
Flags: a.Flags,
|
||||
}
|
||||
}
|
||||
|
||||
// SetupVeth sets up a pair of virtual ethernet devices.
|
||||
// Call SetupVeth from inside the container netns. It will create both veth
|
||||
// devices and move the host-side veth into the provided hostNS namespace.
|
||||
// On success, SetupVeth returns (hostVeth, containerVeth, nil)
|
||||
func SetupVeth(contVethName string, mtu int, hostNS ns.NetNS) (net.Interface, net.Interface, error) {
|
||||
hostVethName, contVeth, err := makeVeth(contVethName, mtu)
|
||||
if err != nil {
|
||||
return net.Interface{}, net.Interface{}, err
|
||||
}
|
||||
|
||||
if err = netlink.LinkSetUp(contVeth); err != nil {
|
||||
return net.Interface{}, net.Interface{}, fmt.Errorf("failed to set %q up: %v", contVethName, err)
|
||||
}
|
||||
|
||||
hostVeth, err := netlink.LinkByName(hostVethName)
|
||||
if err != nil {
|
||||
return net.Interface{}, net.Interface{}, fmt.Errorf("failed to lookup %q: %v", hostVethName, err)
|
||||
}
|
||||
|
||||
if err = netlink.LinkSetNsFd(hostVeth, int(hostNS.Fd())); err != nil {
|
||||
return net.Interface{}, net.Interface{}, fmt.Errorf("failed to move veth to host netns: %v", err)
|
||||
}
|
||||
|
||||
err = hostNS.Do(func(_ ns.NetNS) error {
|
||||
hostVeth, err = netlink.LinkByName(hostVethName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to lookup %q in %q: %v", hostVethName, hostNS.Path(), err)
|
||||
}
|
||||
|
||||
if err = netlink.LinkSetUp(hostVeth); err != nil {
|
||||
return fmt.Errorf("failed to set %q up: %v", hostVethName, err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return net.Interface{}, net.Interface{}, err
|
||||
}
|
||||
return ifaceFromNetlinkLink(hostVeth), ifaceFromNetlinkLink(contVeth), nil
|
||||
}
|
||||
|
||||
// DelLinkByName removes an interface link.
|
||||
func DelLinkByName(ifName string) error {
|
||||
iface, err := netlink.LinkByName(ifName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to lookup %q: %v", ifName, err)
|
||||
}
|
||||
|
||||
if err = netlink.LinkDel(iface); err != nil {
|
||||
return fmt.Errorf("failed to delete %q: %v", ifName, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DelLinkByNameAddr remove an interface returns its IP address
|
||||
// of the specified family
|
||||
func DelLinkByNameAddr(ifName string, family int) (*net.IPNet, error) {
|
||||
iface, err := netlink.LinkByName(ifName)
|
||||
if err != nil {
|
||||
if err != nil && err.Error() == "Link not found" {
|
||||
return nil, ErrLinkNotFound
|
||||
}
|
||||
return nil, fmt.Errorf("failed to lookup %q: %v", ifName, err)
|
||||
}
|
||||
|
||||
addrs, err := netlink.AddrList(iface, family)
|
||||
if err != nil || len(addrs) == 0 {
|
||||
return nil, fmt.Errorf("failed to get IP addresses for %q: %v", ifName, err)
|
||||
}
|
||||
|
||||
if err = netlink.LinkDel(iface); err != nil {
|
||||
return nil, fmt.Errorf("failed to delete %q: %v", ifName, err)
|
||||
}
|
||||
|
||||
return addrs[0].IPNet, nil
|
||||
}
|
||||
|
||||
func SetHWAddrByIP(ifName string, ip4 net.IP, ip6 net.IP) error {
|
||||
iface, err := netlink.LinkByName(ifName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to lookup %q: %v", ifName, err)
|
||||
}
|
||||
|
||||
switch {
|
||||
case ip4 == nil && ip6 == nil:
|
||||
return fmt.Errorf("neither ip4 or ip6 specified")
|
||||
|
||||
case ip4 != nil:
|
||||
{
|
||||
hwAddr, err := hwaddr.GenerateHardwareAddr4(ip4, hwaddr.PrivateMACPrefix)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate hardware addr: %v", err)
|
||||
}
|
||||
if err = netlink.LinkSetHardwareAddr(iface, hwAddr); err != nil {
|
||||
return fmt.Errorf("failed to add hardware addr to %q: %v", ifName, err)
|
||||
}
|
||||
}
|
||||
case ip6 != nil:
|
||||
// TODO: IPv6
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -1,273 +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 ip_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/ip"
|
||||
"github.com/containernetworking/cni/pkg/ns"
|
||||
|
||||
"github.com/vishvananda/netlink"
|
||||
"github.com/vishvananda/netlink/nl"
|
||||
)
|
||||
|
||||
func getHwAddr(linkname string) string {
|
||||
veth, err := netlink.LinkByName(linkname)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return fmt.Sprintf("%s", veth.Attrs().HardwareAddr)
|
||||
}
|
||||
|
||||
var _ = Describe("Link", func() {
|
||||
const (
|
||||
ifaceFormatString string = "i%d"
|
||||
mtu int = 1400
|
||||
ip4onehwaddr = "0a:58:01:01:01:01"
|
||||
)
|
||||
var (
|
||||
hostNetNS ns.NetNS
|
||||
containerNetNS ns.NetNS
|
||||
ifaceCounter int = 0
|
||||
hostVeth net.Interface
|
||||
containerVeth net.Interface
|
||||
hostVethName string
|
||||
containerVethName string
|
||||
|
||||
ip4one = net.ParseIP("1.1.1.1")
|
||||
ip4two = net.ParseIP("1.1.1.2")
|
||||
originalRandReader = rand.Reader
|
||||
)
|
||||
|
||||
BeforeEach(func() {
|
||||
var err error
|
||||
|
||||
hostNetNS, err = ns.NewNS()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
containerNetNS, err = ns.NewNS()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
fakeBytes := make([]byte, 20)
|
||||
//to be reset in AfterEach block
|
||||
rand.Reader = bytes.NewReader(fakeBytes)
|
||||
|
||||
_ = containerNetNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
hostVeth, containerVeth, err = ip.SetupVeth(fmt.Sprintf(ifaceFormatString, ifaceCounter), mtu, hostNetNS)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
hostVethName = hostVeth.Name
|
||||
containerVethName = containerVeth.Name
|
||||
|
||||
return nil
|
||||
})
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
Expect(containerNetNS.Close()).To(Succeed())
|
||||
Expect(hostNetNS.Close()).To(Succeed())
|
||||
ifaceCounter++
|
||||
rand.Reader = originalRandReader
|
||||
})
|
||||
|
||||
It("SetupVeth must put the veth endpoints into the separate namespaces", func() {
|
||||
_ = containerNetNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
containerVethFromName, err := netlink.LinkByName(containerVethName)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(containerVethFromName.Attrs().Index).To(Equal(containerVeth.Index))
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
_ = hostNetNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
hostVethFromName, err := netlink.LinkByName(hostVethName)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(hostVethFromName.Attrs().Index).To(Equal(hostVeth.Index))
|
||||
|
||||
return nil
|
||||
})
|
||||
})
|
||||
|
||||
Context("when container already has an interface with the same name", func() {
|
||||
It("returns useful error", func() {
|
||||
_ = containerNetNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
_, _, err := ip.SetupVeth(containerVethName, mtu, hostNetNS)
|
||||
Expect(err.Error()).To(Equal(fmt.Sprintf("container veth name provided (%s) already exists", containerVethName)))
|
||||
|
||||
return nil
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Context("deleting an non-existent device", func() {
|
||||
It("returns known error", func() {
|
||||
_ = containerNetNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
// This string should match the expected error codes in the cmdDel functions of some of the plugins
|
||||
_, err := ip.DelLinkByNameAddr("THIS_DONT_EXIST", netlink.FAMILY_V4)
|
||||
Expect(err).To(Equal(ip.ErrLinkNotFound))
|
||||
|
||||
return nil
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Context("when there is no name available for the host-side", func() {
|
||||
BeforeEach(func() {
|
||||
//adding different interface to container ns
|
||||
containerVethName += "0"
|
||||
})
|
||||
It("returns useful error", func() {
|
||||
_ = containerNetNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
_, _, err := ip.SetupVeth(containerVethName, mtu, hostNetNS)
|
||||
Expect(err.Error()).To(Equal("failed to move veth to host netns: file exists"))
|
||||
|
||||
return nil
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Context("when there is no name conflict for the host or container interfaces", func() {
|
||||
BeforeEach(func() {
|
||||
//adding different interface to container and host ns
|
||||
containerVethName += "0"
|
||||
rand.Reader = originalRandReader
|
||||
})
|
||||
It("successfully creates the second veth pair", func() {
|
||||
_ = containerNetNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
hostVeth, _, err := ip.SetupVeth(containerVethName, mtu, hostNetNS)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
hostVethName = hostVeth.Name
|
||||
return nil
|
||||
})
|
||||
|
||||
//verify veths are in different namespaces
|
||||
_ = containerNetNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
_, err := netlink.LinkByName(containerVethName)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
_ = hostNetNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
_, err := netlink.LinkByName(hostVethName)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
return nil
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
It("DelLinkByName must delete the veth endpoints", func() {
|
||||
_ = containerNetNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
// this will delete the host endpoint too
|
||||
err := ip.DelLinkByName(containerVethName)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
_, err = netlink.LinkByName(containerVethName)
|
||||
Expect(err).To(HaveOccurred())
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
_ = hostNetNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
_, err := netlink.LinkByName(hostVethName)
|
||||
Expect(err).To(HaveOccurred())
|
||||
|
||||
return nil
|
||||
})
|
||||
})
|
||||
|
||||
It("DelLinkByNameAddr must throw an error for configured interfaces", func() {
|
||||
_ = containerNetNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
// this will delete the host endpoint too
|
||||
addr, err := ip.DelLinkByNameAddr(containerVethName, nl.FAMILY_V4)
|
||||
Expect(err).To(HaveOccurred())
|
||||
|
||||
var ipNetNil *net.IPNet
|
||||
Expect(addr).To(Equal(ipNetNil))
|
||||
return nil
|
||||
})
|
||||
})
|
||||
|
||||
It("SetHWAddrByIP must change the interface hwaddr and be predictable", func() {
|
||||
|
||||
_ = containerNetNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
var err error
|
||||
hwaddrBefore := getHwAddr(containerVethName)
|
||||
|
||||
err = ip.SetHWAddrByIP(containerVethName, ip4one, nil)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
hwaddrAfter1 := getHwAddr(containerVethName)
|
||||
|
||||
Expect(hwaddrBefore).NotTo(Equal(hwaddrAfter1))
|
||||
Expect(hwaddrAfter1).To(Equal(ip4onehwaddr))
|
||||
|
||||
return nil
|
||||
})
|
||||
})
|
||||
|
||||
It("SetHWAddrByIP must be injective", func() {
|
||||
|
||||
_ = containerNetNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
err := ip.SetHWAddrByIP(containerVethName, ip4one, nil)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
hwaddrAfter1 := getHwAddr(containerVethName)
|
||||
|
||||
err = ip.SetHWAddrByIP(containerVethName, ip4two, nil)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
hwaddrAfter2 := getHwAddr(containerVethName)
|
||||
|
||||
Expect(hwaddrAfter1).NotTo(Equal(hwaddrAfter2))
|
||||
return nil
|
||||
})
|
||||
})
|
||||
})
|
@ -1,27 +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 ip
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/vishvananda/netlink"
|
||||
)
|
||||
|
||||
// AddDefaultRoute sets the default route on the given gateway.
|
||||
func AddDefaultRoute(gw net.IP, dev netlink.Link) error {
|
||||
_, defNet, _ := net.ParseCIDR("0.0.0.0/0")
|
||||
return AddRoute(defNet, gw, dev)
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
// Copyright 2015-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 ip
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/vishvananda/netlink"
|
||||
)
|
||||
|
||||
// AddRoute adds a universally-scoped route to a device.
|
||||
func AddRoute(ipn *net.IPNet, gw net.IP, dev netlink.Link) error {
|
||||
return netlink.RouteAdd(&netlink.Route{
|
||||
LinkIndex: dev.Attrs().Index,
|
||||
Scope: netlink.SCOPE_UNIVERSE,
|
||||
Dst: ipn,
|
||||
Gw: gw,
|
||||
})
|
||||
}
|
||||
|
||||
// AddHostRoute adds a host-scoped route to a device.
|
||||
func AddHostRoute(ipn *net.IPNet, gw net.IP, dev netlink.Link) error {
|
||||
return netlink.RouteAdd(&netlink.Route{
|
||||
LinkIndex: dev.Attrs().Index,
|
||||
Scope: netlink.SCOPE_HOST,
|
||||
Dst: ipn,
|
||||
Gw: gw,
|
||||
})
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
// Copyright 2015-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.
|
||||
|
||||
// +build !linux
|
||||
|
||||
package ip
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/vishvananda/netlink"
|
||||
)
|
||||
|
||||
// AddRoute adds a universally-scoped route to a device.
|
||||
func AddRoute(ipn *net.IPNet, gw net.IP, dev netlink.Link) error {
|
||||
return types.NotImplementedError
|
||||
}
|
||||
|
||||
// AddHostRoute adds a host-scoped route to a device.
|
||||
func AddHostRoute(ipn *net.IPNet, gw net.IP, dev netlink.Link) error {
|
||||
return types.NotImplementedError
|
||||
}
|
@ -1,93 +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 ipam
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/invoke"
|
||||
"github.com/containernetworking/cni/pkg/ip"
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/containernetworking/cni/pkg/types/current"
|
||||
|
||||
"github.com/vishvananda/netlink"
|
||||
)
|
||||
|
||||
func ExecAdd(plugin string, netconf []byte) (types.Result, error) {
|
||||
return invoke.DelegateAdd(plugin, netconf)
|
||||
}
|
||||
|
||||
func ExecDel(plugin string, netconf []byte) error {
|
||||
return invoke.DelegateDel(plugin, netconf)
|
||||
}
|
||||
|
||||
// ConfigureIface takes the result of IPAM plugin and
|
||||
// applies to the ifName interface
|
||||
func ConfigureIface(ifName string, res *current.Result) error {
|
||||
if len(res.Interfaces) == 0 {
|
||||
return fmt.Errorf("no interfaces to configure")
|
||||
}
|
||||
|
||||
link, err := netlink.LinkByName(ifName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to lookup %q: %v", ifName, err)
|
||||
}
|
||||
|
||||
if err := netlink.LinkSetUp(link); err != nil {
|
||||
return fmt.Errorf("failed to set %q UP: %v", ifName, err)
|
||||
}
|
||||
|
||||
var v4gw, v6gw net.IP
|
||||
for _, ipc := range res.IPs {
|
||||
if int(ipc.Interface) >= len(res.Interfaces) || res.Interfaces[ipc.Interface].Name != ifName {
|
||||
// IP address is for a different interface
|
||||
return fmt.Errorf("failed to add IP addr %v to %q: invalid interface index", ipc, ifName)
|
||||
}
|
||||
|
||||
addr := &netlink.Addr{IPNet: &ipc.Address, Label: ""}
|
||||
if err = netlink.AddrAdd(link, addr); err != nil {
|
||||
return fmt.Errorf("failed to add IP addr %v to %q: %v", ipc, ifName, err)
|
||||
}
|
||||
|
||||
gwIsV4 := ipc.Gateway.To4() != nil
|
||||
if gwIsV4 && v4gw == nil {
|
||||
v4gw = ipc.Gateway
|
||||
} else if !gwIsV4 && v6gw == nil {
|
||||
v6gw = ipc.Gateway
|
||||
}
|
||||
}
|
||||
|
||||
for _, r := range res.Routes {
|
||||
routeIsV4 := r.Dst.IP.To4() != nil
|
||||
gw := r.GW
|
||||
if gw == nil {
|
||||
if routeIsV4 && v4gw != nil {
|
||||
gw = v4gw
|
||||
} else if !routeIsV4 && v6gw != nil {
|
||||
gw = v6gw
|
||||
}
|
||||
}
|
||||
if err = ip.AddRoute(&r.Dst, gw, link); err != nil {
|
||||
// we skip over duplicate routes as we assume the first one wins
|
||||
if !os.IsExist(err) {
|
||||
return fmt.Errorf("failed to add route '%v via %v dev %v': %v", r.Dst, gw, ifName, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -1,27 +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 ipam_test
|
||||
|
||||
import (
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestIpam(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "Ipam Suite")
|
||||
}
|
@ -1,258 +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 ipam
|
||||
|
||||
import (
|
||||
"net"
|
||||
"syscall"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/ns"
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/containernetworking/cni/pkg/types/current"
|
||||
|
||||
"github.com/vishvananda/netlink"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
const LINK_NAME = "eth0"
|
||||
|
||||
func ipNetEqual(a, b *net.IPNet) bool {
|
||||
aPrefix, aBits := a.Mask.Size()
|
||||
bPrefix, bBits := b.Mask.Size()
|
||||
if aPrefix != bPrefix || aBits != bBits {
|
||||
return false
|
||||
}
|
||||
return a.IP.Equal(b.IP)
|
||||
}
|
||||
|
||||
var _ = Describe("IPAM Operations", func() {
|
||||
var originalNS ns.NetNS
|
||||
var ipv4, ipv6, routev4, routev6 *net.IPNet
|
||||
var ipgw4, ipgw6, routegwv4, routegwv6 net.IP
|
||||
var result *current.Result
|
||||
|
||||
BeforeEach(func() {
|
||||
// Create a new NetNS so we don't modify the host
|
||||
var err error
|
||||
originalNS, err = ns.NewNS()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
// Add master
|
||||
err = netlink.LinkAdd(&netlink.Dummy{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: LINK_NAME,
|
||||
},
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
_, err = netlink.LinkByName(LINK_NAME)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
ipv4, err = types.ParseCIDR("1.2.3.30/24")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(ipv4).NotTo(BeNil())
|
||||
|
||||
_, routev4, err = net.ParseCIDR("15.5.6.8/24")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(routev4).NotTo(BeNil())
|
||||
routegwv4 = net.ParseIP("1.2.3.5")
|
||||
Expect(routegwv4).NotTo(BeNil())
|
||||
|
||||
ipgw4 = net.ParseIP("1.2.3.1")
|
||||
Expect(ipgw4).NotTo(BeNil())
|
||||
|
||||
ipv6, err = types.ParseCIDR("abcd:1234:ffff::cdde/64")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(ipv6).NotTo(BeNil())
|
||||
|
||||
_, routev6, err = net.ParseCIDR("1111:dddd::aaaa/80")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(routev6).NotTo(BeNil())
|
||||
routegwv6 = net.ParseIP("abcd:1234:ffff::10")
|
||||
Expect(routegwv6).NotTo(BeNil())
|
||||
|
||||
ipgw6 = net.ParseIP("abcd:1234:ffff::1")
|
||||
Expect(ipgw6).NotTo(BeNil())
|
||||
|
||||
result = ¤t.Result{
|
||||
Interfaces: []*current.Interface{
|
||||
{
|
||||
Name: "eth0",
|
||||
Mac: "00:11:22:33:44:55",
|
||||
Sandbox: "/proc/3553/ns/net",
|
||||
},
|
||||
{
|
||||
Name: "fake0",
|
||||
Mac: "00:33:44:55:66:77",
|
||||
Sandbox: "/proc/1234/ns/net",
|
||||
},
|
||||
},
|
||||
IPs: []*current.IPConfig{
|
||||
{
|
||||
Version: "4",
|
||||
Interface: 0,
|
||||
Address: *ipv4,
|
||||
Gateway: ipgw4,
|
||||
},
|
||||
{
|
||||
Version: "6",
|
||||
Interface: 0,
|
||||
Address: *ipv6,
|
||||
Gateway: ipgw6,
|
||||
},
|
||||
},
|
||||
Routes: []*types.Route{
|
||||
{Dst: *routev4, GW: routegwv4},
|
||||
{Dst: *routev6, GW: routegwv6},
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
Expect(originalNS.Close()).To(Succeed())
|
||||
})
|
||||
|
||||
It("configures a link with addresses and routes", func() {
|
||||
err := originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
err := ConfigureIface(LINK_NAME, result)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
link, err := netlink.LinkByName(LINK_NAME)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(link.Attrs().Name).To(Equal(LINK_NAME))
|
||||
|
||||
v4addrs, err := netlink.AddrList(link, syscall.AF_INET)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(len(v4addrs)).To(Equal(1))
|
||||
Expect(ipNetEqual(v4addrs[0].IPNet, ipv4)).To(Equal(true))
|
||||
|
||||
v6addrs, err := netlink.AddrList(link, syscall.AF_INET6)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(len(v6addrs)).To(Equal(2))
|
||||
|
||||
var found bool
|
||||
for _, a := range v6addrs {
|
||||
if ipNetEqual(a.IPNet, ipv6) {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
Expect(found).To(Equal(true))
|
||||
|
||||
// Ensure the v4 route, v6 route, and subnet route
|
||||
routes, err := netlink.RouteList(link, 0)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
var v4found, v6found bool
|
||||
for _, route := range routes {
|
||||
isv4 := route.Dst.IP.To4() != nil
|
||||
if isv4 && ipNetEqual(route.Dst, routev4) && route.Gw.Equal(routegwv4) {
|
||||
v4found = true
|
||||
}
|
||||
if !isv4 && ipNetEqual(route.Dst, routev6) && route.Gw.Equal(routegwv6) {
|
||||
v6found = true
|
||||
}
|
||||
|
||||
if v4found && v6found {
|
||||
break
|
||||
}
|
||||
}
|
||||
Expect(v4found).To(Equal(true))
|
||||
Expect(v6found).To(Equal(true))
|
||||
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("configures a link with routes using address gateways", func() {
|
||||
result.Routes[0].GW = nil
|
||||
result.Routes[1].GW = nil
|
||||
err := originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
err := ConfigureIface(LINK_NAME, result)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
link, err := netlink.LinkByName(LINK_NAME)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(link.Attrs().Name).To(Equal(LINK_NAME))
|
||||
|
||||
// Ensure the v4 route, v6 route, and subnet route
|
||||
routes, err := netlink.RouteList(link, 0)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
var v4found, v6found bool
|
||||
for _, route := range routes {
|
||||
isv4 := route.Dst.IP.To4() != nil
|
||||
if isv4 && ipNetEqual(route.Dst, routev4) && route.Gw.Equal(ipgw4) {
|
||||
v4found = true
|
||||
}
|
||||
if !isv4 && ipNetEqual(route.Dst, routev6) && route.Gw.Equal(ipgw6) {
|
||||
v6found = true
|
||||
}
|
||||
|
||||
if v4found && v6found {
|
||||
break
|
||||
}
|
||||
}
|
||||
Expect(v4found).To(Equal(true))
|
||||
Expect(v6found).To(Equal(true))
|
||||
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("returns an error when the interface index doesn't match the link name", func() {
|
||||
result.IPs[0].Interface = 1
|
||||
err := originalNS.Do(func(ns.NetNS) error {
|
||||
return ConfigureIface(LINK_NAME, result)
|
||||
})
|
||||
Expect(err).To(HaveOccurred())
|
||||
})
|
||||
|
||||
It("returns an error when the interface index is too big", func() {
|
||||
result.IPs[0].Interface = 2
|
||||
err := originalNS.Do(func(ns.NetNS) error {
|
||||
return ConfigureIface(LINK_NAME, result)
|
||||
})
|
||||
Expect(err).To(HaveOccurred())
|
||||
})
|
||||
|
||||
It("returns an error when there are no interfaces to configure", func() {
|
||||
result.Interfaces = []*current.Interface{}
|
||||
err := originalNS.Do(func(ns.NetNS) error {
|
||||
return ConfigureIface(LINK_NAME, result)
|
||||
})
|
||||
Expect(err).To(HaveOccurred())
|
||||
})
|
||||
|
||||
It("returns an error when configuring the wrong interface", func() {
|
||||
err := originalNS.Do(func(ns.NetNS) error {
|
||||
return ConfigureIface("asdfasdf", result)
|
||||
})
|
||||
Expect(err).To(HaveOccurred())
|
||||
})
|
||||
})
|
@ -1,34 +0,0 @@
|
||||
### Namespaces, Threads, and Go
|
||||
On Linux each OS thread can have a different network namespace. Go's thread scheduling model switches goroutines between OS threads based on OS thread load and whether the goroutine would block other goroutines. This can result in a goroutine switching network namespaces without notice and lead to errors in your code.
|
||||
|
||||
### Namespace Switching
|
||||
Switching namespaces with the `ns.Set()` method is not recommended without additional strategies to prevent unexpected namespace changes when your goroutines switch OS threads.
|
||||
|
||||
Go provides the `runtime.LockOSThread()` function to ensure a specific goroutine executes on its current OS thread and prevents any other goroutine from running in that thread until the locked one exits. Careful usage of `LockOSThread()` and goroutines can provide good control over which network namespace a given goroutine executes in.
|
||||
|
||||
For example, you cannot rely on the `ns.Set()` namespace being the current namespace after the `Set()` call unless you do two things. First, the goroutine calling `Set()` must have previously called `LockOSThread()`. Second, you must ensure `runtime.UnlockOSThread()` is not called somewhere in-between. You also cannot rely on the initial network namespace remaining the current network namespace if any other code in your program switches namespaces, unless you have already called `LockOSThread()` in that goroutine. Note that `LockOSThread()` prevents the Go scheduler from optimally scheduling goroutines for best performance, so `LockOSThread()` should only be used in small, isolated goroutines that release the lock quickly.
|
||||
|
||||
### Do() The Recommended Thing
|
||||
The `ns.Do()` method provides control over network namespaces for you by implementing these strategies. All code dependent on a particular network namespace (including the root namespace) should be wrapped in the `ns.Do()` method to ensure the correct namespace is selected for the duration of your code. For example:
|
||||
|
||||
```go
|
||||
targetNs, err := ns.NewNS()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = targetNs.Do(func(hostNs ns.NetNS) error {
|
||||
dummy := &netlink.Dummy{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: "dummy0",
|
||||
},
|
||||
}
|
||||
return netlink.LinkAdd(dummy)
|
||||
})
|
||||
```
|
||||
|
||||
Note this requirement to wrap every network call is very onerous - any libraries you call might call out to network services such as DNS, and all such calls need to be protected after you call `ns.Do()`. The CNI plugins all exit very soon after calling `ns.Do()` which helps to minimize the problem.
|
||||
|
||||
### Further Reading
|
||||
- https://github.com/golang/go/wiki/LockOSThread
|
||||
- http://morsmachine.dk/go-scheduler
|
||||
- https://github.com/containernetworking/cni/issues/262
|
178
pkg/ns/ns.go
178
pkg/ns/ns.go
@ -1,178 +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 ns
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"sync"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
type NetNS interface {
|
||||
// Executes the passed closure in this object's network namespace,
|
||||
// attempting to restore the original namespace before returning.
|
||||
// However, since each OS thread can have a different network namespace,
|
||||
// and Go's thread scheduling is highly variable, callers cannot
|
||||
// guarantee any specific namespace is set unless operations that
|
||||
// require that namespace are wrapped with Do(). Also, no code called
|
||||
// from Do() should call runtime.UnlockOSThread(), or the risk
|
||||
// of executing code in an incorrect namespace will be greater. See
|
||||
// https://github.com/golang/go/wiki/LockOSThread for further details.
|
||||
Do(toRun func(NetNS) error) error
|
||||
|
||||
// Sets the current network namespace to this object's network namespace.
|
||||
// Note that since Go's thread scheduling is highly variable, callers
|
||||
// cannot guarantee the requested namespace will be the current namespace
|
||||
// after this function is called; to ensure this wrap operations that
|
||||
// require the namespace with Do() instead.
|
||||
Set() error
|
||||
|
||||
// Returns the filesystem path representing this object's network namespace
|
||||
Path() string
|
||||
|
||||
// Returns a file descriptor representing this object's network namespace
|
||||
Fd() uintptr
|
||||
|
||||
// Cleans up this instance of the network namespace; if this instance
|
||||
// is the last user the namespace will be destroyed
|
||||
Close() error
|
||||
}
|
||||
|
||||
type netNS struct {
|
||||
file *os.File
|
||||
mounted bool
|
||||
closed bool
|
||||
}
|
||||
|
||||
// netNS implements the NetNS interface
|
||||
var _ NetNS = &netNS{}
|
||||
|
||||
const (
|
||||
// https://github.com/torvalds/linux/blob/master/include/uapi/linux/magic.h
|
||||
NSFS_MAGIC = 0x6e736673
|
||||
PROCFS_MAGIC = 0x9fa0
|
||||
)
|
||||
|
||||
type NSPathNotExistErr struct{ msg string }
|
||||
|
||||
func (e NSPathNotExistErr) Error() string { return e.msg }
|
||||
|
||||
type NSPathNotNSErr struct{ msg string }
|
||||
|
||||
func (e NSPathNotNSErr) Error() string { return e.msg }
|
||||
|
||||
func IsNSorErr(nspath string) error {
|
||||
stat := syscall.Statfs_t{}
|
||||
if err := syscall.Statfs(nspath, &stat); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
err = NSPathNotExistErr{msg: fmt.Sprintf("failed to Statfs %q: %v", nspath, err)}
|
||||
} else {
|
||||
err = fmt.Errorf("failed to Statfs %q: %v", nspath, err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
switch stat.Type {
|
||||
case PROCFS_MAGIC, NSFS_MAGIC:
|
||||
return nil
|
||||
default:
|
||||
return NSPathNotNSErr{msg: fmt.Sprintf("unknown FS magic on %q: %x", nspath, stat.Type)}
|
||||
}
|
||||
}
|
||||
|
||||
// Returns an object representing the namespace referred to by @path
|
||||
func GetNS(nspath string) (NetNS, error) {
|
||||
err := IsNSorErr(nspath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fd, err := os.Open(nspath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &netNS{file: fd}, nil
|
||||
}
|
||||
|
||||
func (ns *netNS) Path() string {
|
||||
return ns.file.Name()
|
||||
}
|
||||
|
||||
func (ns *netNS) Fd() uintptr {
|
||||
return ns.file.Fd()
|
||||
}
|
||||
|
||||
func (ns *netNS) errorIfClosed() error {
|
||||
if ns.closed {
|
||||
return fmt.Errorf("%q has already been closed", ns.file.Name())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ns *netNS) Do(toRun func(NetNS) error) error {
|
||||
if err := ns.errorIfClosed(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
containedCall := func(hostNS NetNS) error {
|
||||
threadNS, err := GetCurrentNS()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open current netns: %v", err)
|
||||
}
|
||||
defer threadNS.Close()
|
||||
|
||||
// switch to target namespace
|
||||
if err = ns.Set(); err != nil {
|
||||
return fmt.Errorf("error switching to ns %v: %v", ns.file.Name(), err)
|
||||
}
|
||||
defer threadNS.Set() // switch back
|
||||
|
||||
return toRun(hostNS)
|
||||
}
|
||||
|
||||
// save a handle to current network namespace
|
||||
hostNS, err := GetCurrentNS()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to open current namespace: %v", err)
|
||||
}
|
||||
defer hostNS.Close()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
|
||||
var innerError error
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
runtime.LockOSThread()
|
||||
innerError = containedCall(hostNS)
|
||||
}()
|
||||
wg.Wait()
|
||||
|
||||
return innerError
|
||||
}
|
||||
|
||||
// WithNetNSPath executes the passed closure under the given network
|
||||
// namespace, restoring the original namespace afterwards.
|
||||
func WithNetNSPath(nspath string, toRun func(NetNS) error) error {
|
||||
ns, err := GetNS(nspath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer ns.Close()
|
||||
return ns.Do(toRun)
|
||||
}
|
@ -1,149 +0,0 @@
|
||||
// Copyright 2015-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 ns
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"runtime"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// Returns an object representing the current OS thread's network namespace
|
||||
func GetCurrentNS() (NetNS, error) {
|
||||
return GetNS(getCurrentThreadNetNSPath())
|
||||
}
|
||||
|
||||
func getCurrentThreadNetNSPath() string {
|
||||
// /proc/self/ns/net returns the namespace of the main thread, not
|
||||
// of whatever thread this goroutine is running on. Make sure we
|
||||
// use the thread's net namespace since the thread is switching around
|
||||
return fmt.Sprintf("/proc/%d/task/%d/ns/net", os.Getpid(), unix.Gettid())
|
||||
}
|
||||
|
||||
// Creates a new persistent network namespace and returns an object
|
||||
// representing that namespace, without switching to it
|
||||
func NewNS() (NetNS, error) {
|
||||
const nsRunDir = "/var/run/netns"
|
||||
|
||||
b := make([]byte, 16)
|
||||
_, err := rand.Reader.Read(b)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to generate random netns name: %v", err)
|
||||
}
|
||||
|
||||
err = os.MkdirAll(nsRunDir, 0755)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// create an empty file at the mount point
|
||||
nsName := fmt.Sprintf("cni-%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:])
|
||||
nsPath := path.Join(nsRunDir, nsName)
|
||||
mountPointFd, err := os.Create(nsPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mountPointFd.Close()
|
||||
|
||||
// Ensure the mount point is cleaned up on errors; if the namespace
|
||||
// was successfully mounted this will have no effect because the file
|
||||
// is in-use
|
||||
defer os.RemoveAll(nsPath)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
|
||||
// do namespace work in a dedicated goroutine, so that we can safely
|
||||
// Lock/Unlock OSThread without upsetting the lock/unlock state of
|
||||
// the caller of this function
|
||||
var fd *os.File
|
||||
go (func() {
|
||||
defer wg.Done()
|
||||
runtime.LockOSThread()
|
||||
|
||||
var origNS NetNS
|
||||
origNS, err = GetNS(getCurrentThreadNetNSPath())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer origNS.Close()
|
||||
|
||||
// create a new netns on the current thread
|
||||
err = unix.Unshare(unix.CLONE_NEWNET)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer origNS.Set()
|
||||
|
||||
// bind mount the new netns from the current thread onto the mount point
|
||||
err = unix.Mount(getCurrentThreadNetNSPath(), nsPath, "none", unix.MS_BIND, "")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
fd, err = os.Open(nsPath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
})()
|
||||
wg.Wait()
|
||||
|
||||
if err != nil {
|
||||
unix.Unmount(nsPath, unix.MNT_DETACH)
|
||||
return nil, fmt.Errorf("failed to create namespace: %v", err)
|
||||
}
|
||||
|
||||
return &netNS{file: fd, mounted: true}, nil
|
||||
}
|
||||
|
||||
func (ns *netNS) Close() error {
|
||||
if err := ns.errorIfClosed(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := ns.file.Close(); err != nil {
|
||||
return fmt.Errorf("Failed to close %q: %v", ns.file.Name(), err)
|
||||
}
|
||||
ns.closed = true
|
||||
|
||||
if ns.mounted {
|
||||
if err := unix.Unmount(ns.file.Name(), unix.MNT_DETACH); err != nil {
|
||||
return fmt.Errorf("Failed to unmount namespace %s: %v", ns.file.Name(), err)
|
||||
}
|
||||
if err := os.RemoveAll(ns.file.Name()); err != nil {
|
||||
return fmt.Errorf("Failed to clean up namespace %s: %v", ns.file.Name(), err)
|
||||
}
|
||||
ns.mounted = false
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ns *netNS) Set() error {
|
||||
if err := ns.errorIfClosed(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, _, err := unix.Syscall(unix.SYS_SETNS, ns.Fd(), uintptr(unix.CLONE_NEWNET), 0); err != 0 {
|
||||
return fmt.Errorf("Error switching to ns %v: %v", ns.file.Name(), err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -1,34 +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 ns_test
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"runtime"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
"github.com/onsi/ginkgo/config"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNs(t *testing.T) {
|
||||
rand.Seed(config.GinkgoConfig.RandomSeed)
|
||||
runtime.LockOSThread()
|
||||
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "pkg/ns Suite")
|
||||
}
|
@ -1,252 +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 ns_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/ns"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func getInodeCurNetNS() (uint64, error) {
|
||||
curNS, err := ns.GetCurrentNS()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer curNS.Close()
|
||||
return getInodeNS(curNS)
|
||||
}
|
||||
|
||||
func getInodeNS(netns ns.NetNS) (uint64, error) {
|
||||
return getInodeFd(int(netns.Fd()))
|
||||
}
|
||||
|
||||
func getInode(path string) (uint64, error) {
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer file.Close()
|
||||
return getInodeFd(int(file.Fd()))
|
||||
}
|
||||
|
||||
func getInodeFd(fd int) (uint64, error) {
|
||||
stat := &unix.Stat_t{}
|
||||
err := unix.Fstat(fd, stat)
|
||||
return stat.Ino, err
|
||||
}
|
||||
|
||||
var _ = Describe("Linux namespace operations", func() {
|
||||
Describe("WithNetNS", func() {
|
||||
var (
|
||||
originalNetNS ns.NetNS
|
||||
targetNetNS ns.NetNS
|
||||
)
|
||||
|
||||
BeforeEach(func() {
|
||||
var err error
|
||||
|
||||
originalNetNS, err = ns.NewNS()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
targetNetNS, err = ns.NewNS()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
Expect(targetNetNS.Close()).To(Succeed())
|
||||
Expect(originalNetNS.Close()).To(Succeed())
|
||||
})
|
||||
|
||||
It("executes the callback within the target network namespace", func() {
|
||||
expectedInode, err := getInodeNS(targetNetNS)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
err = targetNetNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
actualInode, err := getInodeCurNetNS()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(actualInode).To(Equal(expectedInode))
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("provides the original namespace as the argument to the callback", func() {
|
||||
// Ensure we start in originalNetNS
|
||||
err := originalNetNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
origNSInode, err := getInodeNS(originalNetNS)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
err = targetNetNS.Do(func(hostNS ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
hostNSInode, err := getInodeNS(hostNS)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(hostNSInode).To(Equal(origNSInode))
|
||||
return nil
|
||||
})
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
Context("when the callback returns an error", func() {
|
||||
It("restores the calling thread to the original namespace before returning", func() {
|
||||
err := originalNetNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
preTestInode, err := getInodeCurNetNS()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
_ = targetNetNS.Do(func(ns.NetNS) error {
|
||||
return errors.New("potato")
|
||||
})
|
||||
|
||||
postTestInode, err := getInodeCurNetNS()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(postTestInode).To(Equal(preTestInode))
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("returns the error from the callback", func() {
|
||||
err := targetNetNS.Do(func(ns.NetNS) error {
|
||||
return errors.New("potato")
|
||||
})
|
||||
Expect(err).To(MatchError("potato"))
|
||||
})
|
||||
})
|
||||
|
||||
Describe("validating inode mapping to namespaces", func() {
|
||||
It("checks that different namespaces have different inodes", func() {
|
||||
origNSInode, err := getInodeNS(originalNetNS)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
testNsInode, err := getInodeNS(targetNetNS)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
Expect(testNsInode).NotTo(Equal(0))
|
||||
Expect(testNsInode).NotTo(Equal(origNSInode))
|
||||
})
|
||||
|
||||
It("should not leak a closed netns onto any threads in the process", func() {
|
||||
By("creating a new netns")
|
||||
createdNetNS, err := ns.NewNS()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
By("discovering the inode of the created netns")
|
||||
createdNetNSInode, err := getInodeNS(createdNetNS)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
createdNetNS.Close()
|
||||
|
||||
By("comparing against the netns inode of every thread in the process")
|
||||
for _, netnsPath := range allNetNSInCurrentProcess() {
|
||||
netnsInode, err := getInode(netnsPath)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(netnsInode).NotTo(Equal(createdNetNSInode))
|
||||
}
|
||||
})
|
||||
|
||||
It("fails when the path is not a namespace", func() {
|
||||
tempFile, err := ioutil.TempFile("", "nstest")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
defer tempFile.Close()
|
||||
|
||||
nspath := tempFile.Name()
|
||||
defer os.Remove(nspath)
|
||||
|
||||
_, err = ns.GetNS(nspath)
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err).To(BeAssignableToTypeOf(ns.NSPathNotNSErr{}))
|
||||
Expect(err).NotTo(BeAssignableToTypeOf(ns.NSPathNotExistErr{}))
|
||||
})
|
||||
})
|
||||
|
||||
Describe("closing a network namespace", func() {
|
||||
It("should prevent further operations", func() {
|
||||
createdNetNS, err := ns.NewNS()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
err = createdNetNS.Close()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
err = createdNetNS.Do(func(ns.NetNS) error { return nil })
|
||||
Expect(err).To(HaveOccurred())
|
||||
|
||||
err = createdNetNS.Set()
|
||||
Expect(err).To(HaveOccurred())
|
||||
})
|
||||
|
||||
It("should only work once", func() {
|
||||
createdNetNS, err := ns.NewNS()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
err = createdNetNS.Close()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
err = createdNetNS.Close()
|
||||
Expect(err).To(HaveOccurred())
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Describe("IsNSorErr", func() {
|
||||
It("should detect a namespace", func() {
|
||||
createdNetNS, err := ns.NewNS()
|
||||
err = ns.IsNSorErr(createdNetNS.Path())
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("should refuse other paths", func() {
|
||||
tempFile, err := ioutil.TempFile("", "nstest")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
defer tempFile.Close()
|
||||
|
||||
nspath := tempFile.Name()
|
||||
defer os.Remove(nspath)
|
||||
|
||||
err = ns.IsNSorErr(nspath)
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err).To(BeAssignableToTypeOf(ns.NSPathNotNSErr{}))
|
||||
Expect(err).NotTo(BeAssignableToTypeOf(ns.NSPathNotExistErr{}))
|
||||
})
|
||||
|
||||
It("should error on non-existing paths", func() {
|
||||
err := ns.IsNSorErr("/tmp/IDoNotExist")
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err).To(BeAssignableToTypeOf(ns.NSPathNotExistErr{}))
|
||||
Expect(err).NotTo(BeAssignableToTypeOf(ns.NSPathNotNSErr{}))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
func allNetNSInCurrentProcess() []string {
|
||||
pid := unix.Getpid()
|
||||
paths, err := filepath.Glob(fmt.Sprintf("/proc/%d/task/*/ns/net", pid))
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return paths
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
// Copyright 2015-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.
|
||||
|
||||
// +build !linux
|
||||
|
||||
package ns
|
||||
|
||||
import "github.com/containernetworking/cni/pkg/types"
|
||||
|
||||
// Returns an object representing the current OS thread's network namespace
|
||||
func GetCurrentNS() (NetNS, error) {
|
||||
return nil, types.NotImplementedError
|
||||
}
|
||||
|
||||
func NewNS() (NetNS, error) {
|
||||
return nil, types.NotImplementedError
|
||||
}
|
||||
|
||||
func (ns *netNS) Close() error {
|
||||
return types.NotImplementedError
|
||||
}
|
||||
|
||||
func (ns *netNS) Set() error {
|
||||
return types.NotImplementedError
|
||||
}
|
228
pkg/skel/skel.go
228
pkg/skel/skel.go
@ -1,228 +0,0 @@
|
||||
// Copyright 2014-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 skel provides skeleton code for a CNI plugin.
|
||||
// In particular, it implements argument parsing and validation.
|
||||
package skel
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/containernetworking/cni/pkg/version"
|
||||
)
|
||||
|
||||
// CmdArgs captures all the arguments passed in to the plugin
|
||||
// via both env vars and stdin
|
||||
type CmdArgs struct {
|
||||
ContainerID string
|
||||
Netns string
|
||||
IfName string
|
||||
Args string
|
||||
Path string
|
||||
StdinData []byte
|
||||
}
|
||||
|
||||
type dispatcher struct {
|
||||
Getenv func(string) string
|
||||
Stdin io.Reader
|
||||
Stdout io.Writer
|
||||
Stderr io.Writer
|
||||
|
||||
ConfVersionDecoder version.ConfigDecoder
|
||||
VersionReconciler version.Reconciler
|
||||
}
|
||||
|
||||
type reqForCmdEntry map[string]bool
|
||||
|
||||
func (t *dispatcher) getCmdArgsFromEnv() (string, *CmdArgs, error) {
|
||||
var cmd, contID, netns, ifName, args, path string
|
||||
|
||||
vars := []struct {
|
||||
name string
|
||||
val *string
|
||||
reqForCmd reqForCmdEntry
|
||||
}{
|
||||
{
|
||||
"CNI_COMMAND",
|
||||
&cmd,
|
||||
reqForCmdEntry{
|
||||
"ADD": true,
|
||||
"DEL": true,
|
||||
},
|
||||
},
|
||||
{
|
||||
"CNI_CONTAINERID",
|
||||
&contID,
|
||||
reqForCmdEntry{
|
||||
"ADD": false,
|
||||
"DEL": false,
|
||||
},
|
||||
},
|
||||
{
|
||||
"CNI_NETNS",
|
||||
&netns,
|
||||
reqForCmdEntry{
|
||||
"ADD": true,
|
||||
"DEL": false,
|
||||
},
|
||||
},
|
||||
{
|
||||
"CNI_IFNAME",
|
||||
&ifName,
|
||||
reqForCmdEntry{
|
||||
"ADD": true,
|
||||
"DEL": true,
|
||||
},
|
||||
},
|
||||
{
|
||||
"CNI_ARGS",
|
||||
&args,
|
||||
reqForCmdEntry{
|
||||
"ADD": false,
|
||||
"DEL": false,
|
||||
},
|
||||
},
|
||||
{
|
||||
"CNI_PATH",
|
||||
&path,
|
||||
reqForCmdEntry{
|
||||
"ADD": true,
|
||||
"DEL": true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
argsMissing := false
|
||||
for _, v := range vars {
|
||||
*v.val = t.Getenv(v.name)
|
||||
if *v.val == "" {
|
||||
if v.reqForCmd[cmd] || v.name == "CNI_COMMAND" {
|
||||
fmt.Fprintf(t.Stderr, "%v env variable missing\n", v.name)
|
||||
argsMissing = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if argsMissing {
|
||||
return "", nil, fmt.Errorf("required env variables missing")
|
||||
}
|
||||
|
||||
stdinData, err := ioutil.ReadAll(t.Stdin)
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("error reading from stdin: %v", err)
|
||||
}
|
||||
|
||||
cmdArgs := &CmdArgs{
|
||||
ContainerID: contID,
|
||||
Netns: netns,
|
||||
IfName: ifName,
|
||||
Args: args,
|
||||
Path: path,
|
||||
StdinData: stdinData,
|
||||
}
|
||||
return cmd, cmdArgs, nil
|
||||
}
|
||||
|
||||
func createTypedError(f string, args ...interface{}) *types.Error {
|
||||
return &types.Error{
|
||||
Code: 100,
|
||||
Msg: fmt.Sprintf(f, args...),
|
||||
}
|
||||
}
|
||||
|
||||
func (t *dispatcher) checkVersionAndCall(cmdArgs *CmdArgs, pluginVersionInfo version.PluginInfo, toCall func(*CmdArgs) error) error {
|
||||
configVersion, err := t.ConfVersionDecoder.Decode(cmdArgs.StdinData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
verErr := t.VersionReconciler.Check(configVersion, pluginVersionInfo)
|
||||
if verErr != nil {
|
||||
return &types.Error{
|
||||
Code: types.ErrIncompatibleCNIVersion,
|
||||
Msg: "incompatible CNI versions",
|
||||
Details: verErr.Details(),
|
||||
}
|
||||
}
|
||||
return toCall(cmdArgs)
|
||||
}
|
||||
|
||||
func (t *dispatcher) pluginMain(cmdAdd, cmdDel func(_ *CmdArgs) error, versionInfo version.PluginInfo) *types.Error {
|
||||
cmd, cmdArgs, err := t.getCmdArgsFromEnv()
|
||||
if err != nil {
|
||||
return createTypedError(err.Error())
|
||||
}
|
||||
|
||||
switch cmd {
|
||||
case "ADD":
|
||||
err = t.checkVersionAndCall(cmdArgs, versionInfo, cmdAdd)
|
||||
case "DEL":
|
||||
err = t.checkVersionAndCall(cmdArgs, versionInfo, cmdDel)
|
||||
case "VERSION":
|
||||
err = versionInfo.Encode(t.Stdout)
|
||||
default:
|
||||
return createTypedError("unknown CNI_COMMAND: %v", cmd)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if e, ok := err.(*types.Error); ok {
|
||||
// don't wrap Error in Error
|
||||
return e
|
||||
}
|
||||
return createTypedError(err.Error())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PluginMainWithError is the core "main" for a plugin. It accepts
|
||||
// callback functions for add and del CNI commands and returns an error.
|
||||
//
|
||||
// The caller must also specify what CNI spec versions the plugin supports.
|
||||
//
|
||||
// It is the responsibility of the caller to check for non-nil error return.
|
||||
//
|
||||
// For a plugin to comply with the CNI spec, it must print any error to stdout
|
||||
// as JSON and then exit with nonzero status code.
|
||||
//
|
||||
// To let this package automatically handle errors and call os.Exit(1) for you,
|
||||
// use PluginMain() instead.
|
||||
func PluginMainWithError(cmdAdd, cmdDel func(_ *CmdArgs) error, versionInfo version.PluginInfo) *types.Error {
|
||||
return (&dispatcher{
|
||||
Getenv: os.Getenv,
|
||||
Stdin: os.Stdin,
|
||||
Stdout: os.Stdout,
|
||||
Stderr: os.Stderr,
|
||||
}).pluginMain(cmdAdd, cmdDel, versionInfo)
|
||||
}
|
||||
|
||||
// PluginMain is the core "main" for a plugin which includes automatic error handling.
|
||||
//
|
||||
// The caller must also specify what CNI spec versions the plugin supports.
|
||||
//
|
||||
// When an error occurs in either cmdAdd or cmdDel, PluginMain will print the error
|
||||
// as JSON to stdout and call os.Exit(1).
|
||||
//
|
||||
// To have more control over error handling, use PluginMainWithError() instead.
|
||||
func PluginMain(cmdAdd, cmdDel func(_ *CmdArgs) error, versionInfo version.PluginInfo) {
|
||||
if e := PluginMainWithError(cmdAdd, cmdDel, versionInfo); e != nil {
|
||||
if err := e.Print(); err != nil {
|
||||
log.Print("Error writing error JSON to stdout: ", err)
|
||||
}
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
@ -1,27 +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 skel
|
||||
|
||||
import (
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSkel(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "Skel Suite")
|
||||
}
|
@ -1,346 +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 skel
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/containernetworking/cni/pkg/version"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/testutils"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/ginkgo/extensions/table"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
type fakeCmd struct {
|
||||
CallCount int
|
||||
Returns struct {
|
||||
Error error
|
||||
}
|
||||
Received struct {
|
||||
CmdArgs *CmdArgs
|
||||
}
|
||||
}
|
||||
|
||||
func (c *fakeCmd) Func(args *CmdArgs) error {
|
||||
c.CallCount++
|
||||
c.Received.CmdArgs = args
|
||||
return c.Returns.Error
|
||||
}
|
||||
|
||||
var _ = Describe("dispatching to the correct callback", func() {
|
||||
var (
|
||||
environment map[string]string
|
||||
stdinData string
|
||||
stdout, stderr *bytes.Buffer
|
||||
cmdAdd, cmdDel *fakeCmd
|
||||
dispatch *dispatcher
|
||||
expectedCmdArgs *CmdArgs
|
||||
versionInfo version.PluginInfo
|
||||
)
|
||||
|
||||
BeforeEach(func() {
|
||||
environment = map[string]string{
|
||||
"CNI_COMMAND": "ADD",
|
||||
"CNI_CONTAINERID": "some-container-id",
|
||||
"CNI_NETNS": "/some/netns/path",
|
||||
"CNI_IFNAME": "eth0",
|
||||
"CNI_ARGS": "some;extra;args",
|
||||
"CNI_PATH": "/some/cni/path",
|
||||
}
|
||||
|
||||
stdinData = `{ "some": "config", "cniVersion": "9.8.7" }`
|
||||
stdout = &bytes.Buffer{}
|
||||
stderr = &bytes.Buffer{}
|
||||
versionInfo = version.PluginSupports("9.8.7")
|
||||
dispatch = &dispatcher{
|
||||
Getenv: func(key string) string { return environment[key] },
|
||||
Stdin: strings.NewReader(stdinData),
|
||||
Stdout: stdout,
|
||||
Stderr: stderr,
|
||||
}
|
||||
cmdAdd = &fakeCmd{}
|
||||
cmdDel = &fakeCmd{}
|
||||
expectedCmdArgs = &CmdArgs{
|
||||
ContainerID: "some-container-id",
|
||||
Netns: "/some/netns/path",
|
||||
IfName: "eth0",
|
||||
Args: "some;extra;args",
|
||||
Path: "/some/cni/path",
|
||||
StdinData: []byte(stdinData),
|
||||
}
|
||||
})
|
||||
|
||||
var envVarChecker = func(envVar string, isRequired bool) {
|
||||
delete(environment, envVar)
|
||||
|
||||
err := dispatch.pluginMain(cmdAdd.Func, cmdDel.Func, versionInfo)
|
||||
if isRequired {
|
||||
Expect(err).To(Equal(&types.Error{
|
||||
Code: 100,
|
||||
Msg: "required env variables missing",
|
||||
}))
|
||||
Expect(stderr.String()).To(ContainSubstring(envVar + " env variable missing\n"))
|
||||
} else {
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
}
|
||||
}
|
||||
|
||||
Context("when the CNI_COMMAND is ADD", func() {
|
||||
It("extracts env vars and stdin data and calls cmdAdd", func() {
|
||||
err := dispatch.pluginMain(cmdAdd.Func, cmdDel.Func, versionInfo)
|
||||
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(cmdAdd.CallCount).To(Equal(1))
|
||||
Expect(cmdDel.CallCount).To(Equal(0))
|
||||
Expect(cmdAdd.Received.CmdArgs).To(Equal(expectedCmdArgs))
|
||||
})
|
||||
|
||||
It("does not call cmdDel", func() {
|
||||
err := dispatch.pluginMain(cmdAdd.Func, cmdDel.Func, versionInfo)
|
||||
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(cmdDel.CallCount).To(Equal(0))
|
||||
})
|
||||
|
||||
DescribeTable("required / optional env vars", envVarChecker,
|
||||
Entry("command", "CNI_COMMAND", true),
|
||||
Entry("container id", "CNI_CONTAINERID", false),
|
||||
Entry("net ns", "CNI_NETNS", true),
|
||||
Entry("if name", "CNI_IFNAME", true),
|
||||
Entry("args", "CNI_ARGS", false),
|
||||
Entry("path", "CNI_PATH", true),
|
||||
)
|
||||
|
||||
Context("when multiple required env vars are missing", func() {
|
||||
BeforeEach(func() {
|
||||
delete(environment, "CNI_NETNS")
|
||||
delete(environment, "CNI_IFNAME")
|
||||
delete(environment, "CNI_PATH")
|
||||
})
|
||||
|
||||
It("reports that all of them are missing, not just the first", func() {
|
||||
Expect(dispatch.pluginMain(cmdAdd.Func, cmdDel.Func, versionInfo)).NotTo(Succeed())
|
||||
log := stderr.String()
|
||||
Expect(log).To(ContainSubstring("CNI_NETNS env variable missing\n"))
|
||||
Expect(log).To(ContainSubstring("CNI_IFNAME env variable missing\n"))
|
||||
Expect(log).To(ContainSubstring("CNI_PATH env variable missing\n"))
|
||||
|
||||
})
|
||||
})
|
||||
|
||||
Context("when the stdin data is missing the required cniVersion config", func() {
|
||||
BeforeEach(func() {
|
||||
dispatch.Stdin = strings.NewReader(`{ "some": "config" }`)
|
||||
})
|
||||
|
||||
Context("when the plugin supports version 0.1.0", func() {
|
||||
BeforeEach(func() {
|
||||
versionInfo = version.PluginSupports("0.1.0")
|
||||
expectedCmdArgs.StdinData = []byte(`{ "some": "config" }`)
|
||||
})
|
||||
|
||||
It("infers the config is 0.1.0 and calls the cmdAdd callback", func() {
|
||||
err := dispatch.pluginMain(cmdAdd.Func, cmdDel.Func, versionInfo)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
Expect(cmdAdd.CallCount).To(Equal(1))
|
||||
Expect(cmdAdd.Received.CmdArgs).To(Equal(expectedCmdArgs))
|
||||
})
|
||||
})
|
||||
|
||||
Context("when the plugin does not support 0.1.0", func() {
|
||||
BeforeEach(func() {
|
||||
versionInfo = version.PluginSupports("4.3.2")
|
||||
})
|
||||
|
||||
It("immediately returns a useful error", func() {
|
||||
err := dispatch.pluginMain(cmdAdd.Func, cmdDel.Func, versionInfo)
|
||||
Expect(err.Code).To(Equal(types.ErrIncompatibleCNIVersion)) // see https://github.com/containernetworking/cni/blob/master/SPEC.md#well-known-error-codes
|
||||
Expect(err.Msg).To(Equal("incompatible CNI versions"))
|
||||
Expect(err.Details).To(Equal(`config is "0.1.0", plugin supports ["4.3.2"]`))
|
||||
})
|
||||
|
||||
It("does not call either callback", func() {
|
||||
dispatch.pluginMain(cmdAdd.Func, cmdDel.Func, versionInfo)
|
||||
Expect(cmdAdd.CallCount).To(Equal(0))
|
||||
Expect(cmdDel.CallCount).To(Equal(0))
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Context("when the CNI_COMMAND is DEL", func() {
|
||||
BeforeEach(func() {
|
||||
environment["CNI_COMMAND"] = "DEL"
|
||||
})
|
||||
|
||||
It("calls cmdDel with the env vars and stdin data", func() {
|
||||
err := dispatch.pluginMain(cmdAdd.Func, cmdDel.Func, versionInfo)
|
||||
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(cmdDel.CallCount).To(Equal(1))
|
||||
Expect(cmdDel.Received.CmdArgs).To(Equal(expectedCmdArgs))
|
||||
})
|
||||
|
||||
It("does not call cmdAdd", func() {
|
||||
err := dispatch.pluginMain(cmdAdd.Func, cmdDel.Func, versionInfo)
|
||||
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(cmdAdd.CallCount).To(Equal(0))
|
||||
})
|
||||
|
||||
DescribeTable("required / optional env vars", envVarChecker,
|
||||
Entry("command", "CNI_COMMAND", true),
|
||||
Entry("container id", "CNI_CONTAINERID", false),
|
||||
Entry("net ns", "CNI_NETNS", false),
|
||||
Entry("if name", "CNI_IFNAME", true),
|
||||
Entry("args", "CNI_ARGS", false),
|
||||
Entry("path", "CNI_PATH", true),
|
||||
)
|
||||
})
|
||||
|
||||
Context("when the CNI_COMMAND is VERSION", func() {
|
||||
BeforeEach(func() {
|
||||
environment["CNI_COMMAND"] = "VERSION"
|
||||
})
|
||||
|
||||
It("prints the version to stdout", func() {
|
||||
err := dispatch.pluginMain(cmdAdd.Func, cmdDel.Func, versionInfo)
|
||||
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(stdout).To(MatchJSON(`{
|
||||
"cniVersion": "0.3.1",
|
||||
"supportedVersions": ["9.8.7"]
|
||||
}`))
|
||||
})
|
||||
|
||||
It("does not call cmdAdd or cmdDel", func() {
|
||||
err := dispatch.pluginMain(cmdAdd.Func, cmdDel.Func, versionInfo)
|
||||
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(cmdAdd.CallCount).To(Equal(0))
|
||||
Expect(cmdDel.CallCount).To(Equal(0))
|
||||
})
|
||||
|
||||
DescribeTable("VERSION does not need the usual env vars", envVarChecker,
|
||||
Entry("command", "CNI_COMMAND", true),
|
||||
Entry("container id", "CNI_CONTAINERID", false),
|
||||
Entry("net ns", "CNI_NETNS", false),
|
||||
Entry("if name", "CNI_IFNAME", false),
|
||||
Entry("args", "CNI_ARGS", false),
|
||||
Entry("path", "CNI_PATH", false),
|
||||
)
|
||||
|
||||
Context("when the stdin is empty", func() {
|
||||
BeforeEach(func() {
|
||||
dispatch.Stdin = strings.NewReader("")
|
||||
})
|
||||
|
||||
It("succeeds without error", func() {
|
||||
err := dispatch.pluginMain(cmdAdd.Func, cmdDel.Func, versionInfo)
|
||||
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(stdout).To(MatchJSON(`{
|
||||
"cniVersion": "0.3.1",
|
||||
"supportedVersions": ["9.8.7"]
|
||||
}`))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Context("when the CNI_COMMAND is unrecognized", func() {
|
||||
BeforeEach(func() {
|
||||
environment["CNI_COMMAND"] = "NOPE"
|
||||
})
|
||||
|
||||
It("does not call any cmd callback", func() {
|
||||
dispatch.pluginMain(cmdAdd.Func, cmdDel.Func, versionInfo)
|
||||
|
||||
Expect(cmdAdd.CallCount).To(Equal(0))
|
||||
Expect(cmdDel.CallCount).To(Equal(0))
|
||||
})
|
||||
|
||||
It("returns an error", func() {
|
||||
err := dispatch.pluginMain(cmdAdd.Func, cmdDel.Func, versionInfo)
|
||||
|
||||
Expect(err).To(Equal(&types.Error{
|
||||
Code: 100,
|
||||
Msg: "unknown CNI_COMMAND: NOPE",
|
||||
}))
|
||||
})
|
||||
})
|
||||
|
||||
Context("when stdin cannot be read", func() {
|
||||
BeforeEach(func() {
|
||||
dispatch.Stdin = &testutils.BadReader{}
|
||||
})
|
||||
|
||||
It("does not call any cmd callback", func() {
|
||||
dispatch.pluginMain(cmdAdd.Func, cmdDel.Func, versionInfo)
|
||||
|
||||
Expect(cmdAdd.CallCount).To(Equal(0))
|
||||
Expect(cmdDel.CallCount).To(Equal(0))
|
||||
})
|
||||
|
||||
It("wraps and returns the error", func() {
|
||||
err := dispatch.pluginMain(cmdAdd.Func, cmdDel.Func, versionInfo)
|
||||
|
||||
Expect(err).To(Equal(&types.Error{
|
||||
Code: 100,
|
||||
Msg: "error reading from stdin: banana",
|
||||
}))
|
||||
})
|
||||
})
|
||||
|
||||
Context("when the callback returns an error", func() {
|
||||
Context("when it is a typed Error", func() {
|
||||
BeforeEach(func() {
|
||||
cmdAdd.Returns.Error = &types.Error{
|
||||
Code: 1234,
|
||||
Msg: "insufficient something",
|
||||
}
|
||||
})
|
||||
|
||||
It("returns the error as-is", func() {
|
||||
err := dispatch.pluginMain(cmdAdd.Func, cmdDel.Func, versionInfo)
|
||||
|
||||
Expect(err).To(Equal(&types.Error{
|
||||
Code: 1234,
|
||||
Msg: "insufficient something",
|
||||
}))
|
||||
})
|
||||
})
|
||||
|
||||
Context("when it is an unknown error", func() {
|
||||
BeforeEach(func() {
|
||||
cmdAdd.Returns.Error = errors.New("potato")
|
||||
})
|
||||
|
||||
It("wraps and returns the error", func() {
|
||||
err := dispatch.pluginMain(cmdAdd.Func, cmdDel.Func, versionInfo)
|
||||
|
||||
Expect(err).To(Equal(&types.Error{
|
||||
Code: 100,
|
||||
Msg: "potato",
|
||||
}))
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
@ -1,33 +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 testutils
|
||||
|
||||
import "errors"
|
||||
|
||||
// BadReader is an io.Reader which always errors
|
||||
type BadReader struct {
|
||||
Error error
|
||||
}
|
||||
|
||||
func (r *BadReader) Read(buffer []byte) (int, error) {
|
||||
if r.Error != nil {
|
||||
return 0, r.Error
|
||||
}
|
||||
return 0, errors.New("banana")
|
||||
}
|
||||
|
||||
func (r *BadReader) Close() error {
|
||||
return nil
|
||||
}
|
@ -1,85 +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 testutils
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/containernetworking/cni/pkg/version"
|
||||
)
|
||||
|
||||
func envCleanup() {
|
||||
os.Unsetenv("CNI_COMMAND")
|
||||
os.Unsetenv("CNI_PATH")
|
||||
os.Unsetenv("CNI_NETNS")
|
||||
os.Unsetenv("CNI_IFNAME")
|
||||
}
|
||||
|
||||
func CmdAddWithResult(cniNetns, cniIfname string, conf []byte, f func() error) (types.Result, []byte, error) {
|
||||
os.Setenv("CNI_COMMAND", "ADD")
|
||||
os.Setenv("CNI_PATH", os.Getenv("PATH"))
|
||||
os.Setenv("CNI_NETNS", cniNetns)
|
||||
os.Setenv("CNI_IFNAME", cniIfname)
|
||||
defer envCleanup()
|
||||
|
||||
// Redirect stdout to capture plugin result
|
||||
oldStdout := os.Stdout
|
||||
r, w, err := os.Pipe()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
os.Stdout = w
|
||||
err = f()
|
||||
w.Close()
|
||||
|
||||
var out []byte
|
||||
if err == nil {
|
||||
out, err = ioutil.ReadAll(r)
|
||||
}
|
||||
os.Stdout = oldStdout
|
||||
|
||||
// Return errors after restoring stdout so Ginkgo will correctly
|
||||
// emit verbose error information on stdout
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Plugin must return result in same version as specified in netconf
|
||||
versionDecoder := &version.ConfigDecoder{}
|
||||
confVersion, err := versionDecoder.Decode(conf)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
result, err := version.NewResult(confVersion, out)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return result, out, nil
|
||||
}
|
||||
|
||||
func CmdDelWithResult(cniNetns, cniIfname string, f func() error) error {
|
||||
os.Setenv("CNI_COMMAND", "DEL")
|
||||
os.Setenv("CNI_PATH", os.Getenv("PATH"))
|
||||
os.Setenv("CNI_NETNS", cniNetns)
|
||||
os.Setenv("CNI_IFNAME", cniIfname)
|
||||
defer envCleanup()
|
||||
|
||||
return f()
|
||||
}
|
@ -1,135 +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 types020
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
)
|
||||
|
||||
const ImplementedSpecVersion string = "0.2.0"
|
||||
|
||||
var SupportedVersions = []string{"", "0.1.0", ImplementedSpecVersion}
|
||||
|
||||
// Compatibility types for CNI version 0.1.0 and 0.2.0
|
||||
|
||||
func NewResult(data []byte) (types.Result, error) {
|
||||
result := &Result{}
|
||||
if err := json.Unmarshal(data, result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func GetResult(r types.Result) (*Result, error) {
|
||||
// We expect version 0.1.0/0.2.0 results
|
||||
result020, err := r.GetAsVersion(ImplementedSpecVersion)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result, ok := result020.(*Result)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("failed to convert result")
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Result is what gets returned from the plugin (via stdout) to the caller
|
||||
type Result struct {
|
||||
CNIVersion string `json:"cniVersion,omitempty"`
|
||||
IP4 *IPConfig `json:"ip4,omitempty"`
|
||||
IP6 *IPConfig `json:"ip6,omitempty"`
|
||||
DNS types.DNS `json:"dns,omitempty"`
|
||||
}
|
||||
|
||||
func (r *Result) Version() string {
|
||||
return ImplementedSpecVersion
|
||||
}
|
||||
|
||||
func (r *Result) GetAsVersion(version string) (types.Result, error) {
|
||||
for _, supportedVersion := range SupportedVersions {
|
||||
if version == supportedVersion {
|
||||
r.CNIVersion = version
|
||||
return r, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("cannot convert version %q to %s", SupportedVersions, version)
|
||||
}
|
||||
|
||||
func (r *Result) Print() error {
|
||||
data, err := json.MarshalIndent(r, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = os.Stdout.Write(data)
|
||||
return err
|
||||
}
|
||||
|
||||
// String returns a formatted string in the form of "[IP4: $1,][ IP6: $2,] DNS: $3" where
|
||||
// $1 represents the receiver's IPv4, $2 represents the receiver's IPv6 and $3 the
|
||||
// receiver's DNS. If $1 or $2 are nil, they won't be present in the returned string.
|
||||
func (r *Result) String() string {
|
||||
var str string
|
||||
if r.IP4 != nil {
|
||||
str = fmt.Sprintf("IP4:%+v, ", *r.IP4)
|
||||
}
|
||||
if r.IP6 != nil {
|
||||
str += fmt.Sprintf("IP6:%+v, ", *r.IP6)
|
||||
}
|
||||
return fmt.Sprintf("%sDNS:%+v", str, r.DNS)
|
||||
}
|
||||
|
||||
// IPConfig contains values necessary to configure an interface
|
||||
type IPConfig struct {
|
||||
IP net.IPNet
|
||||
Gateway net.IP
|
||||
Routes []types.Route
|
||||
}
|
||||
|
||||
// net.IPNet is not JSON (un)marshallable so this duality is needed
|
||||
// for our custom IPNet type
|
||||
|
||||
// JSON (un)marshallable types
|
||||
type ipConfig struct {
|
||||
IP types.IPNet `json:"ip"`
|
||||
Gateway net.IP `json:"gateway,omitempty"`
|
||||
Routes []types.Route `json:"routes,omitempty"`
|
||||
}
|
||||
|
||||
func (c *IPConfig) MarshalJSON() ([]byte, error) {
|
||||
ipc := ipConfig{
|
||||
IP: types.IPNet(c.IP),
|
||||
Gateway: c.Gateway,
|
||||
Routes: c.Routes,
|
||||
}
|
||||
|
||||
return json.Marshal(ipc)
|
||||
}
|
||||
|
||||
func (c *IPConfig) UnmarshalJSON(data []byte) error {
|
||||
ipc := ipConfig{}
|
||||
if err := json.Unmarshal(data, &ipc); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.IP = net.IPNet(ipc.IP)
|
||||
c.Gateway = ipc.Gateway
|
||||
c.Routes = ipc.Routes
|
||||
return nil
|
||||
}
|
@ -1,27 +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 types020_test
|
||||
|
||||
import (
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestTypes010(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "0.1.0/0.2.0 Types Suite")
|
||||
}
|
@ -1,130 +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 types020_test
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/containernetworking/cni/pkg/types/020"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("Ensures compatibility with the 0.1.0/0.2.0 spec", func() {
|
||||
It("correctly encodes a 0.1.0/0.2.0 Result", func() {
|
||||
ipv4, err := types.ParseCIDR("1.2.3.30/24")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(ipv4).NotTo(BeNil())
|
||||
|
||||
routegwv4, routev4, err := net.ParseCIDR("15.5.6.8/24")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(routev4).NotTo(BeNil())
|
||||
Expect(routegwv4).NotTo(BeNil())
|
||||
|
||||
ipv6, err := types.ParseCIDR("abcd:1234:ffff::cdde/64")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(ipv6).NotTo(BeNil())
|
||||
|
||||
routegwv6, routev6, err := net.ParseCIDR("1111:dddd::aaaa/80")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(routev6).NotTo(BeNil())
|
||||
Expect(routegwv6).NotTo(BeNil())
|
||||
|
||||
// Set every field of the struct to ensure source compatibility
|
||||
res := types020.Result{
|
||||
CNIVersion: types020.ImplementedSpecVersion,
|
||||
IP4: &types020.IPConfig{
|
||||
IP: *ipv4,
|
||||
Gateway: net.ParseIP("1.2.3.1"),
|
||||
Routes: []types.Route{
|
||||
{Dst: *routev4, GW: routegwv4},
|
||||
},
|
||||
},
|
||||
IP6: &types020.IPConfig{
|
||||
IP: *ipv6,
|
||||
Gateway: net.ParseIP("abcd:1234:ffff::1"),
|
||||
Routes: []types.Route{
|
||||
{Dst: *routev6, GW: routegwv6},
|
||||
},
|
||||
},
|
||||
DNS: types.DNS{
|
||||
Nameservers: []string{"1.2.3.4", "1::cafe"},
|
||||
Domain: "acompany.com",
|
||||
Search: []string{"somedomain.com", "otherdomain.net"},
|
||||
Options: []string{"foo", "bar"},
|
||||
},
|
||||
}
|
||||
|
||||
Expect(res.String()).To(Equal("IP4:{IP:{IP:1.2.3.30 Mask:ffffff00} Gateway:1.2.3.1 Routes:[{Dst:{IP:15.5.6.0 Mask:ffffff00} GW:15.5.6.8}]}, IP6:{IP:{IP:abcd:1234:ffff::cdde Mask:ffffffffffffffff0000000000000000} Gateway:abcd:1234:ffff::1 Routes:[{Dst:{IP:1111:dddd:: Mask:ffffffffffffffffffff000000000000} GW:1111:dddd::aaaa}]}, DNS:{Nameservers:[1.2.3.4 1::cafe] Domain:acompany.com Search:[somedomain.com otherdomain.net] Options:[foo bar]}"))
|
||||
|
||||
// Redirect stdout to capture JSON result
|
||||
oldStdout := os.Stdout
|
||||
r, w, err := os.Pipe()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
os.Stdout = w
|
||||
err = res.Print()
|
||||
w.Close()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// parse the result
|
||||
out, err := ioutil.ReadAll(r)
|
||||
os.Stdout = oldStdout
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
Expect(string(out)).To(Equal(`{
|
||||
"cniVersion": "0.2.0",
|
||||
"ip4": {
|
||||
"ip": "1.2.3.30/24",
|
||||
"gateway": "1.2.3.1",
|
||||
"routes": [
|
||||
{
|
||||
"dst": "15.5.6.0/24",
|
||||
"gw": "15.5.6.8"
|
||||
}
|
||||
]
|
||||
},
|
||||
"ip6": {
|
||||
"ip": "abcd:1234:ffff::cdde/64",
|
||||
"gateway": "abcd:1234:ffff::1",
|
||||
"routes": [
|
||||
{
|
||||
"dst": "1111:dddd::/80",
|
||||
"gw": "1111:dddd::aaaa"
|
||||
}
|
||||
]
|
||||
},
|
||||
"dns": {
|
||||
"nameservers": [
|
||||
"1.2.3.4",
|
||||
"1::cafe"
|
||||
],
|
||||
"domain": "acompany.com",
|
||||
"search": [
|
||||
"somedomain.com",
|
||||
"otherdomain.net"
|
||||
],
|
||||
"options": [
|
||||
"foo",
|
||||
"bar"
|
||||
]
|
||||
}
|
||||
}`))
|
||||
})
|
||||
})
|
@ -1,101 +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 types
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// UnmarshallableBool typedef for builtin bool
|
||||
// because builtin type's methods can't be declared
|
||||
type UnmarshallableBool bool
|
||||
|
||||
// UnmarshalText implements the encoding.TextUnmarshaler interface.
|
||||
// Returns boolean true if the string is "1" or "[Tt]rue"
|
||||
// Returns boolean false if the string is "0" or "[Ff]alse"
|
||||
func (b *UnmarshallableBool) UnmarshalText(data []byte) error {
|
||||
s := strings.ToLower(string(data))
|
||||
switch s {
|
||||
case "1", "true":
|
||||
*b = true
|
||||
case "0", "false":
|
||||
*b = false
|
||||
default:
|
||||
return fmt.Errorf("Boolean unmarshal error: invalid input %s", s)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshallableString typedef for builtin string
|
||||
type UnmarshallableString string
|
||||
|
||||
// UnmarshalText implements the encoding.TextUnmarshaler interface.
|
||||
// Returns the string
|
||||
func (s *UnmarshallableString) UnmarshalText(data []byte) error {
|
||||
*s = UnmarshallableString(data)
|
||||
return nil
|
||||
}
|
||||
|
||||
// CommonArgs contains the IgnoreUnknown argument
|
||||
// and must be embedded by all Arg structs
|
||||
type CommonArgs struct {
|
||||
IgnoreUnknown UnmarshallableBool `json:"ignoreunknown,omitempty"`
|
||||
}
|
||||
|
||||
// GetKeyField is a helper function to receive Values
|
||||
// Values that represent a pointer to a struct
|
||||
func GetKeyField(keyString string, v reflect.Value) reflect.Value {
|
||||
return v.Elem().FieldByName(keyString)
|
||||
}
|
||||
|
||||
// LoadArgs parses args from a string in the form "K=V;K2=V2;..."
|
||||
func LoadArgs(args string, container interface{}) error {
|
||||
if args == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
containerValue := reflect.ValueOf(container)
|
||||
|
||||
pairs := strings.Split(args, ";")
|
||||
unknownArgs := []string{}
|
||||
for _, pair := range pairs {
|
||||
kv := strings.Split(pair, "=")
|
||||
if len(kv) != 2 {
|
||||
return fmt.Errorf("ARGS: invalid pair %q", pair)
|
||||
}
|
||||
keyString := kv[0]
|
||||
valueString := kv[1]
|
||||
keyField := GetKeyField(keyString, containerValue)
|
||||
if !keyField.IsValid() {
|
||||
unknownArgs = append(unknownArgs, pair)
|
||||
continue
|
||||
}
|
||||
|
||||
u := keyField.Addr().Interface().(encoding.TextUnmarshaler)
|
||||
err := u.UnmarshalText([]byte(valueString))
|
||||
if err != nil {
|
||||
return fmt.Errorf("ARGS: error parsing value of pair %q: %v)", pair, err)
|
||||
}
|
||||
}
|
||||
|
||||
isIgnoreUnknown := GetKeyField("IgnoreUnknown", containerValue).Bool()
|
||||
if len(unknownArgs) > 0 && !isIgnoreUnknown {
|
||||
return fmt.Errorf("ARGS: unknown args %q", unknownArgs)
|
||||
}
|
||||
return nil
|
||||
}
|
@ -1,121 +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 types_test
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
. "github.com/containernetworking/cni/pkg/types"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/ginkgo/extensions/table"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("UnmarshallableBool UnmarshalText", func() {
|
||||
DescribeTable("string to bool detection should succeed in all cases",
|
||||
func(inputs []string, expected bool) {
|
||||
for _, s := range inputs {
|
||||
var ub UnmarshallableBool
|
||||
err := ub.UnmarshalText([]byte(s))
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(ub).To(Equal(UnmarshallableBool(expected)))
|
||||
}
|
||||
},
|
||||
Entry("parse to true", []string{"True", "true", "1"}, true),
|
||||
Entry("parse to false", []string{"False", "false", "0"}, false),
|
||||
)
|
||||
|
||||
Context("When passed an invalid value", func() {
|
||||
It("should result in an error", func() {
|
||||
var ub UnmarshallableBool
|
||||
err := ub.UnmarshalText([]byte("invalid"))
|
||||
Expect(err).To(HaveOccurred())
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
var _ = Describe("UnmarshallableString UnmarshalText", func() {
|
||||
DescribeTable("string to string detection should succeed in all cases",
|
||||
func(inputs []string, expected string) {
|
||||
for _, s := range inputs {
|
||||
var us UnmarshallableString
|
||||
err := us.UnmarshalText([]byte(s))
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(string(us)).To(Equal(expected))
|
||||
}
|
||||
},
|
||||
Entry("parse empty string", []string{""}, ""),
|
||||
Entry("parse non-empty string", []string{"notempty"}, "notempty"),
|
||||
)
|
||||
})
|
||||
|
||||
var _ = Describe("GetKeyField", func() {
|
||||
type testcontainer struct {
|
||||
Valid string `json:"valid,omitempty"`
|
||||
}
|
||||
var (
|
||||
container = testcontainer{Valid: "valid"}
|
||||
containerInterface = func(i interface{}) interface{} { return i }(&container)
|
||||
containerValue = reflect.ValueOf(containerInterface)
|
||||
)
|
||||
Context("When a valid field is provided", func() {
|
||||
It("should return the correct field", func() {
|
||||
field := GetKeyField("Valid", containerValue)
|
||||
Expect(field.String()).To(Equal("valid"))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
var _ = Describe("LoadArgs", func() {
|
||||
Context("When no arguments are passed", func() {
|
||||
It("LoadArgs should succeed", func() {
|
||||
err := LoadArgs("", struct{}{})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
})
|
||||
|
||||
Context("When unknown arguments are passed and ignored", func() {
|
||||
It("LoadArgs should succeed", func() {
|
||||
ca := CommonArgs{}
|
||||
err := LoadArgs("IgnoreUnknown=True;Unk=nown", &ca)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
})
|
||||
|
||||
Context("When unknown arguments are passed and not ignored", func() {
|
||||
It("LoadArgs should fail", func() {
|
||||
ca := CommonArgs{}
|
||||
err := LoadArgs("Unk=nown", &ca)
|
||||
Expect(err).To(HaveOccurred())
|
||||
})
|
||||
})
|
||||
|
||||
Context("When unknown arguments are passed and explicitly not ignored", func() {
|
||||
It("LoadArgs should fail", func() {
|
||||
ca := CommonArgs{}
|
||||
err := LoadArgs("IgnoreUnknown=0, Unk=nown", &ca)
|
||||
Expect(err).To(HaveOccurred())
|
||||
})
|
||||
})
|
||||
|
||||
Context("When known arguments are passed", func() {
|
||||
It("LoadArgs should succeed", func() {
|
||||
ca := CommonArgs{}
|
||||
err := LoadArgs("IgnoreUnknown=1", &ca)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
})
|
||||
})
|
@ -1,296 +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 current
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/containernetworking/cni/pkg/types/020"
|
||||
)
|
||||
|
||||
const ImplementedSpecVersion string = "0.3.1"
|
||||
|
||||
var SupportedVersions = []string{"0.3.0", ImplementedSpecVersion}
|
||||
|
||||
func NewResult(data []byte) (types.Result, error) {
|
||||
result := &Result{}
|
||||
if err := json.Unmarshal(data, result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func GetResult(r types.Result) (*Result, error) {
|
||||
resultCurrent, err := r.GetAsVersion(ImplementedSpecVersion)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result, ok := resultCurrent.(*Result)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("failed to convert result")
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
var resultConverters = []struct {
|
||||
versions []string
|
||||
convert func(types.Result) (*Result, error)
|
||||
}{
|
||||
{types020.SupportedVersions, convertFrom020},
|
||||
{SupportedVersions, convertFrom030},
|
||||
}
|
||||
|
||||
func convertFrom020(result types.Result) (*Result, error) {
|
||||
oldResult, err := types020.GetResult(result)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
newResult := &Result{
|
||||
CNIVersion: ImplementedSpecVersion,
|
||||
DNS: oldResult.DNS,
|
||||
Routes: []*types.Route{},
|
||||
}
|
||||
|
||||
if oldResult.IP4 != nil {
|
||||
newResult.IPs = append(newResult.IPs, &IPConfig{
|
||||
Version: "4",
|
||||
Interface: -1,
|
||||
Address: oldResult.IP4.IP,
|
||||
Gateway: oldResult.IP4.Gateway,
|
||||
})
|
||||
for _, route := range oldResult.IP4.Routes {
|
||||
gw := route.GW
|
||||
if gw == nil {
|
||||
gw = oldResult.IP4.Gateway
|
||||
}
|
||||
newResult.Routes = append(newResult.Routes, &types.Route{
|
||||
Dst: route.Dst,
|
||||
GW: gw,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if oldResult.IP6 != nil {
|
||||
newResult.IPs = append(newResult.IPs, &IPConfig{
|
||||
Version: "6",
|
||||
Interface: -1,
|
||||
Address: oldResult.IP6.IP,
|
||||
Gateway: oldResult.IP6.Gateway,
|
||||
})
|
||||
for _, route := range oldResult.IP6.Routes {
|
||||
gw := route.GW
|
||||
if gw == nil {
|
||||
gw = oldResult.IP6.Gateway
|
||||
}
|
||||
newResult.Routes = append(newResult.Routes, &types.Route{
|
||||
Dst: route.Dst,
|
||||
GW: gw,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if len(newResult.IPs) == 0 {
|
||||
return nil, fmt.Errorf("cannot convert: no valid IP addresses")
|
||||
}
|
||||
|
||||
return newResult, nil
|
||||
}
|
||||
|
||||
func convertFrom030(result types.Result) (*Result, error) {
|
||||
newResult, ok := result.(*Result)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("failed to convert result")
|
||||
}
|
||||
newResult.CNIVersion = ImplementedSpecVersion
|
||||
return newResult, nil
|
||||
}
|
||||
|
||||
func NewResultFromResult(result types.Result) (*Result, error) {
|
||||
version := result.Version()
|
||||
for _, converter := range resultConverters {
|
||||
for _, supportedVersion := range converter.versions {
|
||||
if version == supportedVersion {
|
||||
return converter.convert(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("unsupported CNI result22 version %q", version)
|
||||
}
|
||||
|
||||
// Result is what gets returned from the plugin (via stdout) to the caller
|
||||
type Result struct {
|
||||
CNIVersion string `json:"cniVersion,omitempty"`
|
||||
Interfaces []*Interface `json:"interfaces,omitempty"`
|
||||
IPs []*IPConfig `json:"ips,omitempty"`
|
||||
Routes []*types.Route `json:"routes,omitempty"`
|
||||
DNS types.DNS `json:"dns,omitempty"`
|
||||
}
|
||||
|
||||
// Convert to the older 0.2.0 CNI spec Result type
|
||||
func (r *Result) convertTo020() (*types020.Result, error) {
|
||||
oldResult := &types020.Result{
|
||||
CNIVersion: types020.ImplementedSpecVersion,
|
||||
DNS: r.DNS,
|
||||
}
|
||||
|
||||
for _, ip := range r.IPs {
|
||||
// Only convert the first IP address of each version as 0.2.0
|
||||
// and earlier cannot handle multiple IP addresses
|
||||
if ip.Version == "4" && oldResult.IP4 == nil {
|
||||
oldResult.IP4 = &types020.IPConfig{
|
||||
IP: ip.Address,
|
||||
Gateway: ip.Gateway,
|
||||
}
|
||||
} else if ip.Version == "6" && oldResult.IP6 == nil {
|
||||
oldResult.IP6 = &types020.IPConfig{
|
||||
IP: ip.Address,
|
||||
Gateway: ip.Gateway,
|
||||
}
|
||||
}
|
||||
|
||||
if oldResult.IP4 != nil && oldResult.IP6 != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
for _, route := range r.Routes {
|
||||
is4 := route.Dst.IP.To4() != nil
|
||||
if is4 && oldResult.IP4 != nil {
|
||||
oldResult.IP4.Routes = append(oldResult.IP4.Routes, types.Route{
|
||||
Dst: route.Dst,
|
||||
GW: route.GW,
|
||||
})
|
||||
} else if !is4 && oldResult.IP6 != nil {
|
||||
oldResult.IP6.Routes = append(oldResult.IP6.Routes, types.Route{
|
||||
Dst: route.Dst,
|
||||
GW: route.GW,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if oldResult.IP4 == nil && oldResult.IP6 == nil {
|
||||
return nil, fmt.Errorf("cannot convert: no valid IP addresses")
|
||||
}
|
||||
|
||||
return oldResult, nil
|
||||
}
|
||||
|
||||
func (r *Result) Version() string {
|
||||
return ImplementedSpecVersion
|
||||
}
|
||||
|
||||
func (r *Result) GetAsVersion(version string) (types.Result, error) {
|
||||
switch version {
|
||||
case "0.3.0", ImplementedSpecVersion:
|
||||
r.CNIVersion = version
|
||||
return r, nil
|
||||
case types020.SupportedVersions[0], types020.SupportedVersions[1], types020.SupportedVersions[2]:
|
||||
return r.convertTo020()
|
||||
}
|
||||
return nil, fmt.Errorf("cannot convert version 0.3.x to %q", version)
|
||||
}
|
||||
|
||||
func (r *Result) Print() error {
|
||||
data, err := json.MarshalIndent(r, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = os.Stdout.Write(data)
|
||||
return err
|
||||
}
|
||||
|
||||
// String returns a formatted string in the form of "[Interfaces: $1,][ IP: $2,] DNS: $3" where
|
||||
// $1 represents the receiver's Interfaces, $2 represents the receiver's IP addresses and $3 the
|
||||
// receiver's DNS. If $1 or $2 are nil, they won't be present in the returned string.
|
||||
func (r *Result) String() string {
|
||||
var str string
|
||||
if len(r.Interfaces) > 0 {
|
||||
str += fmt.Sprintf("Interfaces:%+v, ", r.Interfaces)
|
||||
}
|
||||
if len(r.IPs) > 0 {
|
||||
str += fmt.Sprintf("IP:%+v, ", r.IPs)
|
||||
}
|
||||
if len(r.Routes) > 0 {
|
||||
str += fmt.Sprintf("Routes:%+v, ", r.Routes)
|
||||
}
|
||||
return fmt.Sprintf("%sDNS:%+v", str, r.DNS)
|
||||
}
|
||||
|
||||
// Convert this old version result to the current CNI version result
|
||||
func (r *Result) Convert() (*Result, error) {
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// Interface contains values about the created interfaces
|
||||
type Interface struct {
|
||||
Name string `json:"name"`
|
||||
Mac string `json:"mac,omitempty"`
|
||||
Sandbox string `json:"sandbox,omitempty"`
|
||||
}
|
||||
|
||||
func (i *Interface) String() string {
|
||||
return fmt.Sprintf("%+v", *i)
|
||||
}
|
||||
|
||||
// IPConfig contains values necessary to configure an IP address on an interface
|
||||
type IPConfig struct {
|
||||
// IP version, either "4" or "6"
|
||||
Version string
|
||||
// Index into Result structs Interfaces list
|
||||
Interface int
|
||||
Address net.IPNet
|
||||
Gateway net.IP
|
||||
}
|
||||
|
||||
func (i *IPConfig) String() string {
|
||||
return fmt.Sprintf("%+v", *i)
|
||||
}
|
||||
|
||||
// JSON (un)marshallable types
|
||||
type ipConfig struct {
|
||||
Version string `json:"version"`
|
||||
Interface int `json:"interface,omitempty"`
|
||||
Address types.IPNet `json:"address"`
|
||||
Gateway net.IP `json:"gateway,omitempty"`
|
||||
}
|
||||
|
||||
func (c *IPConfig) MarshalJSON() ([]byte, error) {
|
||||
ipc := ipConfig{
|
||||
Version: c.Version,
|
||||
Interface: c.Interface,
|
||||
Address: types.IPNet(c.Address),
|
||||
Gateway: c.Gateway,
|
||||
}
|
||||
|
||||
return json.Marshal(ipc)
|
||||
}
|
||||
|
||||
func (c *IPConfig) UnmarshalJSON(data []byte) error {
|
||||
ipc := ipConfig{}
|
||||
if err := json.Unmarshal(data, &ipc); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.Version = ipc.Version
|
||||
c.Interface = ipc.Interface
|
||||
c.Address = net.IPNet(ipc.Address)
|
||||
c.Gateway = ipc.Gateway
|
||||
return nil
|
||||
}
|
@ -1,27 +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 current_test
|
||||
|
||||
import (
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestTypesCurrent(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "Current Types Suite")
|
||||
}
|
@ -1,215 +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 current_test
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/containernetworking/cni/pkg/types/current"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
func testResult() *current.Result {
|
||||
ipv4, err := types.ParseCIDR("1.2.3.30/24")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(ipv4).NotTo(BeNil())
|
||||
|
||||
routegwv4, routev4, err := net.ParseCIDR("15.5.6.8/24")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(routev4).NotTo(BeNil())
|
||||
Expect(routegwv4).NotTo(BeNil())
|
||||
|
||||
ipv6, err := types.ParseCIDR("abcd:1234:ffff::cdde/64")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(ipv6).NotTo(BeNil())
|
||||
|
||||
routegwv6, routev6, err := net.ParseCIDR("1111:dddd::aaaa/80")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(routev6).NotTo(BeNil())
|
||||
Expect(routegwv6).NotTo(BeNil())
|
||||
|
||||
// Set every field of the struct to ensure source compatibility
|
||||
return ¤t.Result{
|
||||
CNIVersion: "0.3.1",
|
||||
Interfaces: []*current.Interface{
|
||||
{
|
||||
Name: "eth0",
|
||||
Mac: "00:11:22:33:44:55",
|
||||
Sandbox: "/proc/3553/ns/net",
|
||||
},
|
||||
},
|
||||
IPs: []*current.IPConfig{
|
||||
{
|
||||
Version: "4",
|
||||
Interface: 0,
|
||||
Address: *ipv4,
|
||||
Gateway: net.ParseIP("1.2.3.1"),
|
||||
},
|
||||
{
|
||||
Version: "6",
|
||||
Interface: 0,
|
||||
Address: *ipv6,
|
||||
Gateway: net.ParseIP("abcd:1234:ffff::1"),
|
||||
},
|
||||
},
|
||||
Routes: []*types.Route{
|
||||
{Dst: *routev4, GW: routegwv4},
|
||||
{Dst: *routev6, GW: routegwv6},
|
||||
},
|
||||
DNS: types.DNS{
|
||||
Nameservers: []string{"1.2.3.4", "1::cafe"},
|
||||
Domain: "acompany.com",
|
||||
Search: []string{"somedomain.com", "otherdomain.net"},
|
||||
Options: []string{"foo", "bar"},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
var _ = Describe("Current types operations", func() {
|
||||
It("correctly encodes a 0.3.x Result", func() {
|
||||
res := testResult()
|
||||
|
||||
Expect(res.String()).To(Equal("Interfaces:[{Name:eth0 Mac:00:11:22:33:44:55 Sandbox:/proc/3553/ns/net}], IP:[{Version:4 Interface:0 Address:{IP:1.2.3.30 Mask:ffffff00} Gateway:1.2.3.1} {Version:6 Interface:0 Address:{IP:abcd:1234:ffff::cdde Mask:ffffffffffffffff0000000000000000} Gateway:abcd:1234:ffff::1}], Routes:[{Dst:{IP:15.5.6.0 Mask:ffffff00} GW:15.5.6.8} {Dst:{IP:1111:dddd:: Mask:ffffffffffffffffffff000000000000} GW:1111:dddd::aaaa}], DNS:{Nameservers:[1.2.3.4 1::cafe] Domain:acompany.com Search:[somedomain.com otherdomain.net] Options:[foo bar]}"))
|
||||
|
||||
// Redirect stdout to capture JSON result
|
||||
oldStdout := os.Stdout
|
||||
r, w, err := os.Pipe()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
os.Stdout = w
|
||||
err = res.Print()
|
||||
w.Close()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// parse the result
|
||||
out, err := ioutil.ReadAll(r)
|
||||
os.Stdout = oldStdout
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
Expect(string(out)).To(Equal(`{
|
||||
"cniVersion": "0.3.1",
|
||||
"interfaces": [
|
||||
{
|
||||
"name": "eth0",
|
||||
"mac": "00:11:22:33:44:55",
|
||||
"sandbox": "/proc/3553/ns/net"
|
||||
}
|
||||
],
|
||||
"ips": [
|
||||
{
|
||||
"version": "4",
|
||||
"address": "1.2.3.30/24",
|
||||
"gateway": "1.2.3.1"
|
||||
},
|
||||
{
|
||||
"version": "6",
|
||||
"address": "abcd:1234:ffff::cdde/64",
|
||||
"gateway": "abcd:1234:ffff::1"
|
||||
}
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"dst": "15.5.6.0/24",
|
||||
"gw": "15.5.6.8"
|
||||
},
|
||||
{
|
||||
"dst": "1111:dddd::/80",
|
||||
"gw": "1111:dddd::aaaa"
|
||||
}
|
||||
],
|
||||
"dns": {
|
||||
"nameservers": [
|
||||
"1.2.3.4",
|
||||
"1::cafe"
|
||||
],
|
||||
"domain": "acompany.com",
|
||||
"search": [
|
||||
"somedomain.com",
|
||||
"otherdomain.net"
|
||||
],
|
||||
"options": [
|
||||
"foo",
|
||||
"bar"
|
||||
]
|
||||
}
|
||||
}`))
|
||||
})
|
||||
|
||||
It("correctly encodes a 0.1.0 Result", func() {
|
||||
res, err := testResult().GetAsVersion("0.1.0")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
Expect(res.String()).To(Equal("IP4:{IP:{IP:1.2.3.30 Mask:ffffff00} Gateway:1.2.3.1 Routes:[{Dst:{IP:15.5.6.0 Mask:ffffff00} GW:15.5.6.8}]}, IP6:{IP:{IP:abcd:1234:ffff::cdde Mask:ffffffffffffffff0000000000000000} Gateway:abcd:1234:ffff::1 Routes:[{Dst:{IP:1111:dddd:: Mask:ffffffffffffffffffff000000000000} GW:1111:dddd::aaaa}]}, DNS:{Nameservers:[1.2.3.4 1::cafe] Domain:acompany.com Search:[somedomain.com otherdomain.net] Options:[foo bar]}"))
|
||||
|
||||
// Redirect stdout to capture JSON result
|
||||
oldStdout := os.Stdout
|
||||
r, w, err := os.Pipe()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
os.Stdout = w
|
||||
err = res.Print()
|
||||
w.Close()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// parse the result
|
||||
out, err := ioutil.ReadAll(r)
|
||||
os.Stdout = oldStdout
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
Expect(string(out)).To(Equal(`{
|
||||
"cniVersion": "0.2.0",
|
||||
"ip4": {
|
||||
"ip": "1.2.3.30/24",
|
||||
"gateway": "1.2.3.1",
|
||||
"routes": [
|
||||
{
|
||||
"dst": "15.5.6.0/24",
|
||||
"gw": "15.5.6.8"
|
||||
}
|
||||
]
|
||||
},
|
||||
"ip6": {
|
||||
"ip": "abcd:1234:ffff::cdde/64",
|
||||
"gateway": "abcd:1234:ffff::1",
|
||||
"routes": [
|
||||
{
|
||||
"dst": "1111:dddd::/80",
|
||||
"gw": "1111:dddd::aaaa"
|
||||
}
|
||||
]
|
||||
},
|
||||
"dns": {
|
||||
"nameservers": [
|
||||
"1.2.3.4",
|
||||
"1::cafe"
|
||||
],
|
||||
"domain": "acompany.com",
|
||||
"search": [
|
||||
"somedomain.com",
|
||||
"otherdomain.net"
|
||||
],
|
||||
"options": [
|
||||
"foo",
|
||||
"bar"
|
||||
]
|
||||
}
|
||||
}`))
|
||||
})
|
||||
})
|
@ -1,185 +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 types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
)
|
||||
|
||||
// like net.IPNet but adds JSON marshalling and unmarshalling
|
||||
type IPNet net.IPNet
|
||||
|
||||
// ParseCIDR takes a string like "10.2.3.1/24" and
|
||||
// return IPNet with "10.2.3.1" and /24 mask
|
||||
func ParseCIDR(s string) (*net.IPNet, error) {
|
||||
ip, ipn, err := net.ParseCIDR(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ipn.IP = ip
|
||||
return ipn, nil
|
||||
}
|
||||
|
||||
func (n IPNet) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal((*net.IPNet)(&n).String())
|
||||
}
|
||||
|
||||
func (n *IPNet) UnmarshalJSON(data []byte) error {
|
||||
var s string
|
||||
if err := json.Unmarshal(data, &s); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tmp, err := ParseCIDR(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*n = IPNet(*tmp)
|
||||
return nil
|
||||
}
|
||||
|
||||
// NetConf describes a network.
|
||||
type NetConf struct {
|
||||
CNIVersion string `json:"cniVersion,omitempty"`
|
||||
|
||||
Name string `json:"name,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
Capabilities map[string]bool `json:"capabilities,omitempty"`
|
||||
IPAM struct {
|
||||
Type string `json:"type,omitempty"`
|
||||
} `json:"ipam,omitempty"`
|
||||
DNS DNS `json:"dns"`
|
||||
}
|
||||
|
||||
// NetConfList describes an ordered list of networks.
|
||||
type NetConfList struct {
|
||||
CNIVersion string `json:"cniVersion,omitempty"`
|
||||
|
||||
Name string `json:"name,omitempty"`
|
||||
Plugins []*NetConf `json:"plugins,omitempty"`
|
||||
}
|
||||
|
||||
type ResultFactoryFunc func([]byte) (Result, error)
|
||||
|
||||
// Result is an interface that provides the result of plugin execution
|
||||
type Result interface {
|
||||
// The highest CNI specification result verison the result supports
|
||||
// without having to convert
|
||||
Version() string
|
||||
|
||||
// Returns the result converted into the requested CNI specification
|
||||
// result version, or an error if conversion failed
|
||||
GetAsVersion(version string) (Result, error)
|
||||
|
||||
// Prints the result in JSON format to stdout
|
||||
Print() error
|
||||
|
||||
// Returns a JSON string representation of the result
|
||||
String() string
|
||||
}
|
||||
|
||||
func PrintResult(result Result, version string) error {
|
||||
newResult, err := result.GetAsVersion(version)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return newResult.Print()
|
||||
}
|
||||
|
||||
// DNS contains values interesting for DNS resolvers
|
||||
type DNS struct {
|
||||
Nameservers []string `json:"nameservers,omitempty"`
|
||||
Domain string `json:"domain,omitempty"`
|
||||
Search []string `json:"search,omitempty"`
|
||||
Options []string `json:"options,omitempty"`
|
||||
}
|
||||
|
||||
type Route struct {
|
||||
Dst net.IPNet
|
||||
GW net.IP
|
||||
}
|
||||
|
||||
func (r *Route) String() string {
|
||||
return fmt.Sprintf("%+v", *r)
|
||||
}
|
||||
|
||||
// Well known error codes
|
||||
// see https://github.com/containernetworking/cni/blob/master/SPEC.md#well-known-error-codes
|
||||
const (
|
||||
ErrUnknown uint = iota // 0
|
||||
ErrIncompatibleCNIVersion // 1
|
||||
ErrUnsupportedField // 2
|
||||
)
|
||||
|
||||
type Error struct {
|
||||
Code uint `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
Details string `json:"details,omitempty"`
|
||||
}
|
||||
|
||||
func (e *Error) Error() string {
|
||||
return e.Msg
|
||||
}
|
||||
|
||||
func (e *Error) Print() error {
|
||||
return prettyPrint(e)
|
||||
}
|
||||
|
||||
// net.IPNet is not JSON (un)marshallable so this duality is needed
|
||||
// for our custom IPNet type
|
||||
|
||||
// JSON (un)marshallable types
|
||||
type route struct {
|
||||
Dst IPNet `json:"dst"`
|
||||
GW net.IP `json:"gw,omitempty"`
|
||||
}
|
||||
|
||||
func (r *Route) UnmarshalJSON(data []byte) error {
|
||||
rt := route{}
|
||||
if err := json.Unmarshal(data, &rt); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r.Dst = net.IPNet(rt.Dst)
|
||||
r.GW = rt.GW
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Route) MarshalJSON() ([]byte, error) {
|
||||
rt := route{
|
||||
Dst: IPNet(r.Dst),
|
||||
GW: r.GW,
|
||||
}
|
||||
|
||||
return json.Marshal(rt)
|
||||
}
|
||||
|
||||
func prettyPrint(obj interface{}) error {
|
||||
data, err := json.MarshalIndent(obj, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = os.Stdout.Write(data)
|
||||
return err
|
||||
}
|
||||
|
||||
// NotImplementedError is used to indicate that a method is not implemented for the given platform
|
||||
var NotImplementedError = errors.New("Not Implemented")
|
@ -1,27 +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 types_test
|
||||
|
||||
import (
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestTypes(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "Types Suite")
|
||||
}
|
@ -1,63 +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 hwaddr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
)
|
||||
|
||||
const (
|
||||
ipRelevantByteLen = 4
|
||||
PrivateMACPrefixString = "0a:58"
|
||||
)
|
||||
|
||||
var (
|
||||
// private mac prefix safe to use
|
||||
PrivateMACPrefix = []byte{0x0a, 0x58}
|
||||
)
|
||||
|
||||
type SupportIp4OnlyErr struct{ msg string }
|
||||
|
||||
func (e SupportIp4OnlyErr) Error() string { return e.msg }
|
||||
|
||||
type MacParseErr struct{ msg string }
|
||||
|
||||
func (e MacParseErr) Error() string { return e.msg }
|
||||
|
||||
type InvalidPrefixLengthErr struct{ msg string }
|
||||
|
||||
func (e InvalidPrefixLengthErr) Error() string { return e.msg }
|
||||
|
||||
// GenerateHardwareAddr4 generates 48 bit virtual mac addresses based on the IP4 input.
|
||||
func GenerateHardwareAddr4(ip net.IP, prefix []byte) (net.HardwareAddr, error) {
|
||||
switch {
|
||||
|
||||
case ip.To4() == nil:
|
||||
return nil, SupportIp4OnlyErr{msg: "GenerateHardwareAddr4 only supports valid IPv4 address as input"}
|
||||
|
||||
case len(prefix) != len(PrivateMACPrefix):
|
||||
return nil, InvalidPrefixLengthErr{msg: fmt.Sprintf(
|
||||
"Prefix has length %d instead of %d", len(prefix), len(PrivateMACPrefix)),
|
||||
}
|
||||
}
|
||||
|
||||
ipByteLen := len(ip)
|
||||
return (net.HardwareAddr)(
|
||||
append(
|
||||
prefix,
|
||||
ip[ipByteLen-ipRelevantByteLen:ipByteLen]...),
|
||||
), nil
|
||||
}
|
@ -1,27 +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 hwaddr_test
|
||||
|
||||
import (
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestHwaddr(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "Hwaddr Suite")
|
||||
}
|
@ -1,74 +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 hwaddr_test
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/utils/hwaddr"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("Hwaddr", func() {
|
||||
Context("Generate Hardware Address", func() {
|
||||
It("generate hardware address based on ipv4 address", func() {
|
||||
testCases := []struct {
|
||||
ip net.IP
|
||||
expectedMAC net.HardwareAddr
|
||||
}{
|
||||
{
|
||||
ip: net.ParseIP("10.0.0.2"),
|
||||
expectedMAC: (net.HardwareAddr)(append(hwaddr.PrivateMACPrefix, 0x0a, 0x00, 0x00, 0x02)),
|
||||
},
|
||||
{
|
||||
ip: net.ParseIP("10.250.0.244"),
|
||||
expectedMAC: (net.HardwareAddr)(append(hwaddr.PrivateMACPrefix, 0x0a, 0xfa, 0x00, 0xf4)),
|
||||
},
|
||||
{
|
||||
ip: net.ParseIP("172.17.0.2"),
|
||||
expectedMAC: (net.HardwareAddr)(append(hwaddr.PrivateMACPrefix, 0xac, 0x11, 0x00, 0x02)),
|
||||
},
|
||||
{
|
||||
ip: net.IPv4(byte(172), byte(17), byte(0), byte(2)),
|
||||
expectedMAC: (net.HardwareAddr)(append(hwaddr.PrivateMACPrefix, 0xac, 0x11, 0x00, 0x02)),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
mac, err := hwaddr.GenerateHardwareAddr4(tc.ip, hwaddr.PrivateMACPrefix)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(mac).To(Equal(tc.expectedMAC))
|
||||
}
|
||||
})
|
||||
|
||||
It("return error if input is not ipv4 address", func() {
|
||||
testCases := []net.IP{
|
||||
net.ParseIP(""),
|
||||
net.ParseIP("2001:db8:0:1:1:1:1:1"),
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
_, err := hwaddr.GenerateHardwareAddr4(tc, hwaddr.PrivateMACPrefix)
|
||||
Expect(err).To(BeAssignableToTypeOf(hwaddr.SupportIp4OnlyErr{}))
|
||||
}
|
||||
})
|
||||
|
||||
It("return error if prefix is invalid", func() {
|
||||
_, err := hwaddr.GenerateHardwareAddr4(net.ParseIP("10.0.0.2"), []byte{0x58})
|
||||
Expect(err).To(BeAssignableToTypeOf(hwaddr.InvalidPrefixLengthErr{}))
|
||||
})
|
||||
})
|
||||
})
|
@ -1,56 +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 sysctl
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Sysctl provides a method to set/get values from /proc/sys - in linux systems
|
||||
// new interface to set/get values of variables formerly handled by sysctl syscall
|
||||
// If optional `params` have only one string value - this function will
|
||||
// set this value into corresponding sysctl variable
|
||||
func Sysctl(name string, params ...string) (string, error) {
|
||||
if len(params) > 1 {
|
||||
return "", fmt.Errorf("unexcepted additional parameters")
|
||||
} else if len(params) == 1 {
|
||||
return setSysctl(name, params[0])
|
||||
}
|
||||
return getSysctl(name)
|
||||
}
|
||||
|
||||
func getSysctl(name string) (string, error) {
|
||||
fullName := filepath.Join("/proc/sys", strings.Replace(name, ".", "/", -1))
|
||||
fullName = filepath.Clean(fullName)
|
||||
data, err := ioutil.ReadFile(fullName)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(data[:len(data)-1]), nil
|
||||
}
|
||||
|
||||
func setSysctl(name, value string) (string, error) {
|
||||
fullName := filepath.Join("/proc/sys", strings.Replace(name, ".", "/", -1))
|
||||
fullName = filepath.Clean(fullName)
|
||||
if err := ioutil.WriteFile(fullName, []byte(value), 0644); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return getSysctl(name)
|
||||
}
|
@ -1,41 +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 utils
|
||||
|
||||
import (
|
||||
"crypto/sha512"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
const (
|
||||
maxChainLength = 28
|
||||
chainPrefix = "CNI-"
|
||||
prefixLength = len(chainPrefix)
|
||||
)
|
||||
|
||||
// Generates a chain name to be used with iptables.
|
||||
// Ensures that the generated chain name is exactly
|
||||
// maxChainLength chars in length
|
||||
func FormatChainName(name string, id string) string {
|
||||
chainBytes := sha512.Sum512([]byte(name + id))
|
||||
chain := fmt.Sprintf("%s%x", chainPrefix, chainBytes)
|
||||
return chain[:maxChainLength]
|
||||
}
|
||||
|
||||
// FormatComment returns a comment used for easier
|
||||
// rule identification within iptables.
|
||||
func FormatComment(name string, id string) string {
|
||||
return fmt.Sprintf("name: %q id: %q", name, id)
|
||||
}
|
@ -1,27 +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 utils_test
|
||||
|
||||
import (
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestUtils(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "Utils Suite")
|
||||
}
|
@ -1,51 +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 utils
|
||||
|
||||
import (
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("Utils", func() {
|
||||
It("must format a short name", func() {
|
||||
chain := FormatChainName("test", "1234")
|
||||
Expect(len(chain)).To(Equal(maxChainLength))
|
||||
Expect(chain).To(Equal("CNI-2bbe0c48b91a7d1b8a6753a8"))
|
||||
})
|
||||
|
||||
It("must truncate a long name", func() {
|
||||
chain := FormatChainName("testalongnamethatdoesnotmakesense", "1234")
|
||||
Expect(len(chain)).To(Equal(maxChainLength))
|
||||
Expect(chain).To(Equal("CNI-374f33fe84ab0ed84dcdebe3"))
|
||||
})
|
||||
|
||||
It("must be predictable", func() {
|
||||
chain1 := FormatChainName("testalongnamethatdoesnotmakesense", "1234")
|
||||
chain2 := FormatChainName("testalongnamethatdoesnotmakesense", "1234")
|
||||
Expect(len(chain1)).To(Equal(maxChainLength))
|
||||
Expect(len(chain2)).To(Equal(maxChainLength))
|
||||
Expect(chain1).To(Equal(chain2))
|
||||
})
|
||||
|
||||
It("must change when a character changes", func() {
|
||||
chain1 := FormatChainName("testalongnamethatdoesnotmakesense", "1234")
|
||||
chain2 := FormatChainName("testalongnamethatdoesnotmakesense", "1235")
|
||||
Expect(len(chain1)).To(Equal(maxChainLength))
|
||||
Expect(len(chain2)).To(Equal(maxChainLength))
|
||||
Expect(chain1).To(Equal("CNI-374f33fe84ab0ed84dcdebe3"))
|
||||
Expect(chain1).NotTo(Equal(chain2))
|
||||
})
|
||||
})
|
@ -1,37 +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 version
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// ConfigDecoder can decode the CNI version available in network config data
|
||||
type ConfigDecoder struct{}
|
||||
|
||||
func (*ConfigDecoder) Decode(jsonBytes []byte) (string, error) {
|
||||
var conf struct {
|
||||
CNIVersion string `json:"cniVersion"`
|
||||
}
|
||||
err := json.Unmarshal(jsonBytes, &conf)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("decoding version from network config: %s", err)
|
||||
}
|
||||
if conf.CNIVersion == "" {
|
||||
return "0.1.0", nil
|
||||
}
|
||||
return conf.CNIVersion, nil
|
||||
}
|
@ -1,69 +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 version_test
|
||||
|
||||
import (
|
||||
"github.com/containernetworking/cni/pkg/version"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("Decoding the version of network config", func() {
|
||||
var (
|
||||
decoder *version.ConfigDecoder
|
||||
configBytes []byte
|
||||
)
|
||||
|
||||
BeforeEach(func() {
|
||||
decoder = &version.ConfigDecoder{}
|
||||
configBytes = []byte(`{ "cniVersion": "4.3.2" }`)
|
||||
})
|
||||
|
||||
Context("when the version is explict", func() {
|
||||
It("returns the version", func() {
|
||||
version, err := decoder.Decode(configBytes)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
Expect(version).To(Equal("4.3.2"))
|
||||
})
|
||||
})
|
||||
|
||||
Context("when the version is not present in the config", func() {
|
||||
BeforeEach(func() {
|
||||
configBytes = []byte(`{ "not-a-version-field": "foo" }`)
|
||||
})
|
||||
|
||||
It("assumes the config is version 0.1.0", func() {
|
||||
version, err := decoder.Decode(configBytes)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
Expect(version).To(Equal("0.1.0"))
|
||||
})
|
||||
})
|
||||
|
||||
Context("when the config data is malformed", func() {
|
||||
BeforeEach(func() {
|
||||
configBytes = []byte(`{{{`)
|
||||
})
|
||||
|
||||
It("returns a useful error", func() {
|
||||
_, err := decoder.Decode(configBytes)
|
||||
Expect(err).To(MatchError(HavePrefix(
|
||||
"decoding version from network config: invalid character",
|
||||
)))
|
||||
})
|
||||
})
|
||||
})
|
@ -1,167 +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 legacy_examples
|
||||
|
||||
// An ExampleRuntime is a small program that uses libcni to invoke a network plugin.
|
||||
// It should call ADD and DELETE, verifying all intermediate steps
|
||||
// and data structures.
|
||||
type ExampleRuntime struct {
|
||||
Example
|
||||
NetConfs []string // The network configuration names to pass
|
||||
}
|
||||
|
||||
// NetConfs are various versioned network configuration files. Examples should
|
||||
// specify which version they expect
|
||||
var NetConfs = map[string]string{
|
||||
"unversioned": `{
|
||||
"name": "default",
|
||||
"type": "ptp",
|
||||
"ipam": {
|
||||
"type": "host-local",
|
||||
"subnet": "10.1.2.0/24"
|
||||
}
|
||||
}`,
|
||||
"0.1.0": `{
|
||||
"cniVersion": "0.1.0",
|
||||
"name": "default",
|
||||
"type": "ptp",
|
||||
"ipam": {
|
||||
"type": "host-local",
|
||||
"subnet": "10.1.2.0/24"
|
||||
}
|
||||
}`,
|
||||
}
|
||||
|
||||
// V010_Runtime creates a simple ptp network configuration, then
|
||||
// executes libcni against the currently-built plugins.
|
||||
var V010_Runtime = ExampleRuntime{
|
||||
NetConfs: []string{"unversioned", "0.1.0"},
|
||||
Example: Example{
|
||||
Name: "example_invoker_v010",
|
||||
CNIRepoGitRef: "c0d34c69", //version with ns.Do
|
||||
PluginSource: `package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/ns"
|
||||
"github.com/containernetworking/cni/libcni"
|
||||
)
|
||||
|
||||
func main(){
|
||||
code := exec()
|
||||
os.Exit(code)
|
||||
}
|
||||
|
||||
func exec() int {
|
||||
confBytes, err := ioutil.ReadAll(os.Stdin)
|
||||
if err != nil {
|
||||
fmt.Printf("could not read netconfig from stdin: %+v", err)
|
||||
return 1
|
||||
}
|
||||
|
||||
netConf, err := libcni.ConfFromBytes(confBytes)
|
||||
if err != nil {
|
||||
fmt.Printf("could not parse netconfig: %+v", err)
|
||||
return 1
|
||||
}
|
||||
fmt.Printf("Parsed network configuration: %+v\n", netConf.Network)
|
||||
|
||||
if len(os.Args) == 1 {
|
||||
fmt.Printf("Expect CNI plugin paths in argv")
|
||||
return 1
|
||||
}
|
||||
|
||||
targetNs, err := ns.NewNS()
|
||||
if err != nil {
|
||||
fmt.Printf("Could not create ns: %+v", err)
|
||||
return 1
|
||||
}
|
||||
defer targetNs.Close()
|
||||
|
||||
ifName := "eth0"
|
||||
|
||||
runtimeConf := &libcni.RuntimeConf{
|
||||
ContainerID: "some-container-id",
|
||||
NetNS: targetNs.Path(),
|
||||
IfName: ifName,
|
||||
}
|
||||
|
||||
cniConfig := &libcni.CNIConfig{Path: os.Args[1:]}
|
||||
|
||||
result, err := cniConfig.AddNetwork(netConf, runtimeConf)
|
||||
if err != nil {
|
||||
fmt.Printf("AddNetwork failed: %+v", err)
|
||||
return 2
|
||||
}
|
||||
fmt.Printf("AddNetwork result: %+v", result)
|
||||
|
||||
expectedIP := result.IP4.IP
|
||||
|
||||
err = targetNs.Do(func(ns.NetNS) error {
|
||||
netif, err := net.InterfaceByName(ifName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not retrieve interface: %v", err)
|
||||
}
|
||||
|
||||
addrs, err := netif.Addrs()
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not retrieve addresses, %+v", err)
|
||||
}
|
||||
|
||||
found := false
|
||||
for _, addr := range addrs {
|
||||
if addr.String() == expectedIP.String() {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
return fmt.Errorf("Far-side link did not have expected address %s", expectedIP)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return 4
|
||||
}
|
||||
|
||||
err = cniConfig.DelNetwork(netConf, runtimeConf)
|
||||
if err != nil {
|
||||
fmt.Printf("DelNetwork failed: %v", err)
|
||||
return 5
|
||||
}
|
||||
|
||||
err = targetNs.Do(func(ns.NetNS) error {
|
||||
_, err := net.InterfaceByName(ifName)
|
||||
if err == nil {
|
||||
return fmt.Errorf("interface was not deleted")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return 6
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
`,
|
||||
},
|
||||
}
|
@ -1,139 +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 legacy_examples contains sample code from prior versions of
|
||||
// the CNI library, for use in verifying backwards compatibility.
|
||||
package legacy_examples
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/containernetworking/cni/pkg/types/020"
|
||||
"github.com/containernetworking/cni/pkg/version/testhelpers"
|
||||
)
|
||||
|
||||
// An Example is a Git reference to the CNI repo and a Golang CNI plugin that
|
||||
// builds against that version of the repo.
|
||||
//
|
||||
// By convention, every Example plugin returns an ADD result that is
|
||||
// semantically equivalent to the ExpectedResult.
|
||||
type Example struct {
|
||||
Name string
|
||||
CNIRepoGitRef string
|
||||
PluginSource string
|
||||
}
|
||||
|
||||
var buildDir = ""
|
||||
var buildDirLock sync.Mutex
|
||||
|
||||
func ensureBuildDirExists() error {
|
||||
buildDirLock.Lock()
|
||||
defer buildDirLock.Unlock()
|
||||
|
||||
if buildDir != "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
var err error
|
||||
buildDir, err = ioutil.TempDir("", "cni-example-plugins")
|
||||
return err
|
||||
}
|
||||
|
||||
// Build builds the example, returning the path to the binary
|
||||
func (e Example) Build() (string, error) {
|
||||
if err := ensureBuildDirExists(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
outBinPath := filepath.Join(buildDir, e.Name)
|
||||
|
||||
if err := testhelpers.BuildAt([]byte(e.PluginSource), e.CNIRepoGitRef, outBinPath); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return outBinPath, nil
|
||||
}
|
||||
|
||||
// V010 acts like a CNI plugin from the v0.1.0 era
|
||||
var V010 = Example{
|
||||
Name: "example_v010",
|
||||
CNIRepoGitRef: "2c482f4",
|
||||
PluginSource: `package main
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/skel"
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
)
|
||||
|
||||
var result = types.Result{
|
||||
IP4: &types.IPConfig{
|
||||
IP: net.IPNet{
|
||||
IP: net.ParseIP("10.1.2.3"),
|
||||
Mask: net.CIDRMask(24, 32),
|
||||
},
|
||||
Gateway: net.ParseIP("10.1.2.1"),
|
||||
Routes: []types.Route{
|
||||
types.Route{
|
||||
Dst: net.IPNet{
|
||||
IP: net.ParseIP("0.0.0.0"),
|
||||
Mask: net.CIDRMask(0, 32),
|
||||
},
|
||||
GW: net.ParseIP("10.1.0.1"),
|
||||
},
|
||||
},
|
||||
},
|
||||
DNS: types.DNS{
|
||||
Nameservers: []string{"8.8.8.8"},
|
||||
Domain: "example.com",
|
||||
},
|
||||
}
|
||||
|
||||
func c(_ *skel.CmdArgs) error { result.Print(); return nil }
|
||||
|
||||
func main() { skel.PluginMain(c, c) }
|
||||
`,
|
||||
}
|
||||
|
||||
// ExpectedResult is the current representation of the plugin result
|
||||
// that is expected from each of the examples.
|
||||
//
|
||||
// As we change the CNI spec, the Result type and this value may change.
|
||||
// The text of the example plugins should not.
|
||||
var ExpectedResult = &types020.Result{
|
||||
IP4: &types020.IPConfig{
|
||||
IP: net.IPNet{
|
||||
IP: net.ParseIP("10.1.2.3"),
|
||||
Mask: net.CIDRMask(24, 32),
|
||||
},
|
||||
Gateway: net.ParseIP("10.1.2.1"),
|
||||
Routes: []types.Route{
|
||||
types.Route{
|
||||
Dst: net.IPNet{
|
||||
IP: net.ParseIP("0.0.0.0"),
|
||||
Mask: net.CIDRMask(0, 32),
|
||||
},
|
||||
GW: net.ParseIP("10.1.0.1"),
|
||||
},
|
||||
},
|
||||
},
|
||||
DNS: types.DNS{
|
||||
Nameservers: []string{"8.8.8.8"},
|
||||
Domain: "example.com",
|
||||
},
|
||||
}
|
@ -1,27 +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 legacy_examples_test
|
||||
|
||||
import (
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLegacyExamples(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "LegacyExamples Suite")
|
||||
}
|
@ -1,36 +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 legacy_examples_test
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/version/legacy_examples"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("The v0.1.0 Example", func() {
|
||||
It("builds ok", func() {
|
||||
example := legacy_examples.V010
|
||||
pluginPath, err := example.Build()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
Expect(filepath.Base(pluginPath)).To(Equal(example.Name))
|
||||
|
||||
Expect(os.RemoveAll(pluginPath)).To(Succeed())
|
||||
})
|
||||
})
|
@ -1,81 +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 version
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// PluginInfo reports information about CNI versioning
|
||||
type PluginInfo interface {
|
||||
// SupportedVersions returns one or more CNI spec versions that the plugin
|
||||
// supports. If input is provided in one of these versions, then the plugin
|
||||
// promises to use the same CNI version in its response
|
||||
SupportedVersions() []string
|
||||
|
||||
// Encode writes this CNI version information as JSON to the given Writer
|
||||
Encode(io.Writer) error
|
||||
}
|
||||
|
||||
type pluginInfo struct {
|
||||
CNIVersion_ string `json:"cniVersion"`
|
||||
SupportedVersions_ []string `json:"supportedVersions,omitempty"`
|
||||
}
|
||||
|
||||
// pluginInfo implements the PluginInfo interface
|
||||
var _ PluginInfo = &pluginInfo{}
|
||||
|
||||
func (p *pluginInfo) Encode(w io.Writer) error {
|
||||
return json.NewEncoder(w).Encode(p)
|
||||
}
|
||||
|
||||
func (p *pluginInfo) SupportedVersions() []string {
|
||||
return p.SupportedVersions_
|
||||
}
|
||||
|
||||
// PluginSupports returns a new PluginInfo that will report the given versions
|
||||
// as supported
|
||||
func PluginSupports(supportedVersions ...string) PluginInfo {
|
||||
if len(supportedVersions) < 1 {
|
||||
panic("programmer error: you must support at least one version")
|
||||
}
|
||||
return &pluginInfo{
|
||||
CNIVersion_: Current(),
|
||||
SupportedVersions_: supportedVersions,
|
||||
}
|
||||
}
|
||||
|
||||
// PluginDecoder can decode the response returned by a plugin's VERSION command
|
||||
type PluginDecoder struct{}
|
||||
|
||||
func (*PluginDecoder) Decode(jsonBytes []byte) (PluginInfo, error) {
|
||||
var info pluginInfo
|
||||
err := json.Unmarshal(jsonBytes, &info)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("decoding version info: %s", err)
|
||||
}
|
||||
if info.CNIVersion_ == "" {
|
||||
return nil, fmt.Errorf("decoding version info: missing field cniVersion")
|
||||
}
|
||||
if len(info.SupportedVersions_) == 0 {
|
||||
if info.CNIVersion_ == "0.2.0" {
|
||||
return PluginSupports("0.1.0", "0.2.0"), nil
|
||||
}
|
||||
return nil, fmt.Errorf("decoding version info: missing field supportedVersions")
|
||||
}
|
||||
return &info, nil
|
||||
}
|
@ -1,85 +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 version_test
|
||||
|
||||
import (
|
||||
"github.com/containernetworking/cni/pkg/version"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("Decoding versions reported by a plugin", func() {
|
||||
var (
|
||||
decoder *version.PluginDecoder
|
||||
versionStdout []byte
|
||||
)
|
||||
|
||||
BeforeEach(func() {
|
||||
decoder = &version.PluginDecoder{}
|
||||
versionStdout = []byte(`{
|
||||
"cniVersion": "some-library-version",
|
||||
"supportedVersions": [ "some-version", "some-other-version" ]
|
||||
}`)
|
||||
})
|
||||
|
||||
It("returns a PluginInfo that represents the given json bytes", func() {
|
||||
pluginInfo, err := decoder.Decode(versionStdout)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(pluginInfo).NotTo(BeNil())
|
||||
Expect(pluginInfo.SupportedVersions()).To(Equal([]string{
|
||||
"some-version",
|
||||
"some-other-version",
|
||||
}))
|
||||
})
|
||||
|
||||
Context("when the bytes cannot be decoded as json", func() {
|
||||
BeforeEach(func() {
|
||||
versionStdout = []byte(`{{{`)
|
||||
})
|
||||
|
||||
It("returns a meaningful error", func() {
|
||||
_, err := decoder.Decode(versionStdout)
|
||||
Expect(err).To(MatchError("decoding version info: invalid character '{' looking for beginning of object key string"))
|
||||
})
|
||||
})
|
||||
|
||||
Context("when the json bytes are missing the required CNIVersion field", func() {
|
||||
BeforeEach(func() {
|
||||
versionStdout = []byte(`{ "supportedVersions": [ "foo" ] }`)
|
||||
})
|
||||
|
||||
It("returns a meaningful error", func() {
|
||||
_, err := decoder.Decode(versionStdout)
|
||||
Expect(err).To(MatchError("decoding version info: missing field cniVersion"))
|
||||
})
|
||||
})
|
||||
|
||||
Context("when there are no supported versions", func() {
|
||||
BeforeEach(func() {
|
||||
versionStdout = []byte(`{ "cniVersion": "0.2.0" }`)
|
||||
})
|
||||
|
||||
It("assumes that the supported versions are 0.1.0 and 0.2.0", func() {
|
||||
pluginInfo, err := decoder.Decode(versionStdout)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(pluginInfo).NotTo(BeNil())
|
||||
Expect(pluginInfo.SupportedVersions()).To(Equal([]string{
|
||||
"0.1.0",
|
||||
"0.2.0",
|
||||
}))
|
||||
})
|
||||
})
|
||||
|
||||
})
|
@ -1,49 +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 version
|
||||
|
||||
import "fmt"
|
||||
|
||||
type ErrorIncompatible struct {
|
||||
Config string
|
||||
Supported []string
|
||||
}
|
||||
|
||||
func (e *ErrorIncompatible) Details() string {
|
||||
return fmt.Sprintf("config is %q, plugin supports %q", e.Config, e.Supported)
|
||||
}
|
||||
|
||||
func (e *ErrorIncompatible) Error() string {
|
||||
return fmt.Sprintf("incompatible CNI versions: %s", e.Details())
|
||||
}
|
||||
|
||||
type Reconciler struct{}
|
||||
|
||||
func (r *Reconciler) Check(configVersion string, pluginInfo PluginInfo) *ErrorIncompatible {
|
||||
return r.CheckRaw(configVersion, pluginInfo.SupportedVersions())
|
||||
}
|
||||
|
||||
func (*Reconciler) CheckRaw(configVersion string, supportedVersions []string) *ErrorIncompatible {
|
||||
for _, supportedVersion := range supportedVersions {
|
||||
if configVersion == supportedVersion {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return &ErrorIncompatible{
|
||||
Config: configVersion,
|
||||
Supported: supportedVersions,
|
||||
}
|
||||
}
|
@ -1,51 +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 version_test
|
||||
|
||||
import (
|
||||
"github.com/containernetworking/cni/pkg/version"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("Reconcile versions of net config with versions supported by plugins", func() {
|
||||
var (
|
||||
reconciler *version.Reconciler
|
||||
pluginInfo version.PluginInfo
|
||||
)
|
||||
|
||||
BeforeEach(func() {
|
||||
reconciler = &version.Reconciler{}
|
||||
pluginInfo = version.PluginSupports("1.2.3", "4.3.2")
|
||||
})
|
||||
|
||||
It("succeeds if the config version is supported by the plugin", func() {
|
||||
err := reconciler.Check("4.3.2", pluginInfo)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
Context("when the config version is not supported by the plugin", func() {
|
||||
It("returns a helpful error", func() {
|
||||
err := reconciler.Check("0.1.0", pluginInfo)
|
||||
|
||||
Expect(err).To(Equal(&version.ErrorIncompatible{
|
||||
Config: "0.1.0",
|
||||
Supported: []string{"1.2.3", "4.3.2"},
|
||||
}))
|
||||
|
||||
Expect(err.Error()).To(Equal(`incompatible CNI versions: config is "0.1.0", plugin supports ["1.2.3" "4.3.2"]`))
|
||||
})
|
||||
})
|
||||
})
|
@ -1,156 +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 testhelpers supports testing of CNI components of different versions
|
||||
//
|
||||
// For example, to build a plugin against an old version of the CNI library,
|
||||
// we can pass the plugin's source and the old git commit reference to BuildAt.
|
||||
// We could then test how the built binary responds when called by the latest
|
||||
// version of this library.
|
||||
package testhelpers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const packageBaseName = "github.com/containernetworking/cni"
|
||||
|
||||
func run(cmd *exec.Cmd) error {
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
command := strings.Join(cmd.Args, " ")
|
||||
return fmt.Errorf("running %q: %s", command, out)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func goBuildEnviron(gopath string) []string {
|
||||
environ := os.Environ()
|
||||
for i, kvp := range environ {
|
||||
if strings.HasPrefix(kvp, "GOPATH=") {
|
||||
environ[i] = "GOPATH=" + gopath
|
||||
return environ
|
||||
}
|
||||
}
|
||||
environ = append(environ, "GOPATH="+gopath)
|
||||
return environ
|
||||
}
|
||||
|
||||
func buildGoProgram(gopath, packageName, outputFilePath string) error {
|
||||
cmd := exec.Command("go", "build", "-o", outputFilePath, packageName)
|
||||
cmd.Env = goBuildEnviron(gopath)
|
||||
return run(cmd)
|
||||
}
|
||||
|
||||
func createSingleFilePackage(gopath, packageName string, fileContents []byte) error {
|
||||
dirName := filepath.Join(gopath, "src", packageName)
|
||||
err := os.MkdirAll(dirName, 0700)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ioutil.WriteFile(filepath.Join(dirName, "main.go"), fileContents, 0600)
|
||||
}
|
||||
|
||||
func removePackage(gopath, packageName string) error {
|
||||
dirName := filepath.Join(gopath, "src", packageName)
|
||||
return os.RemoveAll(dirName)
|
||||
}
|
||||
|
||||
func isRepoRoot(path string) bool {
|
||||
_, err := ioutil.ReadDir(filepath.Join(path, ".git"))
|
||||
return (err == nil) && (filepath.Base(path) == "cni")
|
||||
}
|
||||
|
||||
func LocateCurrentGitRepo() (string, error) {
|
||||
dir, err := os.Getwd()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for i := 0; i < 5; i++ {
|
||||
if isRepoRoot(dir) {
|
||||
return dir, nil
|
||||
}
|
||||
|
||||
dir, err = filepath.Abs(filepath.Dir(dir))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("abs(dir(%q)): %s", dir, err)
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("unable to find cni repo root, landed at %q", dir)
|
||||
}
|
||||
|
||||
func gitCloneThisRepo(cloneDestination string) error {
|
||||
err := os.MkdirAll(cloneDestination, 0700)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
currentGitRepo, err := LocateCurrentGitRepo()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return run(exec.Command("git", "clone", currentGitRepo, cloneDestination))
|
||||
}
|
||||
|
||||
func gitCheckout(localRepo string, gitRef string) error {
|
||||
return run(exec.Command("git", "-C", localRepo, "checkout", gitRef))
|
||||
}
|
||||
|
||||
// BuildAt builds the go programSource using the version of the CNI library
|
||||
// at gitRef, and saves the resulting binary file at outputFilePath
|
||||
func BuildAt(programSource []byte, gitRef string, outputFilePath string) error {
|
||||
tempGoPath, err := ioutil.TempDir("", "cni-git-")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.RemoveAll(tempGoPath)
|
||||
|
||||
cloneDestination := filepath.Join(tempGoPath, "src", packageBaseName)
|
||||
err = gitCloneThisRepo(cloneDestination)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = gitCheckout(cloneDestination, gitRef)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
testPackageName := fmt.Sprintf("test-package-%x", rand.Int31())
|
||||
|
||||
err = createSingleFilePackage(tempGoPath, testPackageName, programSource)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer removePackage(tempGoPath, testPackageName)
|
||||
|
||||
err = buildGoProgram(tempGoPath, testPackageName, outputFilePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -1,27 +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 testhelpers_test
|
||||
|
||||
import (
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestTesthelpers(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "Testhelpers Suite")
|
||||
}
|
@ -1,106 +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 testhelpers_test
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/version/testhelpers"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("BuildAt", func() {
|
||||
var (
|
||||
gitRef string
|
||||
outputFilePath string
|
||||
outputDir string
|
||||
programSource []byte
|
||||
)
|
||||
BeforeEach(func() {
|
||||
programSource = []byte(`package main
|
||||
|
||||
import "github.com/containernetworking/cni/pkg/skel"
|
||||
|
||||
func c(_ *skel.CmdArgs) error { return nil }
|
||||
|
||||
func main() { skel.PluginMain(c, c) }
|
||||
`)
|
||||
gitRef = "f4364185253"
|
||||
|
||||
var err error
|
||||
outputDir, err = ioutil.TempDir("", "bin")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
outputFilePath = filepath.Join(outputDir, "some-binary")
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
Expect(os.RemoveAll(outputDir)).To(Succeed())
|
||||
})
|
||||
|
||||
It("builds the provided source code using the CNI library at the given git ref", func() {
|
||||
Expect(outputFilePath).NotTo(BeAnExistingFile())
|
||||
|
||||
err := testhelpers.BuildAt(programSource, gitRef, outputFilePath)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
Expect(outputFilePath).To(BeAnExistingFile())
|
||||
|
||||
cmd := exec.Command(outputFilePath)
|
||||
cmd.Env = []string{"CNI_COMMAND=VERSION"}
|
||||
output, err := cmd.CombinedOutput()
|
||||
Expect(err).To(BeAssignableToTypeOf(&exec.ExitError{}))
|
||||
Expect(output).To(ContainSubstring("unknown CNI_COMMAND: VERSION"))
|
||||
})
|
||||
})
|
||||
|
||||
var _ = Describe("LocateCurrentGitRepo", func() {
|
||||
It("returns the path to the root of the CNI git repo", func() {
|
||||
path, err := testhelpers.LocateCurrentGitRepo()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
AssertItIsTheCNIRepoRoot(path)
|
||||
})
|
||||
|
||||
Context("when run from a different directory", func() {
|
||||
BeforeEach(func() {
|
||||
os.Chdir("..")
|
||||
})
|
||||
|
||||
It("still finds the CNI repo root", func() {
|
||||
path, err := testhelpers.LocateCurrentGitRepo()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
AssertItIsTheCNIRepoRoot(path)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
func AssertItIsTheCNIRepoRoot(path string) {
|
||||
Expect(path).To(BeADirectory())
|
||||
files, err := ioutil.ReadDir(path)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
names := []string{}
|
||||
for _, file := range files {
|
||||
names = append(names, file.Name())
|
||||
}
|
||||
|
||||
Expect(names).To(ContainElement("SPEC.md"))
|
||||
Expect(names).To(ContainElement("libcni"))
|
||||
Expect(names).To(ContainElement("cnitool"))
|
||||
}
|
@ -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 version
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/containernetworking/cni/pkg/types/020"
|
||||
"github.com/containernetworking/cni/pkg/types/current"
|
||||
)
|
||||
|
||||
// Current reports the version of the CNI spec implemented by this library
|
||||
func Current() string {
|
||||
return "0.3.1"
|
||||
}
|
||||
|
||||
// Legacy PluginInfo describes a plugin that is backwards compatible with the
|
||||
// CNI spec version 0.1.0. In particular, a runtime compiled against the 0.1.0
|
||||
// library ought to work correctly with a plugin that reports support for
|
||||
// Legacy versions.
|
||||
//
|
||||
// Any future CNI spec versions which meet this definition should be added to
|
||||
// this list.
|
||||
var Legacy = PluginSupports("0.1.0", "0.2.0")
|
||||
var All = PluginSupports("0.1.0", "0.2.0", "0.3.0", "0.3.1")
|
||||
|
||||
var resultFactories = []struct {
|
||||
supportedVersions []string
|
||||
newResult types.ResultFactoryFunc
|
||||
}{
|
||||
{current.SupportedVersions, current.NewResult},
|
||||
{types020.SupportedVersions, types020.NewResult},
|
||||
}
|
||||
|
||||
// Finds a Result object matching the requested version (if any) and asks
|
||||
// that object to parse the plugin result, returning an error if parsing failed.
|
||||
func NewResult(version string, resultBytes []byte) (types.Result, error) {
|
||||
reconciler := &Reconciler{}
|
||||
for _, resultFactory := range resultFactories {
|
||||
err := reconciler.CheckRaw(version, resultFactory.supportedVersions)
|
||||
if err == nil {
|
||||
// Result supports this version
|
||||
return resultFactory.newResult(resultBytes)
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("unsupported CNI result version %q", version)
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user