Skip to content

Commit

Permalink
spec/invoke/skel: add GET command
Browse files Browse the repository at this point in the history
  • Loading branch information
dcbw committed May 21, 2018
1 parent 7b8b5da commit 23a5d8e
Show file tree
Hide file tree
Showing 7 changed files with 459 additions and 57 deletions.
211 changes: 198 additions & 13 deletions SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ https://docs.google.com/a/coreos.com/document/d/1CTAL4gwqRofjxyp4tTkbgHtAwb2YCcP
- The container runtime must add the container to each network by executing the corresponding plugins for each network sequentially.
- Upon completion of the container lifecycle, the runtime must execute the plugins in reverse order (relative to the order in which they were executed to add the container) to disconnect the container from the networks.
- The container runtime must not invoke parallel operations for the same container, but is allowed to invoke parallel operations for different containers.
- The container runtime must order ADD and DEL operations for a container, such that ADD is always followed by a corresponding DEL. DEL may be followed by additional DELs, however, and plugins should handle multiple DELs permissively (i.e. plugin DEL should be idempotent).
- The container runtime must order ADD and DEL operations for a container, such that ADD is always eventually followed by a corresponding DEL. DEL may be followed by additional DELs but plugins should handle multiple DELs permissively (i.e. plugin DEL should be idempotent).
- A container must be uniquely identified by a ContainerID. Plugins that store state should do so using a primary key of `(network name, container id)`.
- A runtime must not call ADD twice (without a corresponding DEL) for the same `(network name, container id)`. In other words, a given container ID must be added to a specific network exactly once.

Expand All @@ -63,7 +63,7 @@ It should then assign the IP to the interface and setup the routes consistent wi

The operations that CNI plugins must support are:

- Add container to network
- `ADD`: Add container to network
- Parameters:
- **Container ID**. A unique plaintext identifier for a container, allocated by the runtime. Must not be empty.
- **Network namespace path**. This represents the path to the network namespace to be added, i.e. /proc/[pid]/ns/net or a bind-mount/link to it.
Expand All @@ -75,7 +75,7 @@ The operations that CNI plugins must support are:
- **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
- `DEL`: Delete container from network
- Parameters:
- **Container ID**, as defined above.
- **Network namespace path**, as defined above.
Expand All @@ -84,8 +84,29 @@ The operations that CNI plugins must support are:
- **Name of the interface inside the container**, as defined above.
- All parameters should be the same as those passed to the corresponding add operation.
- A delete operation should release all resources held by the supplied containerid in the configured network.
- If there was a known previous `ADD` or `GET` action for the container, the runtime MUST add a `prevResult` field to the configuration JSON of the plugin (or all plugins in a chain), which MUST be the `Result` of the immediately previous `ADD` or `GET` action in JSON format ([see below](#network-configuration-list-runtime-examples)).
- When `CNI_NETNS` and/or `prevResult` are not provided, the plugin should clean up as many resources as possible (e.g. releasing IPAM allocations) and return a successful response.
- If the runtime cached the `Result` of a previous `ADD` or `GET` response for a given container, it must delete that cached response on a successful `DEL` for that container.

- Report version
- `GET`: Get container network configuration
- Parameters:
- **Container ID**, as defined for `ADD`.
- **Network namespace path**, as defined for `ADD`.
- **Network configuration**, as defined for `ADD`.
- **Extra arguments**, as defined for `ADD`.
- **Name of the interface inside the container**, as defined for `ADD`.
- Result:
- The plugin should return the same result as an `ADD` action for the same inputs.
- **Interfaces list**, as defined for `ADD`
- **IP configuration assigned to each interface**, as defined for `ADD`
- **DNS information**, as defined for `ADD`
- This action should return the same `Result` object as an `ADD` action for the same inputs. The result should not change over the lifetime of the container.
- The plugin should return an error if any general internal state is unexpected. For example, if the plugin's data storage is missing or corrupt, or its control plane is unavailable, it should return an error.
- The plugin should NOT return an error if its expected sandbox state (eg interfaces, IP addresses, routes, etc) is not found, as subsequent elements in the plugin's chain may alter sandbox state.
- A runtime may call `GET` at any time; but if `GET` is called for a container before an `ADD` or after a `DEL` for that container, the plugin should return error 3 to indicate the container is unknown (see [Well-known Error Codes](#well-known-error-codes) section).
- If the previous action for the container was `ADD` or `GET`, the runtime must add a `prevResult` field to the configuration JSON of the plugin (or all plugins in the chain), which must be the `Result` of that previous `ADD` or `GET` action in JSON format ([see below](#network-configuration-list-runtime-examples)).

- `VERSION`: Report version
- Parameters: NONE.
- Result: information about the CNI spec versions supported by the plugin

Expand All @@ -98,7 +119,7 @@ The operations that CNI plugins must support are:

Runtimes must use the type of network (see [Network Configuration](#network-configuration) below) as the name of the executable to invoke.
Runtimes should then look for this executable in a list of predefined directories (the list of directories is not prescribed by this specification). Once found, it must invoke the executable using the following environment variables for argument passing:
- `CNI_COMMAND`: indicates the desired operation; `ADD`, `DEL` or `VERSION`.
- `CNI_COMMAND`: indicates the desired operation; `ADD`, `DEL`, `GET`, or `VERSION`.
- `CNI_CONTAINERID`: Container ID
- `CNI_NETNS`: Path to network namespace file
- `CNI_IFNAME`: Interface name to set up; if the plugin is unable to use this interface name it must return an error
Expand Down Expand Up @@ -273,8 +294,9 @@ The list is described in JSON form, and can be stored on disk or generated from
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.
For the `ADD` action, the runtime MUST also add a `prevResult` field to the configuration JSON of any plugin after the first one, which MUST be the `Result` of the previous plugin (if any) in JSON format ([see below](#network-configuration-list-runtime-examples)).
For the `GET` and `DEL` actions, the runtime MUST (if available) add a `prevResult` field to the configuration JSON of each plugin, which MUST be the `Result` of the immediately previous `ADD` or `GET` action in JSON format ([see below](#network-configuration-list-runtime-examples)).
For the `ADD` and `GET` actions, plugins SHOULD echo the contents of the `prevResult` field to their stdout to allow subsequent plugins (and the runtime) to receive the result, unless they wish to modify or suppress a previous result.
Plugins are allowed to modify or suppress all or part of a `prevResult`.
However, plugins that support a version of the CNI specification that includes the `prevResult` field MUST handle `prevResult` by either passing it through, modifying it, or suppressing it explicitly.
It is a violation of this specification to be unaware of the `prevResult` field.
Expand Down Expand Up @@ -330,7 +352,7 @@ Plugins should generally complete a DEL action without error even if some resour

#### 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.
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:
Expand Down Expand Up @@ -373,7 +395,116 @@ Note that the runtime adds the `cniVersion` and `name` fields from configuration
{
"version": "4",
"address": "10.0.0.5/32",
"interface": 0
"interface": 2
}
],
"interfaces": [
{
"name": "cni0",
"mac": "00:11:22:33:44:55",
},
{
"name": "veth3243",
"mac": "55:44:33:22:11:11",
},
{
"name": "eth0",
"mac": "99:88:77:66:55:44",
"sandbox": "/var/run/netns/blue",
}
],
"dns": {
"nameservers": [ "10.1.0.1" ]
}
}
}
```

Given the same network configuration JSON list, the container runtime would perform the following steps for the `GET` action.

1) first call the `bridge` plugin with the following JSON, including the `prevResult` field containing the JSON response from the `ADD` operation:

```json
{
"cniVersion": "0.4.0",
"name": "dbnet",
"type": "bridge",
"bridge": "cni0",
"args": {
"labels" : {
"appVersion" : "1.0"
}
},
"ipam": {
"type": "host-local",
// ipam specific
"subnet": "10.1.0.0/16",
"gateway": "10.1.0.1"
},
"dns": {
"nameservers": [ "10.1.0.1" ]
}
"prevResult": {
"ips": [
{
"version": "4",
"address": "10.0.0.5/32",
"interface": 2
}
],
"interfaces": [
{
"name": "cni0",
"mac": "00:11:22:33:44:55",
},
{
"name": "veth3243",
"mac": "55:44:33:22:11:11",
},
{
"name": "eth0",
"mac": "99:88:77:66:55:44",
"sandbox": "/var/run/netns/blue",
}
],
"dns": {
"nameservers": [ "10.1.0.1" ]
}
}
}
```

2) next call the `tuning` plugin with the following JSON, including the `prevResult` field containing the JSON response from the `bridge` plugin:

```json
{
"cniVersion": "0.4.0",
"name": "dbnet",
"type": "tuning",
"sysctl": {
"net.core.somaxconn": "500"
},
"prevResult": {
"ips": [
{
"version": "4",
"address": "10.0.0.5/32",
"interface": 2
}
],
"interfaces": [
{
"name": "cni0",
"mac": "00:11:22:33:44:55",
},
{
"name": "veth3243",
"mac": "55:44:33:22:11:11",
},
{
"name": "eth0",
"mac": "99:88:77:66:55:44",
"sandbox": "/var/run/netns/blue",
}
],
"dns": {
Expand All @@ -384,10 +515,9 @@ Note that the runtime adds the `cniVersion` and `name` fields from configuration
```

Given the same network configuration JSON list, the container runtime would perform the following steps for the 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.
Note that plugins are executed in reverse order from the `ADD` and `GET` actions.

1) first call the `tuning` plugin with the following JSON:
1) first call the `tuning` plugin with the following JSON, including the `prevResult` field containing the JSON response from the `GET` action:

```json
{
Expand All @@ -396,11 +526,38 @@ Also note that plugins are executed in reverse order from the ADD action.
"type": "tuning",
"sysctl": {
"net.core.somaxconn": "500"
},
"prevResult": {
"ips": [
{
"version": "4",
"address": "10.0.0.5/32",
"interface": 2
}
],
"interfaces": [
{
"name": "cni0",
"mac": "00:11:22:33:44:55",
},
{
"name": "veth3243",
"mac": "55:44:33:22:11:11",
},
{
"name": "eth0",
"mac": "99:88:77:66:55:44",
"sandbox": "/var/run/netns/blue",
}
],
"dns": {
"nameservers": [ "10.1.0.1" ]
}
}
}
```

2) next call the `bridge` plugin with the following JSON:
2) next call the `bridge` plugin with the following JSON, including the `prevResult` field containing the JSON response from the `GET` action:

```json
{
Expand All @@ -421,6 +578,33 @@ Also note that plugins are executed in reverse order from the ADD action.
},
"dns": {
"nameservers": [ "10.1.0.1" ]
},
"prevResult": {
"ips": [
{
"version": "4",
"address": "10.0.0.5/32",
"interface": 2
}
],
"interfaces": [
{
"name": "cni0",
"mac": "00:11:22:33:44:55",
},
{
"name": "veth3243",
"mac": "55:44:33:22:11:11",
},
{
"name": "eth0",
"mac": "99:88:77:66:55:44",
"sandbox": "/var/run/netns/blue",
}
],
"dns": {
"nameservers": [ "10.1.0.1" ]
}
}
}
```
Expand Down Expand Up @@ -552,3 +736,4 @@ Error codes 1-99 must not be used other than as specified here.

- `1` - Incompatible CNI version
- `2` - Unsupported field in network configuration. The error message must contain the key and value of the unsupported field.
- `3` - Container unknown or does not exist. This error implies the runtime does not need to perform any container network cleanup (for example, calling the `DEL` action on the container).
27 changes: 21 additions & 6 deletions pkg/invoke/delegate.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,8 @@ import (
"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")
}

func delegateAddOrGet(command, delegatePlugin string, netconf []byte) (types.Result, error) {
paths := filepath.SplitList(os.Getenv("CNI_PATH"))

pluginPath, err := FindInPath(delegatePlugin, paths)
if err != nil {
return nil, err
Expand All @@ -37,6 +32,26 @@ func DelegateAdd(delegatePlugin string, netconf []byte) (types.Result, error) {
return ExecPluginWithResult(pluginPath, netconf, ArgsFromEnv())
}

// DelegateAdd calls the given delegate plugin with the CNI ADD action and
// JSON configuration
func DelegateAdd(delegatePlugin string, netconf []byte) (types.Result, error) {
if os.Getenv("CNI_COMMAND") != "ADD" {
return nil, fmt.Errorf("CNI_COMMAND is not ADD")
}
return delegateAddOrGet("ADD", delegatePlugin, netconf)
}

// DelegateGet calls the given delegate plugin with the CNI GET action and
// JSON configuration
func DelegateGet(delegatePlugin string, netconf []byte) (types.Result, error) {
if os.Getenv("CNI_COMMAND") != "GET" {
return nil, fmt.Errorf("CNI_COMMAND is not GET")
}
return delegateAddOrGet("GET", delegatePlugin, netconf)
}

// DelegateDel calls the given delegate plugin with the CNI DEL action and
// JSON configuration
func DelegateDel(delegatePlugin string, netconf []byte) error {
if os.Getenv("CNI_COMMAND") != "DEL" {
return fmt.Errorf("CNI_COMMAND is not DEL")
Expand Down
43 changes: 41 additions & 2 deletions pkg/invoke/delegate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,11 @@ var _ = Describe("Delegate", func() {
BeforeEach(func() {
netConf, _ = json.Marshal(map[string]string{
"name": "delegate-test",
"cniVersion": "0.3.1",
"cniVersion": "0.4.0",
})

expectedResult = &current.Result{
CNIVersion: "0.3.1",
CNIVersion: "0.4.0",
IPs: []*current.IPConfig{
{
Version: "4",
Expand Down Expand Up @@ -122,6 +122,45 @@ var _ = Describe("Delegate", func() {
})
})

Describe("DelegateGet", func() {
BeforeEach(func() {
os.Setenv("CNI_COMMAND", "GET")
})

It("finds and execs the named plugin", func() {
result, err := invoke.DelegateGet(pluginName, netConf)
Expect(err).NotTo(HaveOccurred())
Expect(result).To(Equal(expectedResult))

pluginInvocation, err := debug.ReadDebug(debugFileName)
Expect(err).NotTo(HaveOccurred())
Expect(pluginInvocation.Command).To(Equal("GET"))
Expect(pluginInvocation.CmdArgs.IfName).To(Equal("eth7"))
})

Context("if the delegation isn't part of an existing GET command", func() {
BeforeEach(func() {
os.Setenv("CNI_COMMAND", "NOPE")
})

It("aborts and returns a useful error", func() {
_, err := invoke.DelegateGet(pluginName, netConf)
Expect(err).To(MatchError("CNI_COMMAND is not GET"))
})
})

Context("when the plugin cannot be found", func() {
BeforeEach(func() {
pluginName = "non-existent-plugin"
})

It("returns a useful error", func() {
_, err := invoke.DelegateGet(pluginName, netConf)
Expect(err).To(MatchError(HavePrefix("failed to find plugin")))
})
})
})

Describe("DelegateDel", func() {
BeforeEach(func() {
os.Setenv("CNI_COMMAND", "DEL")
Expand Down
Loading

0 comments on commit 23a5d8e

Please sign in to comment.