From aec99d6f80e6023a55641b17e03b09ffa5325a9b Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Fri, 1 Sep 2017 22:25:03 -0500 Subject: [PATCH 1/3] vendor: update CNI to 0.6.0 Signed-off-by: Dan Williams --- .../containernetworking/cni/libcni/api.go | 135 +++++++- .../containernetworking/cni/libcni/conf.go | 173 +++++++++- .../cni/pkg/invoke/args.go | 17 +- .../cni/pkg/invoke/delegate.go | 8 +- .../cni/pkg/invoke/exec.go | 16 +- .../cni/pkg/invoke/find.go | 16 +- .../cni/pkg/invoke/os_unix.go | 20 ++ .../cni/pkg/invoke/os_windows.go | 18 ++ .../cni/pkg/invoke/raw_exec.go | 8 +- .../cni/pkg/types/020/types.go | 135 ++++++++ .../containernetworking/cni/pkg/types/args.go | 15 +- .../cni/pkg/types/current/types.go | 300 ++++++++++++++++++ .../cni/pkg/types/types.go | 102 +++--- .../cni/pkg/version/reconcile.go | 20 +- .../cni/pkg/version/version.go | 34 +- .../{cni => plugins}/pkg/ns/README.md | 8 +- .../{cni => plugins}/pkg/ns/ns.go | 131 +------- .../plugins/pkg/ns/ns_linux.go | 149 +++++++++ .../plugins/pkg/ns/ns_unspecified.go | 36 +++ 19 files changed, 1092 insertions(+), 249 deletions(-) create mode 100644 vendor/github.com/containernetworking/cni/pkg/invoke/os_unix.go create mode 100644 vendor/github.com/containernetworking/cni/pkg/invoke/os_windows.go create mode 100644 vendor/github.com/containernetworking/cni/pkg/types/020/types.go create mode 100644 vendor/github.com/containernetworking/cni/pkg/types/current/types.go rename vendor/github.com/containernetworking/{cni => plugins}/pkg/ns/README.md (72%) rename vendor/github.com/containernetworking/{cni => plugins}/pkg/ns/ns.go (58%) create mode 100644 vendor/github.com/containernetworking/plugins/pkg/ns/ns_linux.go create mode 100644 vendor/github.com/containernetworking/plugins/pkg/ns/ns_unspecified.go diff --git a/vendor/github.com/containernetworking/cni/libcni/api.go b/vendor/github.com/containernetworking/cni/libcni/api.go index dfc30cab..a23cbb2c 100644 --- a/vendor/github.com/containernetworking/cni/libcni/api.go +++ b/vendor/github.com/containernetworking/cni/libcni/api.go @@ -15,6 +15,7 @@ package libcni import ( + "os" "strings" "github.com/containernetworking/cni/pkg/invoke" @@ -27,6 +28,12 @@ type RuntimeConf struct { 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 { @@ -34,8 +41,18 @@ type NetworkConfig struct { Bytes []byte } +type NetworkConfigList struct { + Name string + CNIVersion string + Plugins []*NetworkConfig + Bytes []byte +} + type CNI interface { - AddNetwork(net *NetworkConfig, rt *RuntimeConf) (*types.Result, error) + 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 } @@ -46,13 +63,120 @@ type CNIConfig struct { // 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) { +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)) } @@ -63,6 +187,11 @@ func (c *CNIConfig) DelNetwork(net *NetworkConfig, rt *RuntimeConf) error { return err } + net, err = injectRuntimeConfig(net, rt) + if err != nil { + return err + } + return invoke.ExecPluginWithoutResult(pluginPath, net.Bytes, c.args("DEL", rt)) } @@ -85,6 +214,6 @@ func (c *CNIConfig) args(action string, rt *RuntimeConf) *invoke.Args { NetNS: rt.NetNS, PluginArgs: rt.Args, IfName: rt.IfName, - Path: strings.Join(c.Path, ":"), + Path: strings.Join(c.Path, string(os.PathListSeparator)), } } diff --git a/vendor/github.com/containernetworking/cni/libcni/conf.go b/vendor/github.com/containernetworking/cni/libcni/conf.go index 708686e6..c7738c66 100644 --- a/vendor/github.com/containernetworking/cni/libcni/conf.go +++ b/vendor/github.com/containernetworking/cni/libcni/conf.go @@ -23,6 +23,23 @@ import ( "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 { @@ -39,7 +56,73 @@ func ConfFromFile(filename string) (*NetworkConfig, error) { return ConfFromBytes(bytes) } -func ConfFiles(dir string) ([]string, error) { +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 { @@ -56,20 +139,22 @@ func ConfFiles(dir string) ([]string, error) { continue } fileExt := filepath.Ext(f.Name()) - if fileExt == ".conf" || fileExt == ".json" { - confFiles = append(confFiles, filepath.Join(dir, 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) + files, err := ConfFiles(dir, []string{".conf", ".json"}) switch { case err != nil: return nil, err case len(files) == 0: - return nil, fmt.Errorf("no net configurations found") + return nil, NoConfigsFoundError{Dir: dir} } sort.Strings(files) @@ -82,25 +167,59 @@ func LoadConf(dir, name string) (*NetworkConfig, error) { return conf, nil } } - return nil, fmt.Errorf(`no net configuration with name "%s" in %s`, name, dir) + return nil, NotFoundError{dir, name} } -func InjectConf(original *NetworkConfig, key string, newValue interface{}) (*NetworkConfig, error) { +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) } - if key == "" { - return nil, fmt.Errorf("key value can not be empty") - } + for key, value := range newValues { + if key == "" { + return nil, fmt.Errorf("keys cannot be empty") + } - if newValue == nil { - return nil, fmt.Errorf("newValue must be specified") - } + if value == nil { + return nil, fmt.Errorf("key '%s' value must not be nil", key) + } - config[key] = newValue + config[key] = value + } newBytes, err := json.Marshal(config) if err != nil { @@ -109,3 +228,29 @@ func InjectConf(original *NetworkConfig, key string, newValue interface{}) (*Net 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) +} diff --git a/vendor/github.com/containernetworking/cni/pkg/invoke/args.go b/vendor/github.com/containernetworking/cni/pkg/invoke/args.go index ba9d0c3b..39b63972 100644 --- a/vendor/github.com/containernetworking/cni/pkg/invoke/args.go +++ b/vendor/github.com/containernetworking/cni/pkg/invoke/args.go @@ -57,13 +57,16 @@ func (args *Args) AsEnv() []string { 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) + // Ensure that the custom values are first, so any value present in + // the process environment won't override them. + env = append([]string{ + "CNI_COMMAND=" + args.Command, + "CNI_CONTAINERID=" + args.ContainerID, + "CNI_NETNS=" + args.NetNS, + "CNI_ARGS=" + pluginArgsStr, + "CNI_IFNAME=" + args.IfName, + "CNI_PATH=" + args.Path, + }, env...) return env } diff --git a/vendor/github.com/containernetworking/cni/pkg/invoke/delegate.go b/vendor/github.com/containernetworking/cni/pkg/invoke/delegate.go index ddf1d172..c78a69ee 100644 --- a/vendor/github.com/containernetworking/cni/pkg/invoke/delegate.go +++ b/vendor/github.com/containernetworking/cni/pkg/invoke/delegate.go @@ -17,17 +17,17 @@ package invoke import ( "fmt" "os" - "strings" + "path/filepath" "github.com/containernetworking/cni/pkg/types" ) -func DelegateAdd(delegatePlugin string, netconf []byte) (*types.Result, error) { +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 := strings.Split(os.Getenv("CNI_PATH"), ":") + paths := filepath.SplitList(os.Getenv("CNI_PATH")) pluginPath, err := FindInPath(delegatePlugin, paths) if err != nil { @@ -42,7 +42,7 @@ func DelegateDel(delegatePlugin string, netconf []byte) error { return fmt.Errorf("CNI_COMMAND is not DEL") } - paths := strings.Split(os.Getenv("CNI_PATH"), ":") + paths := filepath.SplitList(os.Getenv("CNI_PATH")) pluginPath, err := FindInPath(delegatePlugin, paths) if err != nil { diff --git a/vendor/github.com/containernetworking/cni/pkg/invoke/exec.go b/vendor/github.com/containernetworking/cni/pkg/invoke/exec.go index 167d38f5..fc47e7c8 100644 --- a/vendor/github.com/containernetworking/cni/pkg/invoke/exec.go +++ b/vendor/github.com/containernetworking/cni/pkg/invoke/exec.go @@ -15,7 +15,6 @@ package invoke import ( - "encoding/json" "fmt" "os" @@ -23,7 +22,7 @@ import ( "github.com/containernetworking/cni/pkg/version" ) -func ExecPluginWithResult(pluginPath string, netconf []byte, args CNIArgs) (*types.Result, error) { +func ExecPluginWithResult(pluginPath string, netconf []byte, args CNIArgs) (types.Result, error) { return defaultPluginExec.WithResult(pluginPath, netconf, args) } @@ -49,15 +48,20 @@ type PluginExec struct { } } -func (e *PluginExec) WithResult(pluginPath string, netconf []byte, args CNIArgs) (*types.Result, 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 } - res := &types.Result{} - err = json.Unmarshal(stdoutBytes, res) - return res, 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 { diff --git a/vendor/github.com/containernetworking/cni/pkg/invoke/find.go b/vendor/github.com/containernetworking/cni/pkg/invoke/find.go index 3b037907..e815404c 100644 --- a/vendor/github.com/containernetworking/cni/pkg/invoke/find.go +++ b/vendor/github.com/containernetworking/cni/pkg/invoke/find.go @@ -30,18 +30,14 @@ func FindInPath(plugin string, paths []string) (string, error) { return "", fmt.Errorf("no paths provided") } - var fullpath string for _, path := range paths { - full := filepath.Join(path, plugin) - if fi, err := os.Stat(full); err == nil && fi.Mode().IsRegular() { - fullpath = full - break + for _, fe := range ExecutableFileExtensions { + fullpath := filepath.Join(path, plugin) + fe + if fi, err := os.Stat(fullpath); err == nil && fi.Mode().IsRegular() { + return fullpath, nil + } } } - if fullpath == "" { - return "", fmt.Errorf("failed to find plugin %q in path %s", plugin, paths) - } - - return fullpath, nil + return "", fmt.Errorf("failed to find plugin %q in path %s", plugin, paths) } diff --git a/vendor/github.com/containernetworking/cni/pkg/invoke/os_unix.go b/vendor/github.com/containernetworking/cni/pkg/invoke/os_unix.go new file mode 100644 index 00000000..bab5737a --- /dev/null +++ b/vendor/github.com/containernetworking/cni/pkg/invoke/os_unix.go @@ -0,0 +1,20 @@ +// 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{""} diff --git a/vendor/github.com/containernetworking/cni/pkg/invoke/os_windows.go b/vendor/github.com/containernetworking/cni/pkg/invoke/os_windows.go new file mode 100644 index 00000000..7665125b --- /dev/null +++ b/vendor/github.com/containernetworking/cni/pkg/invoke/os_windows.go @@ -0,0 +1,18 @@ +// 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", ""} diff --git a/vendor/github.com/containernetworking/cni/pkg/invoke/raw_exec.go b/vendor/github.com/containernetworking/cni/pkg/invoke/raw_exec.go index d1bd860d..93f1e75d 100644 --- a/vendor/github.com/containernetworking/cni/pkg/invoke/raw_exec.go +++ b/vendor/github.com/containernetworking/cni/pkg/invoke/raw_exec.go @@ -50,13 +50,9 @@ 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) + emsg.Msg = fmt.Sprintf("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 &emsg } return err diff --git a/vendor/github.com/containernetworking/cni/pkg/types/020/types.go b/vendor/github.com/containernetworking/cni/pkg/types/020/types.go new file mode 100644 index 00000000..2833aba7 --- /dev/null +++ b/vendor/github.com/containernetworking/cni/pkg/types/020/types.go @@ -0,0 +1,135 @@ +// 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 +} diff --git a/vendor/github.com/containernetworking/cni/pkg/types/args.go b/vendor/github.com/containernetworking/cni/pkg/types/args.go index 66dcf9ea..bd8640fc 100644 --- a/vendor/github.com/containernetworking/cni/pkg/types/args.go +++ b/vendor/github.com/containernetworking/cni/pkg/types/args.go @@ -63,6 +63,12 @@ func GetKeyField(keyString string, v reflect.Value) reflect.Value { return v.Elem().FieldByName(keyString) } +// UnmarshalableArgsError is used to indicate error unmarshalling args +// from the args-string in the form "K=V;K2=V2;..." +type UnmarshalableArgsError struct { + error +} + // LoadArgs parses args from a string in the form "K=V;K2=V2;..." func LoadArgs(args string, container interface{}) error { if args == "" { @@ -85,8 +91,13 @@ func LoadArgs(args string, container interface{}) error { unknownArgs = append(unknownArgs, pair) continue } - - u := keyField.Addr().Interface().(encoding.TextUnmarshaler) + keyFieldIface := keyField.Addr().Interface() + u, ok := keyFieldIface.(encoding.TextUnmarshaler) + if !ok { + return UnmarshalableArgsError{fmt.Errorf( + "ARGS: cannot unmarshal into field '%s' - type '%s' does not implement encoding.TextUnmarshaler", + keyString, reflect.TypeOf(keyFieldIface))} + } err := u.UnmarshalText([]byte(valueString)) if err != nil { return fmt.Errorf("ARGS: error parsing value of pair %q: %v)", pair, err) diff --git a/vendor/github.com/containernetworking/cni/pkg/types/current/types.go b/vendor/github.com/containernetworking/cni/pkg/types/current/types.go new file mode 100644 index 00000000..caac92ba --- /dev/null +++ b/vendor/github.com/containernetworking/cni/pkg/types/current/types.go @@ -0,0 +1,300 @@ +// 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", + 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", + 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) +} + +// Int returns a pointer to the int value passed in. Used to +// set the IPConfig.Interface field. +func Int(v int) *int { + return &v +} + +// 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 +} diff --git a/vendor/github.com/containernetworking/cni/pkg/types/types.go b/vendor/github.com/containernetworking/cni/pkg/types/types.go index 17caa49b..64127560 100644 --- a/vendor/github.com/containernetworking/cni/pkg/types/types.go +++ b/vendor/github.com/containernetworking/cni/pkg/types/types.go @@ -16,6 +16,7 @@ package types import ( "encoding/json" + "errors" "fmt" "net" "os" @@ -59,44 +60,48 @@ func (n *IPNet) UnmarshalJSON(data []byte) error { type NetConf struct { CNIVersion string `json:"cniVersion,omitempty"` - Name string `json:"name,omitempty"` - Type string `json:"type,omitempty"` - IPAM struct { + 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"` } -// Result is what gets returned from the plugin (via stdout) to the caller -type Result struct { - IP4 *IPConfig `json:"ip4,omitempty"` - IP6 *IPConfig `json:"ip6,omitempty"` - DNS DNS `json:"dns,omitempty"` +// 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"` } -func (r *Result) Print() error { - return prettyPrint(r) +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 } -// 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) +func PrintResult(result Result, version string) error { + newResult, err := result.GetAsVersion(version) + if err != nil { + return err } - 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 []Route + return newResult.Print() } // DNS contains values interesting for DNS resolvers @@ -112,6 +117,10 @@ type Route struct { 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 ( @@ -127,7 +136,11 @@ type Error struct { } func (e *Error) Error() string { - return e.Msg + details := "" + if e.Details != "" { + details = fmt.Sprintf("; %v", e.Details) + } + return fmt.Sprintf("%v%v", e.Msg, details) } func (e *Error) Print() error { @@ -138,39 +151,11 @@ func (e *Error) Print() error { // for our custom IPNet type // JSON (un)marshallable types -type ipConfig struct { - IP IPNet `json:"ip"` - Gateway net.IP `json:"gateway,omitempty"` - Routes []Route `json:"routes,omitempty"` -} - type route struct { Dst IPNet `json:"dst"` GW net.IP `json:"gw,omitempty"` } -func (c *IPConfig) MarshalJSON() ([]byte, error) { - ipc := ipConfig{ - IP: 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 -} - func (r *Route) UnmarshalJSON(data []byte) error { rt := route{} if err := json.Unmarshal(data, &rt); err != nil { @@ -199,3 +184,6 @@ func prettyPrint(obj interface{}) error { _, err = os.Stdout.Write(data) return err } + +// NotImplementedError is used to indicate that a method is not implemented for the given platform +var NotImplementedError = errors.New("Not Implemented") diff --git a/vendor/github.com/containernetworking/cni/pkg/version/reconcile.go b/vendor/github.com/containernetworking/cni/pkg/version/reconcile.go index f61ef653..25c3810b 100644 --- a/vendor/github.com/containernetworking/cni/pkg/version/reconcile.go +++ b/vendor/github.com/containernetworking/cni/pkg/version/reconcile.go @@ -17,12 +17,12 @@ package version import "fmt" type ErrorIncompatible struct { - Config string - Plugin []string + Config string + Supported []string } func (e *ErrorIncompatible) Details() string { - return fmt.Sprintf("config is %q, plugin supports %q", e.Config, e.Plugin) + return fmt.Sprintf("config is %q, plugin supports %q", e.Config, e.Supported) } func (e *ErrorIncompatible) Error() string { @@ -31,17 +31,19 @@ func (e *ErrorIncompatible) Error() string { type Reconciler struct{} -func (*Reconciler) Check(configVersion string, pluginInfo PluginInfo) *ErrorIncompatible { - pluginVersions := pluginInfo.SupportedVersions() +func (r *Reconciler) Check(configVersion string, pluginInfo PluginInfo) *ErrorIncompatible { + return r.CheckRaw(configVersion, pluginInfo.SupportedVersions()) +} - for _, pluginVersion := range pluginVersions { - if configVersion == pluginVersion { +func (*Reconciler) CheckRaw(configVersion string, supportedVersions []string) *ErrorIncompatible { + for _, supportedVersion := range supportedVersions { + if configVersion == supportedVersion { return nil } } return &ErrorIncompatible{ - Config: configVersion, - Plugin: pluginVersions, + Config: configVersion, + Supported: supportedVersions, } } diff --git a/vendor/github.com/containernetworking/cni/pkg/version/version.go b/vendor/github.com/containernetworking/cni/pkg/version/version.go index e39c3b55..efe8ea87 100644 --- a/vendor/github.com/containernetworking/cni/pkg/version/version.go +++ b/vendor/github.com/containernetworking/cni/pkg/version/version.go @@ -14,9 +14,17 @@ 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.2.0" + return "0.3.1" } // Legacy PluginInfo describes a plugin that is backwards compatible with the @@ -27,3 +35,27 @@ func Current() string { // 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) +} diff --git a/vendor/github.com/containernetworking/cni/pkg/ns/README.md b/vendor/github.com/containernetworking/plugins/pkg/ns/README.md similarity index 72% rename from vendor/github.com/containernetworking/cni/pkg/ns/README.md rename to vendor/github.com/containernetworking/plugins/pkg/ns/README.md index 99aed9c8..c0f5cf2e 100644 --- a/vendor/github.com/containernetworking/cni/pkg/ns/README.md +++ b/vendor/github.com/containernetworking/plugins/pkg/ns/README.md @@ -9,7 +9,7 @@ Go provides the `runtime.LockOSThread()` function to ensure a specific goroutine 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: +The `ns.Do()` method provides **partial** 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() @@ -28,7 +28,13 @@ err = targetNs.Do(func(hostNs ns.NetNS) error { Note this requirement to wrap every network call is very onerous - any libraries you call might call out to network services such as DNS, and all such calls need to be protected after you call `ns.Do()`. The CNI plugins all exit very soon after calling `ns.Do()` which helps to minimize the problem. +Also: If the runtime spawns a new OS thread, it will inherit the network namespace of the parent thread, which may have been temporarily switched, and thus the new OS thread will be permanently "stuck in the wrong namespace". + +In short, **there is no safe way to change network namespaces from within a long-lived, multithreaded Go process**. If your daemon process needs to be namespace aware, consider spawning a separate process (like a CNI plugin) for each namespace. + ### Further Reading - https://github.com/golang/go/wiki/LockOSThread - http://morsmachine.dk/go-scheduler - https://github.com/containernetworking/cni/issues/262 + - https://golang.org/pkg/runtime/ + - https://www.weave.works/blog/linux-namespaces-and-go-don-t-mix diff --git a/vendor/github.com/containernetworking/cni/pkg/ns/ns.go b/vendor/github.com/containernetworking/plugins/pkg/ns/ns.go similarity index 58% rename from vendor/github.com/containernetworking/cni/pkg/ns/ns.go rename to vendor/github.com/containernetworking/plugins/pkg/ns/ns.go index 220dd694..c212f489 100644 --- a/vendor/github.com/containernetworking/cni/pkg/ns/ns.go +++ b/vendor/github.com/containernetworking/plugins/pkg/ns/ns.go @@ -15,15 +15,11 @@ package ns import ( - "crypto/rand" "fmt" "os" - "path" "runtime" "sync" "syscall" - - "golang.org/x/sys/unix" ) type NetNS interface { @@ -65,18 +61,6 @@ type netNS struct { // netNS implements the NetNS interface var _ NetNS = &netNS{} -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()) -} - -// Returns an object representing the current OS thread's network namespace -func GetCurrentNS() (NetNS, error) { - return GetNS(getCurrentThreadNetNSPath()) -} - const ( // https://github.com/torvalds/linux/blob/master/include/uapi/linux/magic.h NSFS_MAGIC = 0x6e736673 @@ -125,82 +109,6 @@ func GetNS(nspath string) (NetNS, error) { return &netNS{file: fd}, nil } -// 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) Path() string { return ns.file.Name() } @@ -216,36 +124,13 @@ func (ns *netNS) errorIfClosed() error { return 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) Do(toRun func(NetNS) error) error { if err := ns.errorIfClosed(); err != nil { return err } containedCall := func(hostNS NetNS) error { - threadNS, err := GetNS(getCurrentThreadNetNSPath()) + threadNS, err := GetCurrentNS() if err != nil { return fmt.Errorf("failed to open current netns: %v", err) } @@ -261,7 +146,7 @@ func (ns *netNS) Do(toRun func(NetNS) error) error { } // save a handle to current network namespace - hostNS, err := GetNS(getCurrentThreadNetNSPath()) + hostNS, err := GetCurrentNS() if err != nil { return fmt.Errorf("Failed to open current namespace: %v", err) } @@ -281,18 +166,6 @@ func (ns *netNS) Do(toRun func(NetNS) error) error { return innerError } -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 -} - // WithNetNSPath executes the passed closure under the given network // namespace, restoring the original namespace afterwards. func WithNetNSPath(nspath string, toRun func(NetNS) error) error { diff --git a/vendor/github.com/containernetworking/plugins/pkg/ns/ns_linux.go b/vendor/github.com/containernetworking/plugins/pkg/ns/ns_linux.go new file mode 100644 index 00000000..8949d21b --- /dev/null +++ b/vendor/github.com/containernetworking/plugins/pkg/ns/ns_linux.go @@ -0,0 +1,149 @@ +// 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.Setns(int(ns.Fd()), unix.CLONE_NEWNET); err != nil { + return fmt.Errorf("Error switching to ns %v: %v", ns.file.Name(), err) + } + + return nil +} diff --git a/vendor/github.com/containernetworking/plugins/pkg/ns/ns_unspecified.go b/vendor/github.com/containernetworking/plugins/pkg/ns/ns_unspecified.go new file mode 100644 index 00000000..41b44686 --- /dev/null +++ b/vendor/github.com/containernetworking/plugins/pkg/ns/ns_unspecified.go @@ -0,0 +1,36 @@ +// 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 +} From 3db6ba7667e64b763e97822a07871cdf6e20fd37 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Tue, 5 Sep 2017 15:06:26 -0500 Subject: [PATCH 2/3] vendor: add github.com/cri-o/ocicni Signed-off-by: Dan Williams --- vendor/github.com/cri-o/ocicni/LICENSE | 191 ++++++++ vendor/github.com/cri-o/ocicni/README.md | 3 + .../cri-o/ocicni/pkg/ocicni/noop.go | 24 + .../cri-o/ocicni/pkg/ocicni/ocicni.go | 421 ++++++++++++++++++ .../cri-o/ocicni/pkg/ocicni/types.go | 62 +++ .../cri-o/ocicni/pkg/ocicni/util.go | 32 ++ 6 files changed, 733 insertions(+) create mode 100644 vendor/github.com/cri-o/ocicni/LICENSE create mode 100644 vendor/github.com/cri-o/ocicni/README.md create mode 100644 vendor/github.com/cri-o/ocicni/pkg/ocicni/noop.go create mode 100644 vendor/github.com/cri-o/ocicni/pkg/ocicni/ocicni.go create mode 100644 vendor/github.com/cri-o/ocicni/pkg/ocicni/types.go create mode 100644 vendor/github.com/cri-o/ocicni/pkg/ocicni/util.go diff --git a/vendor/github.com/cri-o/ocicni/LICENSE b/vendor/github.com/cri-o/ocicni/LICENSE new file mode 100644 index 00000000..3fd70307 --- /dev/null +++ b/vendor/github.com/cri-o/ocicni/LICENSE @@ -0,0 +1,191 @@ + + 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 + + Copyright 2016 Red Hat, Inc. + + 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. diff --git a/vendor/github.com/cri-o/ocicni/README.md b/vendor/github.com/cri-o/ocicni/README.md new file mode 100644 index 00000000..99c103c8 --- /dev/null +++ b/vendor/github.com/cri-o/ocicni/README.md @@ -0,0 +1,3 @@ +# ocicni + +API layer to call the CNI plugins from an OCI lifecycle daemon diff --git a/vendor/github.com/cri-o/ocicni/pkg/ocicni/noop.go b/vendor/github.com/cri-o/ocicni/pkg/ocicni/noop.go new file mode 100644 index 00000000..9f315a7c --- /dev/null +++ b/vendor/github.com/cri-o/ocicni/pkg/ocicni/noop.go @@ -0,0 +1,24 @@ +package ocicni + +type cniNoOp struct { +} + +func (noop *cniNoOp) Name() string { + return "CNINoOp" +} + +func (noop *cniNoOp) SetUpPod(network PodNetwork) error { + return nil +} + +func (noop *cniNoOp) TearDownPod(network PodNetwork) error { + return nil +} + +func (noop *cniNoOp) GetPodNetworkStatus(network PodNetwork) (string, error) { + return "", nil +} + +func (noop *cniNoOp) Status() error { + return nil +} diff --git a/vendor/github.com/cri-o/ocicni/pkg/ocicni/ocicni.go b/vendor/github.com/cri-o/ocicni/pkg/ocicni/ocicni.go new file mode 100644 index 00000000..03918bfa --- /dev/null +++ b/vendor/github.com/cri-o/ocicni/pkg/ocicni/ocicni.go @@ -0,0 +1,421 @@ +package ocicni + +import ( + "errors" + "fmt" + "os/exec" + "sort" + "strings" + "sync" + + "github.com/containernetworking/cni/libcni" + cnitypes "github.com/containernetworking/cni/pkg/types" + "github.com/fsnotify/fsnotify" + "github.com/sirupsen/logrus" +) + +type cniNetworkPlugin struct { + loNetwork *cniNetwork + + sync.RWMutex + defaultNetwork *cniNetwork + + nsenterPath string + pluginDir string + cniDirs []string + vendorCNIDirPrefix string + + monitorNetDirChan chan struct{} + + // The pod map provides synchronization for a given pod's network + // operations. Each pod's setup/teardown/status operations + // are synchronized against each other, but network operations of other + // pods can proceed in parallel. + podsLock sync.Mutex + pods map[string]*podLock +} + +type cniNetwork struct { + name string + NetworkConfig *libcni.NetworkConfigList + CNIConfig libcni.CNI +} + +var errMissingDefaultNetwork = errors.New("Missing CNI default network") + +type podLock struct { + // Count of in-flight operations for this pod; when this reaches zero + // the lock can be removed from the pod map + refcount uint + + // Lock to synchronize operations for this specific pod + mu sync.Mutex +} + +func buildFullPodName(podNetwork PodNetwork) string { + return podNetwork.Namespace + "_" + podNetwork.Name +} + +// Lock network operations for a specific pod. If that pod is not yet in +// the pod map, it will be added. The reference count for the pod will +// be increased. +func (plugin *cniNetworkPlugin) podLock(podNetwork PodNetwork) *sync.Mutex { + plugin.podsLock.Lock() + defer plugin.podsLock.Unlock() + + fullPodName := buildFullPodName(podNetwork) + lock, ok := plugin.pods[fullPodName] + if !ok { + lock = &podLock{} + plugin.pods[fullPodName] = lock + } + lock.refcount++ + return &lock.mu +} + +// Unlock network operations for a specific pod. The reference count for the +// pod will be decreased. If the reference count reaches zero, the pod will be +// removed from the pod map. +func (plugin *cniNetworkPlugin) podUnlock(podNetwork PodNetwork) { + plugin.podsLock.Lock() + defer plugin.podsLock.Unlock() + + fullPodName := buildFullPodName(podNetwork) + lock, ok := plugin.pods[fullPodName] + if !ok { + logrus.Warningf("Unbalanced pod lock unref for %s", fullPodName) + return + } else if lock.refcount == 0 { + // This should never ever happen, but handle it anyway + delete(plugin.pods, fullPodName) + logrus.Errorf("Pod lock for %s still in map with zero refcount", fullPodName) + return + } + lock.refcount-- + lock.mu.Unlock() + if lock.refcount == 0 { + delete(plugin.pods, fullPodName) + } +} + +func (plugin *cniNetworkPlugin) monitorNetDir() { + watcher, err := fsnotify.NewWatcher() + if err != nil { + logrus.Errorf("could not create new watcher %v", err) + return + } + defer watcher.Close() + + go func() { + for { + select { + case event := <-watcher.Events: + logrus.Debugf("CNI monitoring event %v", event) + if event.Op&fsnotify.Create != fsnotify.Create && + event.Op&fsnotify.Write != fsnotify.Write { + continue + } + + if err = plugin.syncNetworkConfig(); err == nil { + logrus.Infof("CNI asynchronous setting succeeded") + continue + } + + logrus.Errorf("CNI setting failed, continue monitoring: %v", err) + + case err := <-watcher.Errors: + logrus.Errorf("CNI monitoring error %v", err) + close(plugin.monitorNetDirChan) + return + } + } + }() + + if err = watcher.Add(plugin.pluginDir); err != nil { + logrus.Error(err) + return + } + + <-plugin.monitorNetDirChan +} + +// InitCNI takes the plugin directory and cni directories where the cni files should be searched for +// Returns a valid plugin object and any error +func InitCNI(pluginDir string, cniDirs ...string) (CNIPlugin, error) { + plugin := probeNetworkPluginsWithVendorCNIDirPrefix(pluginDir, cniDirs, "") + var err error + plugin.nsenterPath, err = exec.LookPath("nsenter") + if err != nil { + return nil, err + } + + // check if a default network exists, otherwise dump the CNI search and return a noop plugin + _, err = getDefaultCNINetwork(plugin.pluginDir, plugin.cniDirs, plugin.vendorCNIDirPrefix) + if err != nil { + if err != errMissingDefaultNetwork { + logrus.Warningf("Error in finding usable CNI plugin - %v", err) + // create a noop plugin instead + return &cniNoOp{}, nil + } + + // We do not have a default network, we start the monitoring thread. + go plugin.monitorNetDir() + } + + return plugin, nil +} + +func probeNetworkPluginsWithVendorCNIDirPrefix(pluginDir string, cniDirs []string, vendorCNIDirPrefix string) *cniNetworkPlugin { + plugin := &cniNetworkPlugin{ + defaultNetwork: nil, + loNetwork: getLoNetwork(cniDirs, vendorCNIDirPrefix), + pluginDir: pluginDir, + cniDirs: cniDirs, + vendorCNIDirPrefix: vendorCNIDirPrefix, + monitorNetDirChan: make(chan struct{}), + pods: make(map[string]*podLock), + } + + // sync NetworkConfig in best effort during probing. + if err := plugin.syncNetworkConfig(); err != nil { + logrus.Error(err) + } + return plugin +} + +func getDefaultCNINetwork(pluginDir string, cniDirs []string, vendorCNIDirPrefix string) (*cniNetwork, error) { + if pluginDir == "" { + pluginDir = DefaultNetDir + } + if len(cniDirs) == 0 { + cniDirs = []string{DefaultCNIDir} + } + + files, err := libcni.ConfFiles(pluginDir, []string{".conf", ".conflist", ".json"}) + switch { + case err != nil: + return nil, err + case len(files) == 0: + return nil, errMissingDefaultNetwork + } + + sort.Strings(files) + for _, confFile := range files { + var confList *libcni.NetworkConfigList + if strings.HasSuffix(confFile, ".conflist") { + confList, err = libcni.ConfListFromFile(confFile) + if err != nil { + logrus.Warningf("Error loading CNI config list file %s: %v", confFile, err) + continue + } + } else { + conf, err := libcni.ConfFromFile(confFile) + if err != nil { + logrus.Warningf("Error loading CNI config file %s: %v", confFile, err) + continue + } + if conf.Network.Type == "" { + logrus.Warningf("Error loading CNI config file %s: no 'type'; perhaps this is a .conflist?", confFile) + continue + } + confList, err = libcni.ConfListFromConf(conf) + if err != nil { + logrus.Warningf("Error converting CNI config file %s to list: %v", confFile, err) + continue + } + } + if len(confList.Plugins) == 0 { + logrus.Warningf("CNI config list %s has no networks, skipping", confFile) + continue + } + logrus.Infof("CNI network %s (type=%v) is used from %s", confList.Name, confList.Plugins[0].Network.Type, confFile) + // Search for vendor-specific plugins as well as default plugins in the CNI codebase. + vendorDir := vendorCNIDir(vendorCNIDirPrefix, confList.Plugins[0].Network.Type) + cninet := &libcni.CNIConfig{ + Path: append(cniDirs, vendorDir), + } + network := &cniNetwork{name: confList.Name, NetworkConfig: confList, CNIConfig: cninet} + return network, nil + } + return nil, fmt.Errorf("No valid networks found in %s", pluginDir) +} + +func vendorCNIDir(prefix, pluginType string) string { + return fmt.Sprintf(VendorCNIDirTemplate, prefix, pluginType) +} + +func getLoNetwork(cniDirs []string, vendorDirPrefix string) *cniNetwork { + if len(cniDirs) == 0 { + cniDirs = []string{DefaultCNIDir} + } + + loConfig, err := libcni.ConfListFromBytes([]byte(`{ + "cniVersion": "0.2.0", + "name": "cni-loopback", + "plugins": [{ + "type": "loopback" + }] +}`)) + if err != nil { + // The hardcoded config above should always be valid and unit tests will + // catch this + panic(err) + } + vendorDir := vendorCNIDir(vendorDirPrefix, loConfig.Plugins[0].Network.Type) + cninet := &libcni.CNIConfig{ + Path: append(cniDirs, vendorDir), + } + loNetwork := &cniNetwork{ + name: "lo", + NetworkConfig: loConfig, + CNIConfig: cninet, + } + + return loNetwork +} + +func (plugin *cniNetworkPlugin) syncNetworkConfig() error { + network, err := getDefaultCNINetwork(plugin.pluginDir, plugin.cniDirs, plugin.vendorCNIDirPrefix) + if err != nil { + logrus.Errorf("error updating cni config: %s", err) + return err + } + plugin.setDefaultNetwork(network) + + return nil +} + +func (plugin *cniNetworkPlugin) getDefaultNetwork() *cniNetwork { + plugin.RLock() + defer plugin.RUnlock() + return plugin.defaultNetwork +} + +func (plugin *cniNetworkPlugin) setDefaultNetwork(n *cniNetwork) { + plugin.Lock() + defer plugin.Unlock() + plugin.defaultNetwork = n +} + +func (plugin *cniNetworkPlugin) checkInitialized() error { + if plugin.getDefaultNetwork() == nil { + return errors.New("cni config uninitialized") + } + return nil +} + +func (plugin *cniNetworkPlugin) Name() string { + return CNIPluginName +} + +func (plugin *cniNetworkPlugin) SetUpPod(podNetwork PodNetwork) error { + if err := plugin.checkInitialized(); err != nil { + return err + } + + plugin.podLock(podNetwork).Lock() + defer plugin.podUnlock(podNetwork) + + _, err := plugin.loNetwork.addToNetwork(podNetwork) + if err != nil { + logrus.Errorf("Error while adding to cni lo network: %s", err) + return err + } + + _, err = plugin.getDefaultNetwork().addToNetwork(podNetwork) + if err != nil { + logrus.Errorf("Error while adding to cni network: %s", err) + return err + } + + return err +} + +func (plugin *cniNetworkPlugin) TearDownPod(podNetwork PodNetwork) error { + if err := plugin.checkInitialized(); err != nil { + return err + } + + plugin.podLock(podNetwork).Lock() + defer plugin.podUnlock(podNetwork) + + return plugin.getDefaultNetwork().deleteFromNetwork(podNetwork) +} + +// TODO: Use the addToNetwork function to obtain the IP of the Pod. That will assume idempotent ADD call to the plugin. +// Also fix the runtime's call to Status function to be done only in the case that the IP is lost, no need to do periodic calls +func (plugin *cniNetworkPlugin) GetPodNetworkStatus(podNetwork PodNetwork) (string, error) { + plugin.podLock(podNetwork).Lock() + defer plugin.podUnlock(podNetwork) + + ip, err := getContainerIP(plugin.nsenterPath, podNetwork.NetNS, DefaultInterfaceName, "-4") + if err != nil { + return "", err + } + + return ip.String(), nil +} + +func (network *cniNetwork) addToNetwork(podNetwork PodNetwork) (cnitypes.Result, error) { + rt, err := buildCNIRuntimeConf(podNetwork) + if err != nil { + logrus.Errorf("Error adding network: %v", err) + return nil, err + } + + netconf, cninet := network.NetworkConfig, network.CNIConfig + logrus.Infof("About to add CNI network %s (type=%v)", netconf.Name, netconf.Plugins[0].Network.Type) + res, err := cninet.AddNetworkList(netconf, rt) + if err != nil { + logrus.Errorf("Error adding network: %v", err) + return nil, err + } + + return res, nil +} + +func (network *cniNetwork) deleteFromNetwork(podNetwork PodNetwork) error { + rt, err := buildCNIRuntimeConf(podNetwork) + if err != nil { + logrus.Errorf("Error deleting network: %v", err) + return err + } + + netconf, cninet := network.NetworkConfig, network.CNIConfig + logrus.Infof("About to del CNI network %s (type=%v)", netconf.Name, netconf.Plugins[0].Network.Type) + err = cninet.DelNetworkList(netconf, rt) + if err != nil { + logrus.Errorf("Error deleting network: %v", err) + return err + } + return nil +} + +func buildCNIRuntimeConf(podNetwork PodNetwork) (*libcni.RuntimeConf, error) { + logrus.Infof("Got pod network %+v", podNetwork) + + rt := &libcni.RuntimeConf{ + ContainerID: podNetwork.ID, + NetNS: podNetwork.NetNS, + IfName: DefaultInterfaceName, + Args: [][2]string{ + {"IgnoreUnknown", "1"}, + {"K8S_POD_NAMESPACE", podNetwork.Namespace}, + {"K8S_POD_NAME", podNetwork.Name}, + {"K8S_POD_INFRA_CONTAINER_ID", podNetwork.ID}, + }, + } + + if len(podNetwork.PortMappings) == 0 { + return rt, nil + } + + rt.CapabilityArgs = map[string]interface{}{ + "portMappings": podNetwork.PortMappings, + } + return rt, nil +} + +func (plugin *cniNetworkPlugin) Status() error { + return plugin.checkInitialized() +} diff --git a/vendor/github.com/cri-o/ocicni/pkg/ocicni/types.go b/vendor/github.com/cri-o/ocicni/pkg/ocicni/types.go new file mode 100644 index 00000000..a272e92e --- /dev/null +++ b/vendor/github.com/cri-o/ocicni/pkg/ocicni/types.go @@ -0,0 +1,62 @@ +package ocicni + +const ( + // DefaultInterfaceName is the string to be used for the interface name inside the net namespace + DefaultInterfaceName = "eth0" + // CNIPluginName is the default name of the plugin + CNIPluginName = "cni" + // DefaultNetDir is the place to look for CNI Network + DefaultNetDir = "/etc/cni/net.d" + // DefaultCNIDir is the place to look for cni config files + DefaultCNIDir = "/opt/cni/bin" + // VendorCNIDirTemplate is the template for looking up vendor specific cni config/executable files + VendorCNIDirTemplate = "%s/opt/%s/bin" +) + +// PortMapping maps to the standard CNI portmapping Capability +// see: https://github.com/containernetworking/cni/blob/master/CONVENTIONS.md +type PortMapping struct { + // HostPort is the port number on the host. + HostPort int32 `json:"hostPort"` + // ContainerPort is the port number inside the sandbox. + ContainerPort int32 `json:"containerPort"` + // Protocol is the protocol of the port mapping. + Protocol string `json:"protocol"` + // HostIP is the host ip to use. + HostIP string `json:"hostIP"` +} + +// PodNetwork configures the network of a pod sandbox. +type PodNetwork struct { + // Name is the name of the sandbox. + Name string + // Namespace is the namespace of the sandbox. + Namespace string + // ID is the id of the sandbox container. + ID string + // NetNS is the network namespace path of the sandbox. + NetNS string + // PortMappings is the port mapping of the sandbox. + PortMappings []PortMapping +} + +// CNIPlugin is the interface that needs to be implemented by a plugin +type CNIPlugin interface { + // Name returns the plugin's name. This will be used when searching + // for a plugin by name, e.g. + Name() string + + // SetUpPod is the method called after the sandbox container of + // the pod has been created but before the other containers of the + // pod are launched. + SetUpPod(network PodNetwork) error + + // TearDownPod is the method called before a pod's sandbox container will be deleted + TearDownPod(network PodNetwork) error + + // Status is the method called to obtain the ipv4 or ipv6 addresses of the pod sandbox + GetPodNetworkStatus(network PodNetwork) (string, error) + + // NetworkStatus returns error if the network plugin is in error state + Status() error +} diff --git a/vendor/github.com/cri-o/ocicni/pkg/ocicni/util.go b/vendor/github.com/cri-o/ocicni/pkg/ocicni/util.go new file mode 100644 index 00000000..547e9597 --- /dev/null +++ b/vendor/github.com/cri-o/ocicni/pkg/ocicni/util.go @@ -0,0 +1,32 @@ +package ocicni + +import ( + "fmt" + "net" + "os/exec" + "strings" +) + +func getContainerIP(nsenterPath, netnsPath, interfaceName, addrType string) (net.IP, error) { + // Try to retrieve ip inside container network namespace + output, err := exec.Command(nsenterPath, fmt.Sprintf("--net=%s", netnsPath), "-F", "--", + "ip", "-o", addrType, "addr", "show", "dev", interfaceName, "scope", "global").CombinedOutput() + if err != nil { + return nil, fmt.Errorf("Unexpected command output %s with error: %v", output, err) + } + + lines := strings.Split(string(output), "\n") + if len(lines) < 1 { + return nil, fmt.Errorf("Unexpected command output %s", output) + } + fields := strings.Fields(lines[0]) + if len(fields) < 4 { + return nil, fmt.Errorf("Unexpected address output %s ", lines[0]) + } + ip, _, err := net.ParseCIDR(fields[3]) + if err != nil { + return nil, fmt.Errorf("CNI failed to parse ip from output %s due to %v", output, err) + } + + return ip, nil +} From 0df30c5319034e50cd3c7fe86e0ffa7cc28bfa79 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Tue, 5 Sep 2017 15:10:42 -0500 Subject: [PATCH 3/3] server: port to github.com/cri-o/ocicni; remove pkg/ocicni Signed-off-by: Dan Williams --- libkpod/sandbox/sandbox.go | 2 +- oci/container.go | 2 +- pkg/ocicni/LICENSE | 191 ---------------------- pkg/ocicni/README.md | 3 - pkg/ocicni/noop.go | 24 --- pkg/ocicni/ocicni.go | 319 ------------------------------------- pkg/ocicni/types.go | 35 ---- pkg/ocicni/util.go | 32 ---- server/sandbox_run.go | 15 +- server/sandbox_stop.go | 11 +- server/server.go | 2 +- server/utils.go | 11 ++ 12 files changed, 28 insertions(+), 619 deletions(-) delete mode 100644 pkg/ocicni/LICENSE delete mode 100644 pkg/ocicni/README.md delete mode 100644 pkg/ocicni/noop.go delete mode 100644 pkg/ocicni/ocicni.go delete mode 100644 pkg/ocicni/types.go delete mode 100644 pkg/ocicni/util.go diff --git a/libkpod/sandbox/sandbox.go b/libkpod/sandbox/sandbox.go index 62550762..65875bb7 100644 --- a/libkpod/sandbox/sandbox.go +++ b/libkpod/sandbox/sandbox.go @@ -8,7 +8,7 @@ import ( "path/filepath" "sync" - "github.com/containernetworking/cni/pkg/ns" + "github.com/containernetworking/plugins/pkg/ns" "github.com/docker/docker/pkg/mount" "github.com/docker/docker/pkg/symlink" "github.com/kubernetes-incubator/cri-o/oci" diff --git a/oci/container.go b/oci/container.go index 09ee0aa1..433e4c8c 100644 --- a/oci/container.go +++ b/oci/container.go @@ -9,7 +9,7 @@ import ( "sync" "time" - "github.com/containernetworking/cni/pkg/ns" + "github.com/containernetworking/plugins/pkg/ns" "github.com/docker/docker/pkg/signal" specs "github.com/opencontainers/runtime-spec/specs-go" "k8s.io/apimachinery/pkg/fields" diff --git a/pkg/ocicni/LICENSE b/pkg/ocicni/LICENSE deleted file mode 100644 index 3fd70307..00000000 --- a/pkg/ocicni/LICENSE +++ /dev/null @@ -1,191 +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 - - Copyright 2016 Red Hat, Inc. - - 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. diff --git a/pkg/ocicni/README.md b/pkg/ocicni/README.md deleted file mode 100644 index 99c103c8..00000000 --- a/pkg/ocicni/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# ocicni - -API layer to call the CNI plugins from an OCI lifecycle daemon diff --git a/pkg/ocicni/noop.go b/pkg/ocicni/noop.go deleted file mode 100644 index 95447438..00000000 --- a/pkg/ocicni/noop.go +++ /dev/null @@ -1,24 +0,0 @@ -package ocicni - -type cniNoOp struct { -} - -func (noop *cniNoOp) Name() string { - return "CNINoOp" -} - -func (noop *cniNoOp) SetUpPod(netnsPath string, namespace string, name string, containerID string) error { - return nil -} - -func (noop *cniNoOp) TearDownPod(netnsPath string, namespace string, name string, containerID string) error { - return nil -} - -func (noop *cniNoOp) GetContainerNetworkStatus(netnsPath string, namespace string, name string, containerID string) (string, error) { - return "", nil -} - -func (noop *cniNoOp) Status() error { - return nil -} diff --git a/pkg/ocicni/ocicni.go b/pkg/ocicni/ocicni.go deleted file mode 100644 index ed80498e..00000000 --- a/pkg/ocicni/ocicni.go +++ /dev/null @@ -1,319 +0,0 @@ -package ocicni - -import ( - "errors" - "fmt" - "os/exec" - "sort" - "sync" - - "github.com/containernetworking/cni/libcni" - cnitypes "github.com/containernetworking/cni/pkg/types" - "github.com/fsnotify/fsnotify" - "github.com/sirupsen/logrus" -) - -type cniNetworkPlugin struct { - loNetwork *cniNetwork - - sync.RWMutex - defaultNetwork *cniNetwork - - nsenterPath string - pluginDir string - cniDirs []string - vendorCNIDirPrefix string - - monitorNetDirChan chan struct{} -} - -type cniNetwork struct { - name string - NetworkConfig *libcni.NetworkConfig - CNIConfig libcni.CNI -} - -var errMissingDefaultNetwork = errors.New("Missing CNI default network") - -func (plugin *cniNetworkPlugin) monitorNetDir() { - watcher, err := fsnotify.NewWatcher() - if err != nil { - logrus.Errorf("could not create new watcher %v", err) - return - } - defer watcher.Close() - - go func() { - for { - select { - case event := <-watcher.Events: - logrus.Debugf("CNI monitoring event %v", event) - if event.Op&fsnotify.Create != fsnotify.Create && - event.Op&fsnotify.Write != fsnotify.Write { - continue - } - - if err = plugin.syncNetworkConfig(); err == nil { - logrus.Infof("CNI asynchronous setting succeeded") - continue - } - - logrus.Errorf("CNI setting failed, continue monitoring: %v", err) - - case err := <-watcher.Errors: - logrus.Errorf("CNI monitoring error %v", err) - close(plugin.monitorNetDirChan) - return - } - } - }() - - if err = watcher.Add(plugin.pluginDir); err != nil { - logrus.Error(err) - return - } - - <-plugin.monitorNetDirChan -} - -// InitCNI takes the plugin directory and cni directories where the cni files should be searched for -// Returns a valid plugin object and any error -func InitCNI(pluginDir string, cniDirs ...string) (CNIPlugin, error) { - plugin := probeNetworkPluginsWithVendorCNIDirPrefix(pluginDir, cniDirs, "") - var err error - plugin.nsenterPath, err = exec.LookPath("nsenter") - if err != nil { - return nil, err - } - - // check if a default network exists, otherwise dump the CNI search and return a noop plugin - _, err = getDefaultCNINetwork(plugin.pluginDir, plugin.cniDirs, plugin.vendorCNIDirPrefix) - if err != nil { - if err != errMissingDefaultNetwork { - logrus.Warningf("Error in finding usable CNI plugin - %v", err) - // create a noop plugin instead - return &cniNoOp{}, nil - } - - // We do not have a default network, we start the monitoring thread. - go plugin.monitorNetDir() - } - - return plugin, nil -} - -func probeNetworkPluginsWithVendorCNIDirPrefix(pluginDir string, cniDirs []string, vendorCNIDirPrefix string) *cniNetworkPlugin { - plugin := &cniNetworkPlugin{ - defaultNetwork: nil, - loNetwork: getLoNetwork(cniDirs, vendorCNIDirPrefix), - pluginDir: pluginDir, - cniDirs: cniDirs, - vendorCNIDirPrefix: vendorCNIDirPrefix, - monitorNetDirChan: make(chan struct{}), - } - - // sync NetworkConfig in best effort during probing. - if err := plugin.syncNetworkConfig(); err != nil { - logrus.Error(err) - } - return plugin -} - -func getDefaultCNINetwork(pluginDir string, cniDirs []string, vendorCNIDirPrefix string) (*cniNetwork, error) { - if pluginDir == "" { - pluginDir = DefaultNetDir - } - if len(cniDirs) == 0 { - cniDirs = []string{DefaultCNIDir} - } - - files, err := libcni.ConfFiles(pluginDir) - switch { - case err != nil: - return nil, err - case len(files) == 0: - return nil, errMissingDefaultNetwork - } - - sort.Strings(files) - for _, confFile := range files { - conf, err := libcni.ConfFromFile(confFile) - if err != nil { - logrus.Warningf("Error loading CNI config file %s: %v", confFile, err) - continue - } - - // Search for vendor-specific plugins as well as default plugins in the CNI codebase. - vendorDir := vendorCNIDir(vendorCNIDirPrefix, conf.Network.Type) - cninet := &libcni.CNIConfig{ - Path: append(cniDirs, vendorDir), - } - - network := &cniNetwork{name: conf.Network.Name, NetworkConfig: conf, CNIConfig: cninet} - return network, nil - } - return nil, fmt.Errorf("No valid networks found in %s", pluginDir) -} - -func vendorCNIDir(prefix, pluginType string) string { - return fmt.Sprintf(VendorCNIDirTemplate, prefix, pluginType) -} - -func getLoNetwork(cniDirs []string, vendorDirPrefix string) *cniNetwork { - if len(cniDirs) == 0 { - cniDirs = []string{DefaultCNIDir} - } - - loConfig, err := libcni.ConfFromBytes([]byte(`{ - "cniVersion": "0.1.0", - "name": "cni-loopback", - "type": "loopback" -}`)) - if err != nil { - // The hardcoded config above should always be valid and unit tests will - // catch this - panic(err) - } - vendorDir := vendorCNIDir(vendorDirPrefix, loConfig.Network.Type) - cninet := &libcni.CNIConfig{ - Path: append(cniDirs, vendorDir), - } - loNetwork := &cniNetwork{ - name: "lo", - NetworkConfig: loConfig, - CNIConfig: cninet, - } - - return loNetwork -} - -func (plugin *cniNetworkPlugin) syncNetworkConfig() error { - network, err := getDefaultCNINetwork(plugin.pluginDir, plugin.cniDirs, plugin.vendorCNIDirPrefix) - if err != nil { - logrus.Errorf("error updating cni config: %s", err) - return err - } - plugin.setDefaultNetwork(network) - - return nil -} - -func (plugin *cniNetworkPlugin) getDefaultNetwork() *cniNetwork { - plugin.RLock() - defer plugin.RUnlock() - return plugin.defaultNetwork -} - -func (plugin *cniNetworkPlugin) setDefaultNetwork(n *cniNetwork) { - plugin.Lock() - defer plugin.Unlock() - plugin.defaultNetwork = n -} - -func (plugin *cniNetworkPlugin) checkInitialized() error { - if plugin.getDefaultNetwork() == nil { - return errors.New("cni config uninitialized") - } - return nil -} - -func (plugin *cniNetworkPlugin) Name() string { - return CNIPluginName -} - -func (plugin *cniNetworkPlugin) SetUpPod(netnsPath string, namespace string, name string, id string) error { - if err := plugin.checkInitialized(); err != nil { - return err - } - - _, err := plugin.loNetwork.addToNetwork(name, namespace, id, netnsPath) - if err != nil { - logrus.Errorf("Error while adding to cni lo network: %s", err) - return err - } - - _, err = plugin.getDefaultNetwork().addToNetwork(name, namespace, id, netnsPath) - if err != nil { - logrus.Errorf("Error while adding to cni network: %s", err) - return err - } - - return err -} - -func (plugin *cniNetworkPlugin) TearDownPod(netnsPath string, namespace string, name string, id string) error { - if err := plugin.checkInitialized(); err != nil { - return err - } - - return plugin.getDefaultNetwork().deleteFromNetwork(name, namespace, id, netnsPath) -} - -// TODO: Use the addToNetwork function to obtain the IP of the Pod. That will assume idempotent ADD call to the plugin. -// Also fix the runtime's call to Status function to be done only in the case that the IP is lost, no need to do periodic calls -func (plugin *cniNetworkPlugin) GetContainerNetworkStatus(netnsPath string, namespace string, name string, id string) (string, error) { - ip, err := getContainerIP(plugin.nsenterPath, netnsPath, DefaultInterfaceName, "-4") - if err != nil { - return "", err - } - - return ip.String(), nil -} - -func (network *cniNetwork) addToNetwork(podName string, podNamespace string, podInfraContainerID string, podNetnsPath string) (*cnitypes.Result, error) { - rt, err := buildCNIRuntimeConf(podName, podNamespace, podInfraContainerID, podNetnsPath) - if err != nil { - logrus.Errorf("Error adding network: %v", err) - return nil, err - } - - netconf, cninet := network.NetworkConfig, network.CNIConfig - logrus.Infof("About to run with conf.Network.Type=%v", netconf.Network.Type) - res, err := cninet.AddNetwork(netconf, rt) - if err != nil { - logrus.Errorf("Error adding network: %v", err) - return nil, err - } - - return res, nil -} - -func (network *cniNetwork) deleteFromNetwork(podName string, podNamespace string, podInfraContainerID string, podNetnsPath string) error { - rt, err := buildCNIRuntimeConf(podName, podNamespace, podInfraContainerID, podNetnsPath) - if err != nil { - logrus.Errorf("Error deleting network: %v", err) - return err - } - - netconf, cninet := network.NetworkConfig, network.CNIConfig - logrus.Infof("About to run with conf.Network.Type=%v", netconf.Network.Type) - err = cninet.DelNetwork(netconf, rt) - if err != nil { - logrus.Errorf("Error deleting network: %v", err) - return err - } - return nil -} - -func buildCNIRuntimeConf(podName string, podNs string, podInfraContainerID string, podNetnsPath string) (*libcni.RuntimeConf, error) { - logrus.Infof("Got netns path %v", podNetnsPath) - logrus.Infof("Using netns path %v", podNs) - - rt := &libcni.RuntimeConf{ - ContainerID: podInfraContainerID, - NetNS: podNetnsPath, - IfName: DefaultInterfaceName, - Args: [][2]string{ - {"IgnoreUnknown", "1"}, - {"K8S_POD_NAMESPACE", podNs}, - {"K8S_POD_NAME", podName}, - {"K8S_POD_INFRA_CONTAINER_ID", podInfraContainerID}, - }, - } - - return rt, nil -} - -func (plugin *cniNetworkPlugin) Status() error { - return plugin.checkInitialized() -} diff --git a/pkg/ocicni/types.go b/pkg/ocicni/types.go deleted file mode 100644 index 4483a203..00000000 --- a/pkg/ocicni/types.go +++ /dev/null @@ -1,35 +0,0 @@ -package ocicni - -const ( - // DefaultInterfaceName is the string to be used for the interface name inside the net namespace - DefaultInterfaceName = "eth0" - // CNIPluginName is the default name of the plugin - CNIPluginName = "cni" - // DefaultNetDir is the place to look for CNI Network - DefaultNetDir = "/etc/cni/net.d" - // DefaultCNIDir is the place to look for cni config files - DefaultCNIDir = "/opt/cni/bin" - // VendorCNIDirTemplate is the template for looking up vendor specific cni config/executable files - VendorCNIDirTemplate = "%s/opt/%s/bin" -) - -// CNIPlugin is the interface that needs to be implemented by a plugin -type CNIPlugin interface { - // Name returns the plugin's name. This will be used when searching - // for a plugin by name, e.g. - Name() string - - // SetUpPod is the method called after the infra container of - // the pod has been created but before the other containers of the - // pod are launched. - SetUpPod(netnsPath string, namespace string, name string, containerID string) error - - // TearDownPod is the method called before a pod's infra container will be deleted - TearDownPod(netnsPath string, namespace string, name string, containerID string) error - - // Status is the method called to obtain the ipv4 or ipv6 addresses of the container - GetContainerNetworkStatus(netnsPath string, namespace string, name string, containerID string) (string, error) - - // NetworkStatus returns error if the network plugin is in error state - Status() error -} diff --git a/pkg/ocicni/util.go b/pkg/ocicni/util.go deleted file mode 100644 index 547e9597..00000000 --- a/pkg/ocicni/util.go +++ /dev/null @@ -1,32 +0,0 @@ -package ocicni - -import ( - "fmt" - "net" - "os/exec" - "strings" -) - -func getContainerIP(nsenterPath, netnsPath, interfaceName, addrType string) (net.IP, error) { - // Try to retrieve ip inside container network namespace - output, err := exec.Command(nsenterPath, fmt.Sprintf("--net=%s", netnsPath), "-F", "--", - "ip", "-o", addrType, "addr", "show", "dev", interfaceName, "scope", "global").CombinedOutput() - if err != nil { - return nil, fmt.Errorf("Unexpected command output %s with error: %v", output, err) - } - - lines := strings.Split(string(output), "\n") - if len(lines) < 1 { - return nil, fmt.Errorf("Unexpected command output %s", output) - } - fields := strings.Fields(lines[0]) - if len(fields) < 4 { - return nil, fmt.Errorf("Unexpected address output %s ", lines[0]) - } - ip, _, err := net.ParseCIDR(fields[3]) - if err != nil { - return nil, fmt.Errorf("CNI failed to parse ip from output %s due to %v", output, err) - } - - return ip, nil -} diff --git a/server/sandbox_run.go b/server/sandbox_run.go index ac9823c8..a1b87755 100644 --- a/server/sandbox_run.go +++ b/server/sandbox_run.go @@ -464,27 +464,28 @@ func (s *Server) RunPodSandbox(ctx context.Context, req *pb.RunPodSandboxRequest var ip string // setup the network if !hostNetwork { - if err = s.netPlugin.SetUpPod(netNsPath, namespace, kubeName, id); err != nil { - return nil, fmt.Errorf("failed to create network for container %s in sandbox %s: %v", containerName, id, err) + podNetwork := newPodNetwork(sb.Namespace(), sb.KubeName(), sb.ID(), netNsPath) + if err = s.netPlugin.SetUpPod(podNetwork); err != nil { + return nil, fmt.Errorf("failed to create pod network sandbox %s(%s): %v", sb.Name(), id, err) } - if ip, err = s.netPlugin.GetContainerNetworkStatus(netNsPath, namespace, id, kubeName); err != nil { - return nil, fmt.Errorf("failed to get network status for container %s in sandbox %s: %v", containerName, id, err) + if ip, err = s.netPlugin.GetPodNetworkStatus(podNetwork); err != nil { + return nil, fmt.Errorf("failed to get network status for pod sandbox %s(%s): %v", sb.Name(), id, err) } if len(portMappings) != 0 { ip4 := net.ParseIP(ip).To4() if ip4 == nil { - return nil, fmt.Errorf("failed to get valid ipv4 address for container %s in sandbox %s", containerName, id) + return nil, fmt.Errorf("failed to get valid ipv4 address for sandbox %s(%s)", sb.Name(), id) } if err = s.hostportManager.Add(id, &hostport.PodPortMapping{ - Name: name, + Name: sb.Name(), PortMappings: portMappings, IP: ip4, HostNetwork: false, }, "lo"); err != nil { - return nil, fmt.Errorf("failed to add hostport mapping for container %s in sandbox %s: %v", containerName, id, err) + return nil, fmt.Errorf("failed to add hostport mapping for sandbox %s(%s): %v", sb.Name(), id, err) } } diff --git a/server/sandbox_stop.go b/server/sandbox_stop.go index bdd85a24..1f422db5 100644 --- a/server/sandbox_stop.go +++ b/server/sandbox_stop.go @@ -55,16 +55,17 @@ func (s *Server) StopPodSandbox(ctx context.Context, req *pb.StopPodSandboxReque PortMappings: sb.PortMappings(), HostNetwork: false, }); err2 != nil { - logrus.Warnf("failed to remove hostport for container %s in sandbox %s: %v", + logrus.Warnf("failed to remove hostport for pod sandbox %s(%s): %v", podInfraContainer.Name(), sb.ID(), err2) } - if err2 := s.netPlugin.TearDownPod(netnsPath, sb.Namespace(), sb.KubeName(), sb.ID()); err2 != nil { - logrus.Warnf("failed to destroy network for container %s in sandbox %s: %v", - podInfraContainer.Name(), sb.ID(), err2) + podNetwork := newPodNetwork(sb.Namespace(), sb.KubeName(), sb.ID(), netnsPath) + if err2 := s.netPlugin.TearDownPod(podNetwork); err2 != nil { + logrus.Warnf("failed to destroy network for pod sandbox %s(%s): %v", + sb.Name(), sb.ID(), err2) } } else if !os.IsNotExist(err) { // it's ok for netnsPath to *not* exist - return nil, fmt.Errorf("failed to stat netns path for container %s in sandbox %s before tearing down the network: %v", + return nil, fmt.Errorf("failed to stat netns path for pod sandbox %s(%s) before tearing down the network: %v", sb.Name(), sb.ID(), err) } diff --git a/server/server.go b/server/server.go index 63016ab6..67613d4c 100644 --- a/server/server.go +++ b/server/server.go @@ -13,11 +13,11 @@ import ( "strings" "sync" + "github.com/cri-o/ocicni/pkg/ocicni" "github.com/fsnotify/fsnotify" "github.com/kubernetes-incubator/cri-o/libkpod" "github.com/kubernetes-incubator/cri-o/libkpod/sandbox" "github.com/kubernetes-incubator/cri-o/oci" - "github.com/kubernetes-incubator/cri-o/pkg/ocicni" "github.com/kubernetes-incubator/cri-o/pkg/storage" "github.com/kubernetes-incubator/cri-o/server/apparmor" "github.com/kubernetes-incubator/cri-o/server/seccomp" diff --git a/server/utils.go b/server/utils.go index a8245e1a..39aa96a5 100644 --- a/server/utils.go +++ b/server/utils.go @@ -5,6 +5,8 @@ import ( "io" "os" "strings" + + "github.com/cri-o/ocicni/pkg/ocicni" ) const ( @@ -144,3 +146,12 @@ func SysctlsFromPodAnnotation(annotation string) ([]Sysctl, error) { } return sysctls, nil } + +func newPodNetwork(namespace, name, id, netns string) ocicni.PodNetwork { + return ocicni.PodNetwork{ + Name: name, + Namespace: namespace, + ID: id, + NetNS: netns, + } +}