From bc7a6f2556c476ff1af927aac72c369ae73b5a2f Mon Sep 17 00:00:00 2001 From: Tonis Tiigi <tonistiigi@gmail.com> Date: Wed, 10 Jul 2019 14:42:30 -0700 Subject: [PATCH 1/6] add cni networking support Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com> --- cmd/buildkitd/config/config.go | 8 + cmd/buildkitd/main.go | 16 + cmd/buildkitd/main_containerd_worker.go | 35 +- cmd/buildkitd/main_oci_worker.go | 34 +- cmd/buildkitd/main_unix.go | 4 + go.mod | 2 + go.sum | 12 + hack/dockerfiles/test.buildkit.Dockerfile | 15 +- util/network/cni.go | 91 ++++++ util/network/netns_create/hook.go | 51 +++ util/network/network.go | 42 ++- .../github.com/containerd/go-cni/.travis.yml | 22 ++ vendor/github.com/containerd/go-cni/LICENSE | 201 ++++++++++++ vendor/github.com/containerd/go-cni/README.md | 60 ++++ vendor/github.com/containerd/go-cni/cni.go | 219 +++++++++++++ vendor/github.com/containerd/go-cni/errors.go | 55 ++++ vendor/github.com/containerd/go-cni/helper.go | 41 +++ .../github.com/containerd/go-cni/namespace.go | 75 +++++ .../containerd/go-cni/namespace_opts.go | 67 ++++ vendor/github.com/containerd/go-cni/opts.go | 245 ++++++++++++++ vendor/github.com/containerd/go-cni/result.go | 106 +++++++ .../github.com/containerd/go-cni/testutils.go | 78 +++++ vendor/github.com/containerd/go-cni/types.go | 55 ++++ .../github.com/containerd/go-cni/vendor.conf | 6 + .../containernetworking/cni/LICENSE | 202 ++++++++++++ .../containernetworking/cni/libcni/api.go | 219 +++++++++++++ .../containernetworking/cni/libcni/conf.go | 259 +++++++++++++++ .../cni/pkg/invoke/args.go | 82 +++++ .../cni/pkg/invoke/delegate.go | 53 ++++ .../cni/pkg/invoke/exec.go | 95 ++++++ .../cni/pkg/invoke/find.go | 43 +++ .../cni/pkg/invoke/os_unix.go | 20 ++ .../cni/pkg/invoke/os_windows.go | 18 ++ .../cni/pkg/invoke/raw_exec.go | 59 ++++ .../cni/pkg/types/020/types.go | 135 ++++++++ .../containernetworking/cni/pkg/types/args.go | 112 +++++++ .../cni/pkg/types/current/types.go | 300 ++++++++++++++++++ .../cni/pkg/types/types.go | 191 +++++++++++ .../cni/pkg/version/conf.go | 37 +++ .../cni/pkg/version/plugin.go | 81 +++++ .../cni/pkg/version/reconcile.go | 49 +++ .../cni/pkg/version/version.go | 61 ++++ vendor/modules.txt | 9 + worker/containerd/containerd.go | 13 +- worker/runc/runc.go | 10 +- 45 files changed, 3574 insertions(+), 14 deletions(-) create mode 100644 util/network/cni.go create mode 100644 util/network/netns_create/hook.go create mode 100644 vendor/github.com/containerd/go-cni/.travis.yml create mode 100644 vendor/github.com/containerd/go-cni/LICENSE create mode 100644 vendor/github.com/containerd/go-cni/README.md create mode 100644 vendor/github.com/containerd/go-cni/cni.go create mode 100644 vendor/github.com/containerd/go-cni/errors.go create mode 100644 vendor/github.com/containerd/go-cni/helper.go create mode 100644 vendor/github.com/containerd/go-cni/namespace.go create mode 100644 vendor/github.com/containerd/go-cni/namespace_opts.go create mode 100644 vendor/github.com/containerd/go-cni/opts.go create mode 100644 vendor/github.com/containerd/go-cni/result.go create mode 100644 vendor/github.com/containerd/go-cni/testutils.go create mode 100644 vendor/github.com/containerd/go-cni/types.go create mode 100644 vendor/github.com/containerd/go-cni/vendor.conf create mode 100644 vendor/github.com/containernetworking/cni/LICENSE create mode 100644 vendor/github.com/containernetworking/cni/libcni/api.go create mode 100644 vendor/github.com/containernetworking/cni/libcni/conf.go create mode 100644 vendor/github.com/containernetworking/cni/pkg/invoke/args.go create mode 100644 vendor/github.com/containernetworking/cni/pkg/invoke/delegate.go create mode 100644 vendor/github.com/containernetworking/cni/pkg/invoke/exec.go create mode 100644 vendor/github.com/containernetworking/cni/pkg/invoke/find.go 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/invoke/raw_exec.go create mode 100644 vendor/github.com/containernetworking/cni/pkg/types/020/types.go create mode 100644 vendor/github.com/containernetworking/cni/pkg/types/args.go create mode 100644 vendor/github.com/containernetworking/cni/pkg/types/current/types.go create mode 100644 vendor/github.com/containernetworking/cni/pkg/types/types.go create mode 100644 vendor/github.com/containernetworking/cni/pkg/version/conf.go create mode 100644 vendor/github.com/containernetworking/cni/pkg/version/plugin.go create mode 100644 vendor/github.com/containernetworking/cni/pkg/version/reconcile.go create mode 100644 vendor/github.com/containernetworking/cni/pkg/version/version.go diff --git a/cmd/buildkitd/config/config.go b/cmd/buildkitd/config/config.go index bd568574326e..0c9df323c153 100644 --- a/cmd/buildkitd/config/config.go +++ b/cmd/buildkitd/config/config.go @@ -58,6 +58,12 @@ type GCConfig struct { GCPolicy []GCPolicy `toml:"gcpolicy"` } +type NetworkConfig struct { + Mode string `toml:"networkMode"` + CNIConfigPath string `toml:"cniConfigPath"` + CNIBinaryPath string `toml:"cniBinaryPath"` +} + type OCIConfig struct { Enabled *bool `toml:"enabled"` Labels map[string]string `toml:"labels"` @@ -66,6 +72,7 @@ type OCIConfig struct { Rootless bool `toml:"rootless"` NoProcessSandbox bool `toml:"noProcessSandbox"` GCConfig + NetworkConfig // UserRemapUnsupported is unsupported key for testing. The feature is // incomplete and the intention is to make it default without config. UserRemapUnsupported string `toml:"userRemapUnsupported"` @@ -78,6 +85,7 @@ type ContainerdConfig struct { Platforms []string `toml:"platforms"` Namespace string `toml:"namespace"` GCConfig + NetworkConfig } type GCPolicy struct { diff --git a/cmd/buildkitd/main.go b/cmd/buildkitd/main.go index afcbc248c330..078d8467ece6 100644 --- a/cmd/buildkitd/main.go +++ b/cmd/buildkitd/main.go @@ -350,6 +350,19 @@ func defaultConf() (config.Config, *toml.MetaData, error) { return cfg, md, nil } +func setDefaultNetworkConfig(nc config.NetworkConfig) config.NetworkConfig { + if nc.Mode == "" { + nc.Mode = "auto" + } + if nc.CNIConfigPath == "" { + nc.CNIConfigPath = "/etc/buildkit/cni.json" + } + if nc.CNIBinaryPath == "" { + nc.CNIBinaryPath = "/opt/cni/bin" + } + return nc +} + func setDefaultConfig(cfg *config.Config) { orig := *cfg @@ -368,6 +381,9 @@ func setDefaultConfig(cfg *config.Config) { cfg.Workers.Containerd.Platforms = binfmt_misc.SupportedPlatforms() } + cfg.Workers.OCI.NetworkConfig = setDefaultNetworkConfig(cfg.Workers.OCI.NetworkConfig) + cfg.Workers.Containerd.NetworkConfig = setDefaultNetworkConfig(cfg.Workers.Containerd.NetworkConfig) + if system.RunningInUserNS() { // if buildkitd is being executed as the mapped-root (not only EUID==0 but also $USER==root) // in a user namespace, we need to enable the rootless mode but diff --git a/cmd/buildkitd/main_containerd_worker.go b/cmd/buildkitd/main_containerd_worker.go index e3e933831545..01a85eb44963 100644 --- a/cmd/buildkitd/main_containerd_worker.go +++ b/cmd/buildkitd/main_containerd_worker.go @@ -10,6 +10,7 @@ import ( ctd "github.com/containerd/containerd" "github.com/moby/buildkit/cmd/buildkitd/config" + "github.com/moby/buildkit/util/network" "github.com/moby/buildkit/worker" "github.com/moby/buildkit/worker/base" "github.com/moby/buildkit/worker/containerd" @@ -69,6 +70,21 @@ func init() { Value: defaultConf.Workers.Containerd.Namespace, Hidden: true, }, + cli.StringFlag{ + Name: "containerd-worker-net", + Usage: "worker network type (auto, cni or host)", + Value: defaultConf.Workers.Containerd.NetworkConfig.Mode, + }, + cli.StringFlag{ + Name: "containerd-cni-config-path", + Usage: "path of cni config file", + Value: defaultConf.Workers.Containerd.NetworkConfig.CNIConfigPath, + }, + cli.StringFlag{ + Name: "containerd-cni-binary-dir", + Usage: "path of cni binary files", + Value: defaultConf.Workers.Containerd.NetworkConfig.CNIBinaryPath, + }, } if defaultConf.Workers.Containerd.GC == nil || *defaultConf.Workers.Containerd.GC { @@ -158,6 +174,16 @@ func applyContainerdFlags(c *cli.Context, cfg *config.Config) error { cfg.Workers.Containerd.GCKeepStorage = c.GlobalInt64("containerd-worker-gc-keepstorage") * 1e6 } + if c.GlobalIsSet("containerd-worker-net") { + cfg.Workers.Containerd.NetworkConfig.Mode = c.GlobalString("containerd-worker-net") + } + if c.GlobalIsSet("containerd-cni-config-path") { + cfg.Workers.Containerd.NetworkConfig.CNIConfigPath = c.GlobalString("containerd-cni-worker-path") + } + if c.GlobalIsSet("containerd-cni-binary-dir") { + cfg.Workers.Containerd.NetworkConfig.CNIBinaryPath = c.GlobalString("containerd-cni-binary-dir") + } + return nil } @@ -174,7 +200,14 @@ func containerdWorkerInitializer(c *cli.Context, common workerInitializerOpt) ([ dns := getDNSConfig(common.config.DNS) - opt, err := containerd.NewWorkerOpt(common.config.Root, cfg.Address, ctd.DefaultSnapshotter, cfg.Namespace, cfg.Labels, dns, ctd.WithTimeout(60*time.Second)) + nc := network.Opt{ + Root: common.config.Root, + Mode: common.config.Workers.Containerd.NetworkConfig.Mode, + CNIConfigPath: common.config.Workers.Containerd.CNIConfigPath, + CNIBinaryDir: common.config.Workers.Containerd.CNIBinaryPath, + } + + opt, err := containerd.NewWorkerOpt(common.config.Root, cfg.Address, ctd.DefaultSnapshotter, cfg.Namespace, cfg.Labels, dns, nc, ctd.WithTimeout(60*time.Second)) if err != nil { return nil, err } diff --git a/cmd/buildkitd/main_oci_worker.go b/cmd/buildkitd/main_oci_worker.go index 93f94a97bf4c..c450b5a9f9fe 100644 --- a/cmd/buildkitd/main_oci_worker.go +++ b/cmd/buildkitd/main_oci_worker.go @@ -11,6 +11,7 @@ import ( "github.com/containerd/containerd/snapshots/overlay" "github.com/moby/buildkit/cmd/buildkitd/config" "github.com/moby/buildkit/executor/oci" + "github.com/moby/buildkit/util/network" "github.com/moby/buildkit/worker" "github.com/moby/buildkit/worker/base" "github.com/moby/buildkit/worker/runc" @@ -53,6 +54,21 @@ func init() { Name: "oci-worker-platform", Usage: "override supported platforms for worker", }, + cli.StringFlag{ + Name: "oci-worker-net", + Usage: "worker network type (auto, cni or host)", + Value: defaultConf.Workers.OCI.NetworkConfig.Mode, + }, + cli.StringFlag{ + Name: "oci-cni-config-path", + Usage: "path of cni config file", + Value: defaultConf.Workers.OCI.NetworkConfig.CNIConfigPath, + }, + cli.StringFlag{ + Name: "oci-cni-binary-dir", + Usage: "path of cni binary files", + Value: defaultConf.Workers.OCI.NetworkConfig.CNIBinaryPath, + }, } n := "oci-worker-rootless" u := "enable rootless mode" @@ -154,6 +170,15 @@ func applyOCIFlags(c *cli.Context, cfg *config.Config) error { cfg.Workers.OCI.GCKeepStorage = c.GlobalInt64("oci-worker-gc-keepstorage") * 1e6 } + if c.GlobalIsSet("oci-worker-net") { + cfg.Workers.OCI.NetworkConfig.Mode = c.GlobalString("oci-worker-net") + } + if c.GlobalIsSet("oci-cni-config-path") { + cfg.Workers.OCI.NetworkConfig.CNIConfigPath = c.GlobalString("oci-cni-worker-path") + } + if c.GlobalIsSet("oci-cni-binary-dir") { + cfg.Workers.OCI.NetworkConfig.CNIBinaryPath = c.GlobalString("oci-cni-binary-dir") + } return nil } @@ -194,7 +219,14 @@ func ociWorkerInitializer(c *cli.Context, common workerInitializerOpt) ([]worker dns := getDNSConfig(common.config.DNS) - opt, err := runc.NewWorkerOpt(common.config.Root, snFactory, cfg.Rootless, processMode, cfg.Labels, idmapping, dns) + nc := network.Opt{ + Root: common.config.Root, + Mode: common.config.Workers.OCI.NetworkConfig.Mode, + CNIConfigPath: common.config.Workers.OCI.CNIConfigPath, + CNIBinaryDir: common.config.Workers.OCI.CNIBinaryPath, + } + + opt, err := runc.NewWorkerOpt(common.config.Root, snFactory, cfg.Rootless, processMode, cfg.Labels, idmapping, nc, dns) if err != nil { return nil, err } diff --git a/cmd/buildkitd/main_unix.go b/cmd/buildkitd/main_unix.go index 93533adb1fdf..649fc0f5ba51 100644 --- a/cmd/buildkitd/main_unix.go +++ b/cmd/buildkitd/main_unix.go @@ -4,8 +4,12 @@ package main import ( "syscall" + + "github.com/moby/buildkit/util/network/netns_create" ) func init() { + netns_create.Handle() + syscall.Umask(0) } diff --git a/go.mod b/go.mod index 3d54653c7592..825c02e7ae78 100644 --- a/go.mod +++ b/go.mod @@ -12,9 +12,11 @@ require ( github.com/containerd/containerd v1.3.0-0.20190426060238-3a3f0aac8819 github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc github.com/containerd/fifo v0.0.0-20180307165137-3d5202aec260 // indirect + github.com/containerd/go-cni v0.0.0-20190610170741-5a4663dad645 github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3 github.com/containerd/ttrpc v0.0.0-20190411181408-699c4e40d1e7 // indirect github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd // indirect + github.com/containernetworking/cni v0.6.1-0.20180218032124-142cde0c766c // indirect github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e github.com/docker/cli v0.0.0-20190321234815-f40f9c240ab0 github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible diff --git a/go.sum b/go.sum index d407c5527ed6..0512165a2f67 100644 --- a/go.sum +++ b/go.sum @@ -23,12 +23,16 @@ github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc h1:TP+534wVl github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= github.com/containerd/fifo v0.0.0-20180307165137-3d5202aec260 h1:XGyg7oTtD0DoRFhbpV6x1WfV0flKC4UxXU7ab1zC08U= github.com/containerd/fifo v0.0.0-20180307165137-3d5202aec260/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= +github.com/containerd/go-cni v0.0.0-20190610170741-5a4663dad645 h1:CCxW+4asjsbhMjWAznv/rTLYYI7Mcm6LovkzaPtD3rU= +github.com/containerd/go-cni v0.0.0-20190610170741-5a4663dad645/go.mod h1:2wlRxCQdiBY+OcjNg5x8kI+5mEL1fGt25L4IzQHYJsM= github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3 h1:esQOJREg8nw8aXj6uCN5dfW5cKUBiEJ/+nni1Q/D/sw= github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= github.com/containerd/ttrpc v0.0.0-20190411181408-699c4e40d1e7 h1:SKDlsIhYxNE1LO0xwuOR+3QWj3zRibVQu5jWIMQmOfU= github.com/containerd/ttrpc v0.0.0-20190411181408-699c4e40d1e7/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd h1:JNn81o/xG+8NEo3bC/vx9pbi/g2WI8mtP2/nXzu297Y= github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc= +github.com/containernetworking/cni v0.6.1-0.20180218032124-142cde0c766c h1:X6Gxg2GV1i0UhDodKZYrp//lg8h3KANe8l3gtFHoi9M= +github.com/containernetworking/cni v0.6.1-0.20180218032124-142cde0c766c/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e h1:Wf6HqHfScWJN9/ZjdUKyjop4mf3Qdd+1TvvltAvM3m8= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -51,6 +55,7 @@ github.com/docker/go-units v0.3.1 h1:QAFdsA6jLCnglbqE6mUsHuPcJlntY94DkxHf4deHKIU github.com/docker/go-units v0.3.1/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/libnetwork v0.8.0-dev.2.0.20190604151032-3c26b4e7495e h1:/9dBUVUO865jROD5LfE232z9ssWlBlzIMVW0BaEn8DM= github.com/docker/libnetwork v0.8.0-dev.2.0.20190604151032-3c26b4e7495e/go.mod h1:93m0aTqz6z+g32wla4l4WxTrdtvBRmVzYRkYvasA5Z8= +github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/godbus/dbus v4.1.0+incompatible h1:WqqLRTsQic3apZUK9qC5sGNfXthmPXzUZ7nQPrNITa4= github.com/godbus/dbus v4.1.0+incompatible/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= @@ -77,6 +82,7 @@ github.com/hashicorp/golang-lru v0.0.0-20160207214719-a0d98a5f2880 h1:OaRuzt9oCK github.com/hashicorp/golang-lru v0.0.0-20160207214719-a0d98a5f2880/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/uuid v0.0.0-20160311170451-ebb0a03e909c h1:nQcv325vxv2fFHJsOt53eSRf1eINt6vOdYUFfXs4rgk= github.com/hashicorp/uuid v0.0.0-20160311170451-ebb0a03e909c/go.mod h1:fHzc09UnyJyqyW+bFuq864eh+wC7dj65aXmXLRe5to0= +github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ishidawataru/sctp v0.0.0-20180213033435-07191f837fed h1:3MJOWnAfq3T9eoCQTarEY2DMlUWYcBkBLf03dAMvEb8= github.com/ishidawataru/sctp v0.0.0-20180213033435-07191f837fed/go.mod h1:DM4VvS+hD/kDi1U1QsX2fnZowwBhqD0Dk3bRPKF/Oc8= @@ -92,7 +98,9 @@ github.com/mitchellh/hashstructure v0.0.0-20170609045927-2bca23e0e452/go.mod h1: github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c h1:nXxl5PrvVm2L/wCy8dQu6DMTwH4oIuGN8GJDAlqDdVE= github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ= github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= @@ -119,6 +127,7 @@ github.com/sirupsen/logrus v1.0.3/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjM github.com/sirupsen/logrus v1.3.0 h1:hI/7Q+DtNZ2kINb6qt/lS+IyXnHQe9e90POfeewL/ME= github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= @@ -175,9 +184,12 @@ gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gotest.tools v2.1.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= diff --git a/hack/dockerfiles/test.buildkit.Dockerfile b/hack/dockerfiles/test.buildkit.Dockerfile index 084ee2146b51..9ef4bf256b84 100644 --- a/hack/dockerfiles/test.buildkit.Dockerfile +++ b/hack/dockerfiles/test.buildkit.Dockerfile @@ -198,9 +198,17 @@ VOLUME /var/lib/containerd VOLUME /run/containerd ENTRYPOINT ["containerd"] +FROM alpine AS cni-plugins +RUN apk add --no-cache curl +ENV CNI_VERSION=v0.8.1 +ARG TARGETOS +ARG TARGETARCH +WORKDIR /opt/cni/bin +RUN curl -Ls https://github.com/containernetworking/plugins/releases/download/$CNI_VERSION/cni-plugins-$TARGETOS-$TARGETARCH-$CNI_VERSION.tgz | tar xzv + FROM buildkit-base AS integration-tests ENV BUILDKIT_INTEGRATION_ROOTLESS_IDPAIR="1000:1000" -RUN apt-get install -y --no-install-recommends uidmap sudo \ +RUN apt-get install -y --no-install-recommends uidmap sudo vim iptables \ && useradd --create-home --home-dir /home/user --uid 1000 -s /bin/sh user \ && echo "XDG_RUNTIME_DIR=/run/user/1000; export XDG_RUNTIME_DIR" >> /home/user/.profile \ && mkdir -m 0700 -p /run/user/1000 \ @@ -212,9 +220,14 @@ COPY --from=containerd10 /out/containerd* /opt/containerd-1.0/bin/ COPY --from=registry /bin/registry /usr/bin COPY --from=runc /usr/bin/runc /usr/bin COPY --from=containerd /out/containerd* /usr/bin/ +COPY --from=cni-plugins /opt/cni/bin/bridge /opt/cni/bin/host-local /opt/cni/bin +COPY hack/fixtures/cni.json /etc/buildkit/cni.json COPY --from=binaries / /usr/bin/ COPY . . +FROM integration-tests AS dev-env +VOLUME /var/lib/buildkit + # To allow running buildkit in a container without CAP_SYS_ADMIN, we need to do either # a) install newuidmap/newgidmap with file capabilities rather than SETUID (requires kernel >= 4.14) # b) install newuidmap/newgidmap >= 20181125 (59c2dabb264ef7b3137f5edb52c0b31d5af0cf76) diff --git a/util/network/cni.go b/util/network/cni.go new file mode 100644 index 000000000000..087803c3a8d8 --- /dev/null +++ b/util/network/cni.go @@ -0,0 +1,91 @@ +package network + +import ( + "os" + "path/filepath" + "syscall" + + "github.com/containerd/containerd/oci" + "github.com/containerd/go-cni" + "github.com/moby/buildkit/identity" + "github.com/moby/buildkit/util/network/netns_create" + specs "github.com/opencontainers/runtime-spec/specs-go" + "github.com/pkg/errors" + "golang.org/x/sys/unix" +) + +func NewCNIProvider(opt Opt) (Provider, error) { + if _, err := os.Stat(opt.CNIConfigPath); err != nil { + return nil, errors.Wrapf(err, "failed to read cni config %q", opt.CNIConfigPath) + } + if _, err := os.Stat(opt.CNIBinaryDir); err != nil { + return nil, errors.Wrapf(err, "failed to read cni binary dir %q", opt.CNIBinaryDir) + } + + cniHandle, err := cni.New( + cni.WithMinNetworkCount(2), + cni.WithConfFile(opt.CNIConfigPath), + cni.WithPluginDir([]string{opt.CNIBinaryDir}), + cni.WithLoNetwork, + cni.WithInterfacePrefix(("eth"))) + if err != nil { + return nil, err + } + + if err != nil { + return nil, err + } + + return &cniProvider{CNI: cniHandle, root: opt.Root}, nil +} + +type cniProvider struct { + cni.CNI + root string +} + +func (c *cniProvider) New() (Namespace, error) { + id := identity.NewID() + nsPath := filepath.Join(c.root, "net/cni", id) + if err := os.MkdirAll(filepath.Dir(nsPath), 0700); err != nil { + return nil, err + } + + if err := netns_create.CreateNetNS(nsPath); err != nil { + return nil, err + } + + if _, err := c.CNI.Setup(id, nsPath); err != nil { + return nil, errors.Wrap(err, "CNI setup error") + } + + return &cniNS{path: nsPath, id: id, handle: c.CNI}, nil +} + +type cniNS struct { + handle cni.CNI + id string + path string +} + +func (ns *cniNS) Set(s *specs.Spec) { + oci.WithLinuxNamespace(specs.LinuxNamespace{ + Type: specs.NetworkNamespace, + Path: ns.path, + })(nil, nil, nil, s) +} + +func (ns *cniNS) Close() error { + err := ns.handle.Remove(ns.id, ns.path) + + if err1 := unix.Unmount(ns.path, unix.MNT_DETACH); err1 != nil { + if err1 != syscall.EINVAL && err1 != syscall.ENOENT && err == nil { + err = errors.Wrap(err1, "error unmounting network namespace") + } + } + if err1 := os.RemoveAll(filepath.Dir(ns.path)); err1 != nil && !os.IsNotExist(err1) && err == nil { + err = errors.Wrap(err, "error removing network namespace") + } + + return err +} diff --git a/util/network/netns_create/hook.go b/util/network/netns_create/hook.go new file mode 100644 index 000000000000..d2a060c7b7d4 --- /dev/null +++ b/util/network/netns_create/hook.go @@ -0,0 +1,51 @@ +package netns_create + +import ( + "log" + "os" + "os/exec" + "syscall" + + "github.com/pkg/errors" + "golang.org/x/sys/unix" +) + +const envKey = "BUILDKIT_CREATE_NS_PATH" + +func Handle() { + if path := os.Getenv(envKey); path != "" { + if err := handle(path); err != nil { + log.Printf("%v", err) + os.Exit(1) + } + os.Exit(0) + } +} + +func handle(path string) error { + f, err := os.Create(path) + if err != nil { + return err + } + if err := f.Close(); err != nil { + return err + } + if err := unix.Mount("/proc/self/ns/net", path, "none", unix.MS_BIND, ""); err != nil { + return err + } + return nil +} + +func CreateNetNS(path string) error { + cmd := exec.Command("/proc/self/exe") + cmd.Env = []string{envKey + "=" + path} + cmd.SysProcAttr = &syscall.SysProcAttr{ + Cloneflags: unix.CLONE_NEWNET, + } + out, err := cmd.CombinedOutput() + if err != nil { + return errors.Wrap(err, string(out)) + + } + return nil +} diff --git a/util/network/network.go b/util/network/network.go index 055a52da8bce..824d3edf3b7c 100644 --- a/util/network/network.go +++ b/util/network/network.go @@ -2,19 +2,51 @@ package network import ( "io" + "os" "github.com/moby/buildkit/solver/pb" specs "github.com/opencontainers/runtime-spec/specs-go" + "github.com/pkg/errors" ) -// Default returns the default network provider set -func Default() map[pb.NetMode]Provider { +type Opt struct { + Root string + Mode string + CNIConfigPath string + CNIBinaryDir string +} + +// Providers returns the network provider set +func Providers(opt Opt) (map[pb.NetMode]Provider, error) { + var defaultProvider Provider + switch opt.Mode { + case "cni": + cniProvider, err := NewCNIProvider(opt) + if err != nil { + return nil, err + } + defaultProvider = cniProvider + case "host": + defaultProvider = NewHostProvider() + case "auto", "": + if _, err := os.Stat(opt.CNIConfigPath); err == nil { + cniProvider, err := NewCNIProvider(opt) + if err != nil { + return nil, err + } + defaultProvider = cniProvider + } else { + defaultProvider = NewHostProvider() + } + default: + return nil, errors.Errorf("invalid network mode: %q", opt.Mode) + } + return map[pb.NetMode]Provider{ - // FIXME: still uses host if no provider configured - pb.NetMode_UNSET: NewHostProvider(), + pb.NetMode_UNSET: defaultProvider, pb.NetMode_HOST: NewHostProvider(), pb.NetMode_NONE: NewNoneProvider(), - } + }, nil } // Provider interface for Network diff --git a/vendor/github.com/containerd/go-cni/.travis.yml b/vendor/github.com/containerd/go-cni/.travis.yml new file mode 100644 index 000000000000..9fe2eadc60af --- /dev/null +++ b/vendor/github.com/containerd/go-cni/.travis.yml @@ -0,0 +1,22 @@ +language: go +go: + - 1.10.x + - tip + +go_import_path: github.com/containerd/go-cni + +install: + - go get -d + - go get -u github.com/vbatts/git-validation + - go get -u github.com/kunalkushwaha/ltag + +before_script: + - pushd ..; git clone https://github.com/containerd/project; popd + +script: + - DCO_VERBOSITY=-q ../project/script/validate/dco + - ../project/script/validate/fileheader ../project/ + - go test -race -coverprofile=coverage.txt -covermode=atomic + +after_success: + - bash <(curl -s https://codecov.io/bash) diff --git a/vendor/github.com/containerd/go-cni/LICENSE b/vendor/github.com/containerd/go-cni/LICENSE new file mode 100644 index 000000000000..261eeb9e9f8b --- /dev/null +++ b/vendor/github.com/containerd/go-cni/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/containerd/go-cni/README.md b/vendor/github.com/containerd/go-cni/README.md new file mode 100644 index 000000000000..1bd2f0013fb7 --- /dev/null +++ b/vendor/github.com/containerd/go-cni/README.md @@ -0,0 +1,60 @@ +[![Build Status](https://travis-ci.org/containerd/go-cni.svg?branch=master)](https://travis-ci.org/containerd/go-cni) + +# go-cni + +A generic CNI library to provide APIs for CNI plugin interactions. The library provides APIs to: + +- Load CNI network config from different sources +- Setup networks for container namespace +- Remove networks from container namespace +- Query status of CNI network plugin initialization + +go-cni aims to support plugins that implement [Container Network Interface](https://github.com/containernetworking/cni) + +## Usage +```go +func main() { + id := "123456" + netns := "/proc/9999/ns/net" + defaultIfName := "eth0" + // Initialize library + l = gocni.New(gocni.WithMinNetworkCount(2), + gocni.WithPluginConfDir("/etc/mycni/net.d"), + gocni.WithPluginDir([]string{"/opt/mycni/bin", "/opt/cni/bin"}), + gocni.WithDefaultIfName(defaultIfName)) + + // Load the cni configuration + err:= l.Load(gocni.WithLoNetwork, gocni.WithDefaultConf) + if err != nil{ + log.Errorf("failed to load cni configuration: %v", err) + return + } + + // Setup network for namespace. + labels := map[string]string{ + "K8S_POD_NAMESPACE": "namespace1", + "K8S_POD_NAME": "pod1", + "K8S_POD_INFRA_CONTAINER_ID": id, + } + result, err := l.Setup(id, netns, gocni.WithLabels(labels)) + if err != nil { + log.Errorf("failed to setup network for namespace %q: %v",id, err) + return + } + + // Get IP of the default interface + IP := result.Interfaces[defaultIfName].IPConfigs[0].IP.String() + fmt.Printf("IP of the default interface %s:%s", defaultIfName, IP) +} +``` + +## Project details + +The go-cni is a containerd sub-project, licensed under the [Apache 2.0 license](./LICENSE). +As a containerd sub-project, you will find the: + + * [Project governance](https://github.com/containerd/project/blob/master/GOVERNANCE.md), + * [Maintainers](https://github.com/containerd/project/blob/master/MAINTAINERS), + * and [Contributing guidelines](https://github.com/containerd/project/blob/master/CONTRIBUTING.md) + +information in our [`containerd/project`](https://github.com/containerd/project) repository. diff --git a/vendor/github.com/containerd/go-cni/cni.go b/vendor/github.com/containerd/go-cni/cni.go new file mode 100644 index 000000000000..bdd63b841bb7 --- /dev/null +++ b/vendor/github.com/containerd/go-cni/cni.go @@ -0,0 +1,219 @@ +/* + Copyright The containerd 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 cni + +import ( + "fmt" + "strings" + "sync" + + cnilibrary "github.com/containernetworking/cni/libcni" + "github.com/containernetworking/cni/pkg/types" + "github.com/containernetworking/cni/pkg/types/current" + "github.com/pkg/errors" +) + +type CNI interface { + // Setup setup the network for the namespace + Setup(id string, path string, opts ...NamespaceOpts) (*CNIResult, error) + // Remove tears down the network of the namespace. + Remove(id string, path string, opts ...NamespaceOpts) error + // Load loads the cni network config + Load(opts ...CNIOpt) error + // Status checks the status of the cni initialization + Status() error + // GetConfig returns a copy of the CNI plugin configurations as parsed by CNI + GetConfig() *ConfigResult +} + +type ConfigResult struct { + PluginDirs []string + PluginConfDir string + PluginMaxConfNum int + Prefix string + Networks []*ConfNetwork +} + +type ConfNetwork struct { + Config *NetworkConfList + IFName string +} + +// NetworkConfList is a source bytes to string version of cnilibrary.NetworkConfigList +type NetworkConfList struct { + Name string + CNIVersion string + Plugins []*NetworkConf + Source string +} + +// NetworkConf is a source bytes to string conversion of cnilibrary.NetworkConfig +type NetworkConf struct { + Network *types.NetConf + Source string +} + +type libcni struct { + config + + cniConfig cnilibrary.CNI + networkCount int // minimum network plugin configurations needed to initialize cni + networks []*Network + sync.RWMutex +} + +func defaultCNIConfig() *libcni { + return &libcni{ + config: config{ + pluginDirs: []string{DefaultCNIDir}, + pluginConfDir: DefaultNetDir, + pluginMaxConfNum: DefaultMaxConfNum, + prefix: DefaultPrefix, + }, + cniConfig: &cnilibrary.CNIConfig{ + Path: []string{DefaultCNIDir}, + }, + networkCount: 1, + } +} + +// New creates a new libcni instance. +func New(config ...CNIOpt) (CNI, error) { + cni := defaultCNIConfig() + var err error + for _, c := range config { + if err = c(cni); err != nil { + return nil, err + } + } + return cni, nil +} + +// Load loads the latest config from cni config files. +func (c *libcni) Load(opts ...CNIOpt) error { + var err error + c.Lock() + defer c.Unlock() + // Reset the networks on a load operation to ensure + // config happens on a clean slate + c.reset() + + for _, o := range opts { + if err = o(c); err != nil { + return errors.Wrapf(ErrLoad, fmt.Sprintf("cni config load failed: %v", err)) + } + } + return nil +} + +// Status returns the status of CNI initialization. +func (c *libcni) Status() error { + c.RLock() + defer c.RUnlock() + if len(c.networks) < c.networkCount { + return ErrCNINotInitialized + } + return nil +} + +// Networks returns all the configured networks. +// NOTE: Caller MUST NOT modify anything in the returned array. +func (c *libcni) Networks() []*Network { + c.RLock() + defer c.RUnlock() + return append([]*Network{}, c.networks...) +} + +// Setup setups the network in the namespace +func (c *libcni) Setup(id string, path string, opts ...NamespaceOpts) (*CNIResult, error) { + if err := c.Status(); err != nil { + return nil, err + } + ns, err := newNamespace(id, path, opts...) + if err != nil { + return nil, err + } + var results []*current.Result + for _, network := range c.Networks() { + r, err := network.Attach(ns) + if err != nil { + return nil, err + } + results = append(results, r) + } + return c.GetCNIResultFromResults(results) +} + +// Remove removes the network config from the namespace +func (c *libcni) Remove(id string, path string, opts ...NamespaceOpts) error { + if err := c.Status(); err != nil { + return err + } + ns, err := newNamespace(id, path, opts...) + if err != nil { + return err + } + for _, network := range c.Networks() { + if err := network.Remove(ns); err != nil { + // Based on CNI spec v0.7.0, empty network namespace is allowed to + // do best effort cleanup. However, it is not handled consistently + // right now: + // https://github.com/containernetworking/plugins/issues/210 + // TODO(random-liu): Remove the error handling when the issue is + // fixed and the CNI spec v0.6.0 support is deprecated. + if path == "" && strings.Contains(err.Error(), "no such file or directory") { + continue + } + return err + } + } + return nil +} + +// GetConfig returns a copy of the CNI plugin configurations as parsed by CNI +func (c *libcni) GetConfig() *ConfigResult { + c.RLock() + defer c.RUnlock() + r := &ConfigResult{ + PluginDirs: c.config.pluginDirs, + PluginConfDir: c.config.pluginConfDir, + PluginMaxConfNum: c.config.pluginMaxConfNum, + Prefix: c.config.prefix, + } + for _, network := range c.networks { + conf := &NetworkConfList{ + Name: network.config.Name, + CNIVersion: network.config.CNIVersion, + Source: string(network.config.Bytes), + } + for _, plugin := range network.config.Plugins { + conf.Plugins = append(conf.Plugins, &NetworkConf{ + Network: plugin.Network, + Source: string(plugin.Bytes), + }) + } + r.Networks = append(r.Networks, &ConfNetwork{ + Config: conf, + IFName: network.ifName, + }) + } + return r +} + +func (c *libcni) reset() { + c.networks = nil +} diff --git a/vendor/github.com/containerd/go-cni/errors.go b/vendor/github.com/containerd/go-cni/errors.go new file mode 100644 index 000000000000..28761711ed29 --- /dev/null +++ b/vendor/github.com/containerd/go-cni/errors.go @@ -0,0 +1,55 @@ +/* + Copyright The containerd 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 cni + +import ( + "github.com/pkg/errors" +) + +var ( + ErrCNINotInitialized = errors.New("cni plugin not initialized") + ErrInvalidConfig = errors.New("invalid cni config") + ErrNotFound = errors.New("not found") + ErrRead = errors.New("failed to read config file") + ErrInvalidResult = errors.New("invalid result") + ErrLoad = errors.New("failed to load cni config") +) + +// IsCNINotInitialized returns true if the error is due to cni config not being initialized +func IsCNINotInitialized(err error) bool { + return errors.Cause(err) == ErrCNINotInitialized +} + +// IsInvalidConfig returns true if the error is invalid cni config +func IsInvalidConfig(err error) bool { + return errors.Cause(err) == ErrInvalidConfig +} + +// IsNotFound returns true if the error is due to a missing config or result +func IsNotFound(err error) bool { + return errors.Cause(err) == ErrNotFound +} + +// IsReadFailure return true if the error is a config read failure +func IsReadFailure(err error) bool { + return errors.Cause(err) == ErrRead +} + +// IsInvalidResult return true if the error is due to invalid cni result +func IsInvalidResult(err error) bool { + return errors.Cause(err) == ErrInvalidResult +} diff --git a/vendor/github.com/containerd/go-cni/helper.go b/vendor/github.com/containerd/go-cni/helper.go new file mode 100644 index 000000000000..6cde2b33294b --- /dev/null +++ b/vendor/github.com/containerd/go-cni/helper.go @@ -0,0 +1,41 @@ +/* + Copyright The containerd 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 cni + +import ( + "fmt" + + "github.com/containernetworking/cni/pkg/types/current" +) + +func validateInterfaceConfig(ipConf *current.IPConfig, ifs int) error { + if ipConf == nil { + return fmt.Errorf("invalid IP configuration") + } + if ipConf.Interface != nil && *ipConf.Interface > ifs { + return fmt.Errorf("invalid IP configuration with invalid interface %d", *ipConf.Interface) + } + return nil +} + +func getIfName(prefix string, i int) string { + return fmt.Sprintf("%s%d", prefix, i) +} + +func defaultInterface(prefix string) string { + return getIfName(prefix, 0) +} diff --git a/vendor/github.com/containerd/go-cni/namespace.go b/vendor/github.com/containerd/go-cni/namespace.go new file mode 100644 index 000000000000..746c995eb984 --- /dev/null +++ b/vendor/github.com/containerd/go-cni/namespace.go @@ -0,0 +1,75 @@ +/* + Copyright The containerd 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 cni + +import ( + cnilibrary "github.com/containernetworking/cni/libcni" + "github.com/containernetworking/cni/pkg/types/current" +) + +type Network struct { + cni cnilibrary.CNI + config *cnilibrary.NetworkConfigList + ifName string +} + +func (n *Network) Attach(ns *Namespace) (*current.Result, error) { + r, err := n.cni.AddNetworkList(n.config, ns.config(n.ifName)) + if err != nil { + return nil, err + } + return current.NewResultFromResult(r) +} + +func (n *Network) Remove(ns *Namespace) error { + return n.cni.DelNetworkList(n.config, ns.config(n.ifName)) +} + +type Namespace struct { + id string + path string + capabilityArgs map[string]interface{} + args map[string]string +} + +func newNamespace(id, path string, opts ...NamespaceOpts) (*Namespace, error) { + ns := &Namespace{ + id: id, + path: path, + capabilityArgs: make(map[string]interface{}), + args: make(map[string]string), + } + for _, o := range opts { + if err := o(ns); err != nil { + return nil, err + } + } + return ns, nil +} + +func (ns *Namespace) config(ifName string) *cnilibrary.RuntimeConf { + c := &cnilibrary.RuntimeConf{ + ContainerID: ns.id, + NetNS: ns.path, + IfName: ifName, + } + for k, v := range ns.args { + c.Args = append(c.Args, [2]string{k, v}) + } + c.CapabilityArgs = ns.capabilityArgs + return c +} diff --git a/vendor/github.com/containerd/go-cni/namespace_opts.go b/vendor/github.com/containerd/go-cni/namespace_opts.go new file mode 100644 index 000000000000..e8092e85ecd7 --- /dev/null +++ b/vendor/github.com/containerd/go-cni/namespace_opts.go @@ -0,0 +1,67 @@ +/* + Copyright The containerd 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 cni + +type NamespaceOpts func(s *Namespace) error + +// Capabilities +func WithCapabilityPortMap(portMapping []PortMapping) NamespaceOpts { + return func(c *Namespace) error { + c.capabilityArgs["portMappings"] = portMapping + return nil + } +} + +func WithCapabilityIPRanges(ipRanges []IPRanges) NamespaceOpts { + return func(c *Namespace) error { + c.capabilityArgs["ipRanges"] = ipRanges + return nil + } +} + +// WithCapabilityBandWitdh adds support for traffic shaping: +// https://github.com/heptio/cni-plugins/tree/master/plugins/meta/bandwidth +func WithCapabilityBandWidth(bandWidth BandWidth) NamespaceOpts { + return func(c *Namespace) error { + c.capabilityArgs["bandwidth"] = bandWidth + return nil + } +} + +func WithCapability(name string, capability interface{}) NamespaceOpts { + return func(c *Namespace) error { + c.capabilityArgs[name] = capability + return nil + } +} + +// Args +func WithLabels(labels map[string]string) NamespaceOpts { + return func(c *Namespace) error { + for k, v := range labels { + c.args[k] = v + } + return nil + } +} + +func WithArgs(k, v string) NamespaceOpts { + return func(c *Namespace) error { + c.args[k] = v + return nil + } +} diff --git a/vendor/github.com/containerd/go-cni/opts.go b/vendor/github.com/containerd/go-cni/opts.go new file mode 100644 index 000000000000..4a6f66d05f0e --- /dev/null +++ b/vendor/github.com/containerd/go-cni/opts.go @@ -0,0 +1,245 @@ +/* + Copyright The containerd 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 cni + +import ( + "sort" + "strings" + + cnilibrary "github.com/containernetworking/cni/libcni" + "github.com/pkg/errors" +) + +type CNIOpt func(c *libcni) error + +// WithInterfacePrefix sets the prefix for network interfaces +// e.g. eth or wlan +func WithInterfacePrefix(prefix string) CNIOpt { + return func(c *libcni) error { + c.prefix = prefix + return nil + } +} + +// WithPluginDir can be used to set the locations of +// the cni plugin binaries +func WithPluginDir(dirs []string) CNIOpt { + return func(c *libcni) error { + c.pluginDirs = dirs + c.cniConfig = &cnilibrary.CNIConfig{Path: dirs} + return nil + } +} + +// WithPluginConfDir can be used to configure the +// cni configuration directory. +func WithPluginConfDir(dir string) CNIOpt { + return func(c *libcni) error { + c.pluginConfDir = dir + return nil + } +} + +// WithPluginMaxConfNum can be used to configure the +// max cni plugin config file num. +func WithPluginMaxConfNum(max int) CNIOpt { + return func(c *libcni) error { + c.pluginMaxConfNum = max + return nil + } +} + +// WithMinNetworkCount can be used to configure the +// minimum networks to be configured and initialized +// for the status to report success. By default its 1. +func WithMinNetworkCount(count int) CNIOpt { + return func(c *libcni) error { + c.networkCount = count + return nil + } +} + +// WithLoNetwork can be used to load the loopback +// network config. +func WithLoNetwork(c *libcni) error { + loConfig, _ := cnilibrary.ConfListFromBytes([]byte(`{ +"cniVersion": "0.3.1", +"name": "cni-loopback", +"plugins": [{ + "type": "loopback" +}] +}`)) + + c.networks = append(c.networks, &Network{ + cni: c.cniConfig, + config: loConfig, + ifName: "lo", + }) + return nil +} + +// WithConf can be used to load config directly +// from byte. +func WithConf(bytes []byte) CNIOpt { + return WithConfIndex(bytes, 0) +} + +// WithConfIndex can be used to load config directly +// from byte and set the interface name's index. +func WithConfIndex(bytes []byte, index int) CNIOpt { + return func(c *libcni) error { + conf, err := cnilibrary.ConfFromBytes(bytes) + if err != nil { + return err + } + confList, err := cnilibrary.ConfListFromConf(conf) + if err != nil { + return err + } + c.networks = append(c.networks, &Network{ + cni: c.cniConfig, + config: confList, + ifName: getIfName(c.prefix, index), + }) + return nil + } +} + +// WithConfFile can be used to load network config +// from an .conf file. Supported with absolute fileName +// with path only. +func WithConfFile(fileName string) CNIOpt { + return func(c *libcni) error { + conf, err := cnilibrary.ConfFromFile(fileName) + if err != nil { + return err + } + // upconvert to conf list + confList, err := cnilibrary.ConfListFromConf(conf) + if err != nil { + return err + } + c.networks = append(c.networks, &Network{ + cni: c.cniConfig, + config: confList, + ifName: getIfName(c.prefix, 0), + }) + return nil + } +} + +// WithConfListFile can be used to load network config +// from an .conflist file. Supported with absolute fileName +// with path only. +func WithConfListFile(fileName string) CNIOpt { + return func(c *libcni) error { + confList, err := cnilibrary.ConfListFromFile(fileName) + if err != nil { + return err + } + i := len(c.networks) + c.networks = append(c.networks, &Network{ + cni: c.cniConfig, + config: confList, + ifName: getIfName(c.prefix, i), + }) + return nil + } +} + +// WithDefaultConf can be used to detect the default network +// config file from the configured cni config directory and load +// it. +// Since the CNI spec does not specify a way to detect default networks, +// the convention chosen is - the first network configuration in the sorted +// list of network conf files as the default network. +func WithDefaultConf(c *libcni) error { + return loadFromConfDir(c, c.pluginMaxConfNum) +} + +// WithAllConf can be used to detect all network config +// files from the configured cni config directory and load +// them. +func WithAllConf(c *libcni) error { + return loadFromConfDir(c, 0) +} + +// loadFromConfDir detects network config files from the +// configured cni config directory and load them. max is +// the maximum network config to load (max i<= 0 means no limit). +func loadFromConfDir(c *libcni, max int) error { + files, err := cnilibrary.ConfFiles(c.pluginConfDir, []string{".conf", ".conflist", ".json"}) + switch { + case err != nil: + return errors.Wrapf(ErrRead, "failed to read config file: %v", err) + case len(files) == 0: + return errors.Wrapf(ErrCNINotInitialized, "no network config found in %s", c.pluginConfDir) + } + + // files contains the network config files associated with cni network. + // Use lexicographical way as a defined order for network config files. + sort.Strings(files) + // Since the CNI spec does not specify a way to detect default networks, + // the convention chosen is - the first network configuration in the sorted + // list of network conf files as the default network and choose the default + // interface provided during init as the network interface for this default + // network. For every other network use a generated interface id. + i := 0 + var networks []*Network + for _, confFile := range files { + var confList *cnilibrary.NetworkConfigList + if strings.HasSuffix(confFile, ".conflist") { + confList, err = cnilibrary.ConfListFromFile(confFile) + if err != nil { + return errors.Wrapf(ErrInvalidConfig, "failed to load CNI config list file %s: %v", confFile, err) + } + } else { + conf, err := cnilibrary.ConfFromFile(confFile) + if err != nil { + return errors.Wrapf(ErrInvalidConfig, "failed to load CNI config file %s: %v", confFile, err) + } + // Ensure the config has a "type" so we know what plugin to run. + // Also catches the case where somebody put a conflist into a conf file. + if conf.Network.Type == "" { + return errors.Wrapf(ErrInvalidConfig, "network type not found in %s", confFile) + } + + confList, err = cnilibrary.ConfListFromConf(conf) + if err != nil { + return errors.Wrapf(ErrInvalidConfig, "failed to convert CNI config file %s to list: %v", confFile, err) + } + } + if len(confList.Plugins) == 0 { + return errors.Wrapf(ErrInvalidConfig, "CNI config list %s has no networks, skipping", confFile) + + } + networks = append(networks, &Network{ + cni: c.cniConfig, + config: confList, + ifName: getIfName(c.prefix, i), + }) + i++ + if i == max { + break + } + } + if len(networks) == 0 { + return errors.Wrapf(ErrCNINotInitialized, "no valid networks found in %s", c.pluginDirs) + } + c.networks = append(c.networks, networks...) + return nil +} diff --git a/vendor/github.com/containerd/go-cni/result.go b/vendor/github.com/containerd/go-cni/result.go new file mode 100644 index 000000000000..1e958dc76bc1 --- /dev/null +++ b/vendor/github.com/containerd/go-cni/result.go @@ -0,0 +1,106 @@ +/* + Copyright The containerd 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 cni + +import ( + "net" + + "github.com/containernetworking/cni/pkg/types" + "github.com/containernetworking/cni/pkg/types/current" + "github.com/pkg/errors" +) + +type IPConfig struct { + IP net.IP + Gateway net.IP +} + +type CNIResult struct { + Interfaces map[string]*Config + DNS []types.DNS + Routes []*types.Route +} + +type Config struct { + IPConfigs []*IPConfig + Mac string + Sandbox string +} + +// GetCNIResultFromResults returns a structured data containing the +// interface configuration for each of the interfaces created in the namespace. +// Conforms with +// Result: +// a) Interfaces list. Depending on the plugin, this can include the sandbox +// (eg, container or hypervisor) interface name and/or the host interface +// name, the hardware addresses of each interface, and details about the +// sandbox (if any) the interface is in. +// b) IP configuration assigned to each interface. The IPv4 and/or IPv6 addresses, +// gateways, and routes assigned to sandbox and/or host interfaces. +// c) DNS information. Dictionary that includes DNS information for nameservers, +// domain, search domains and options. +func (c *libcni) GetCNIResultFromResults(results []*current.Result) (*CNIResult, error) { + c.RLock() + defer c.RUnlock() + + r := &CNIResult{ + Interfaces: make(map[string]*Config), + } + + // Plugins may not need to return Interfaces in result if + // if there are no multiple interfaces created. In that case + // all configs should be applied against default interface + r.Interfaces[defaultInterface(c.prefix)] = &Config{} + + // Walk through all the results + for _, result := range results { + // Walk through all the interface in each result + for _, intf := range result.Interfaces { + r.Interfaces[intf.Name] = &Config{ + Mac: intf.Mac, + Sandbox: intf.Sandbox, + } + } + // Walk through all the IPs in the result and attach it to corresponding + // interfaces + for _, ipConf := range result.IPs { + if err := validateInterfaceConfig(ipConf, len(result.Interfaces)); err != nil { + return nil, errors.Wrapf(ErrInvalidResult, "failed to valid interface config: %v", err) + } + name := c.getInterfaceName(result.Interfaces, ipConf) + r.Interfaces[name].IPConfigs = append(r.Interfaces[name].IPConfigs, + &IPConfig{IP: ipConf.Address.IP, Gateway: ipConf.Gateway}) + } + r.DNS = append(r.DNS, result.DNS) + r.Routes = append(r.Routes, result.Routes...) + } + if _, ok := r.Interfaces[defaultInterface(c.prefix)]; !ok { + return nil, errors.Wrapf(ErrNotFound, "default network not found") + } + return r, nil +} + +// getInterfaceName returns the interface name if the plugins +// return the result with associated interfaces. If interface +// is not present then default interface name is used +func (c *libcni) getInterfaceName(interfaces []*current.Interface, + ipConf *current.IPConfig) string { + if ipConf.Interface != nil { + return interfaces[*ipConf.Interface].Name + } + return defaultInterface(c.prefix) +} diff --git a/vendor/github.com/containerd/go-cni/testutils.go b/vendor/github.com/containerd/go-cni/testutils.go new file mode 100644 index 000000000000..d9453c8d983d --- /dev/null +++ b/vendor/github.com/containerd/go-cni/testutils.go @@ -0,0 +1,78 @@ +/* + Copyright The containerd 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 cni + +import ( + "fmt" + "io/ioutil" + "os" + "path" + "testing" +) + +func makeTmpDir(prefix string) (string, error) { + tmpDir, err := ioutil.TempDir(os.TempDir(), prefix) + if err != nil { + return "", err + } + return tmpDir, nil +} + +func makeFakeCNIConfig(t *testing.T) (string, string) { + cniDir, err := makeTmpDir("fakecni") + if err != nil { + t.Fatalf("Failed to create plugin config dir: %v", err) + } + + cniConfDir := path.Join(cniDir, "net.d") + err = os.MkdirAll(cniConfDir, 0777) + if err != nil { + t.Fatalf("Failed to create network config dir: %v", err) + } + + networkConfig1 := path.Join(cniConfDir, "mocknetwork1.conf") + f1, err := os.Create(networkConfig1) + if err != nil { + t.Fatalf("Failed to create network config %v: %v", f1, err) + } + networkConfig2 := path.Join(cniConfDir, "mocknetwork2.conf") + f2, err := os.Create(networkConfig2) + if err != nil { + t.Fatalf("Failed to create network config %v: %v", f2, err) + } + + cfg1 := fmt.Sprintf(`{ "name": "%s", "type": "%s", "capabilities": {"portMappings": true} }`, "plugin1", "fakecni") + _, err = f1.WriteString(cfg1) + if err != nil { + t.Fatalf("Failed to write network config file %v: %v", f1, err) + } + f1.Close() + cfg2 := fmt.Sprintf(`{ "name": "%s", "type": "%s", "capabilities": {"portMappings": true} }`, "plugin2", "fakecni") + _, err = f2.WriteString(cfg2) + if err != nil { + t.Fatalf("Failed to write network config file %v: %v", f2, err) + } + f2.Close() + return cniDir, cniConfDir +} + +func tearDownCNIConfig(t *testing.T, confDir string) { + err := os.RemoveAll(confDir) + if err != nil { + t.Fatalf("Failed to cleanup CNI configs: %v", err) + } +} diff --git a/vendor/github.com/containerd/go-cni/types.go b/vendor/github.com/containerd/go-cni/types.go new file mode 100644 index 000000000000..8583050e48a5 --- /dev/null +++ b/vendor/github.com/containerd/go-cni/types.go @@ -0,0 +1,55 @@ +/* + Copyright The containerd 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 cni + +const ( + CNIPluginName = "cni" + DefaultNetDir = "/etc/cni/net.d" + DefaultCNIDir = "/opt/cni/bin" + DefaultMaxConfNum = 1 + VendorCNIDirTemplate = "%s/opt/%s/bin" + DefaultPrefix = "eth" +) + +type config struct { + pluginDirs []string + pluginConfDir string + pluginMaxConfNum int + prefix string +} + +type PortMapping struct { + HostPort int32 + ContainerPort int32 + Protocol string + HostIP string +} + +type IPRanges struct { + Subnet string + RangeStart string + RangeEnd string + Gateway string +} + +// BandWidth defines the ingress/egress rate and burst limits +type BandWidth struct { + IngressRate uint64 + IngressBurst uint64 + EgressRate uint64 + EgressBurst uint64 +} diff --git a/vendor/github.com/containerd/go-cni/vendor.conf b/vendor/github.com/containerd/go-cni/vendor.conf new file mode 100644 index 000000000000..aefe9a108cfc --- /dev/null +++ b/vendor/github.com/containerd/go-cni/vendor.conf @@ -0,0 +1,6 @@ +github.com/stretchr/testify b89eecf5ca5db6d3ba60b237ffe3df7bafb7662f +github.com/davecgh/go-spew 8991bc29aa16c548c550c7ff78260e27b9ab7c73 +github.com/pmezard/go-difflib 792786c7400a136282c1664665ae0a8db921c6c2 +github.com/stretchr/objx 8a3f7159479fbc75b30357fbc48f380b7320f08e +github.com/containernetworking/cni 142cde0c766cd6055cc7fdfdcb44579c0c9c35bf +github.com/pkg/errors v0.8.0 diff --git a/vendor/github.com/containernetworking/cni/LICENSE b/vendor/github.com/containernetworking/cni/LICENSE new file mode 100644 index 000000000000..8f71f43fee3f --- /dev/null +++ b/vendor/github.com/containernetworking/cni/LICENSE @@ -0,0 +1,202 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/vendor/github.com/containernetworking/cni/libcni/api.go b/vendor/github.com/containernetworking/cni/libcni/api.go new file mode 100644 index 000000000000..a23cbb2c57b0 --- /dev/null +++ b/vendor/github.com/containernetworking/cni/libcni/api.go @@ -0,0 +1,219 @@ +// Copyright 2015 CNI authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package libcni + +import ( + "os" + "strings" + + "github.com/containernetworking/cni/pkg/invoke" + "github.com/containernetworking/cni/pkg/types" + "github.com/containernetworking/cni/pkg/version" +) + +type RuntimeConf struct { + ContainerID string + NetNS string + IfName string + Args [][2]string + // A dictionary of capability-specific data passed by the runtime + // to plugins as top-level keys in the 'runtimeConfig' dictionary + // of the plugin's stdin data. libcni will ensure that only keys + // in this map which match the capabilities of the plugin are passed + // to the plugin + CapabilityArgs map[string]interface{} +} + +type NetworkConfig struct { + Network *types.NetConf + Bytes []byte +} + +type NetworkConfigList struct { + Name string + CNIVersion string + Plugins []*NetworkConfig + Bytes []byte +} + +type CNI interface { + AddNetworkList(net *NetworkConfigList, rt *RuntimeConf) (types.Result, error) + DelNetworkList(net *NetworkConfigList, rt *RuntimeConf) error + + AddNetwork(net *NetworkConfig, rt *RuntimeConf) (types.Result, error) + DelNetwork(net *NetworkConfig, rt *RuntimeConf) error +} + +type CNIConfig struct { + Path []string +} + +// CNIConfig implements the CNI interface +var _ CNI = &CNIConfig{} + +func buildOneConfig(list *NetworkConfigList, orig *NetworkConfig, prevResult types.Result, rt *RuntimeConf) (*NetworkConfig, error) { + var err error + + inject := map[string]interface{}{ + "name": list.Name, + "cniVersion": list.CNIVersion, + } + // Add previous plugin result + if prevResult != nil { + inject["prevResult"] = prevResult + } + + // Ensure every config uses the same name and version + orig, err = InjectConf(orig, inject) + if err != nil { + return nil, err + } + + return injectRuntimeConfig(orig, rt) +} + +// This function takes a libcni RuntimeConf structure and injects values into +// a "runtimeConfig" dictionary in the CNI network configuration JSON that +// will be passed to the plugin on stdin. +// +// Only "capabilities arguments" passed by the runtime are currently injected. +// These capabilities arguments are filtered through the plugin's advertised +// capabilities from its config JSON, and any keys in the CapabilityArgs +// matching plugin capabilities are added to the "runtimeConfig" dictionary +// sent to the plugin via JSON on stdin. For exmaple, if the plugin's +// capabilities include "portMappings", and the CapabilityArgs map includes a +// "portMappings" key, that key and its value are added to the "runtimeConfig" +// dictionary to be passed to the plugin's stdin. +func injectRuntimeConfig(orig *NetworkConfig, rt *RuntimeConf) (*NetworkConfig, error) { + var err error + + rc := make(map[string]interface{}) + for capability, supported := range orig.Network.Capabilities { + if !supported { + continue + } + if data, ok := rt.CapabilityArgs[capability]; ok { + rc[capability] = data + } + } + + if len(rc) > 0 { + orig, err = InjectConf(orig, map[string]interface{}{"runtimeConfig": rc}) + if err != nil { + return nil, err + } + } + + return orig, nil +} + +// AddNetworkList executes a sequence of plugins with the ADD command +func (c *CNIConfig) AddNetworkList(list *NetworkConfigList, rt *RuntimeConf) (types.Result, error) { + var prevResult types.Result + for _, net := range list.Plugins { + pluginPath, err := invoke.FindInPath(net.Network.Type, c.Path) + if err != nil { + return nil, err + } + + newConf, err := buildOneConfig(list, net, prevResult, rt) + if err != nil { + return nil, err + } + + prevResult, err = invoke.ExecPluginWithResult(pluginPath, newConf.Bytes, c.args("ADD", rt)) + if err != nil { + return nil, err + } + } + + return prevResult, nil +} + +// DelNetworkList executes a sequence of plugins with the DEL command +func (c *CNIConfig) DelNetworkList(list *NetworkConfigList, rt *RuntimeConf) error { + for i := len(list.Plugins) - 1; i >= 0; i-- { + net := list.Plugins[i] + + pluginPath, err := invoke.FindInPath(net.Network.Type, c.Path) + if err != nil { + return err + } + + newConf, err := buildOneConfig(list, net, nil, rt) + if err != nil { + return err + } + + if err := invoke.ExecPluginWithoutResult(pluginPath, newConf.Bytes, c.args("DEL", rt)); err != nil { + return err + } + } + + return nil +} + +// AddNetwork executes the plugin with the ADD command +func (c *CNIConfig) AddNetwork(net *NetworkConfig, rt *RuntimeConf) (types.Result, error) { + pluginPath, err := invoke.FindInPath(net.Network.Type, c.Path) + if err != nil { + return nil, err + } + + net, err = injectRuntimeConfig(net, rt) + if err != nil { + return nil, err + } + + return invoke.ExecPluginWithResult(pluginPath, net.Bytes, c.args("ADD", rt)) +} + +// DelNetwork executes the plugin with the DEL command +func (c *CNIConfig) DelNetwork(net *NetworkConfig, rt *RuntimeConf) error { + pluginPath, err := invoke.FindInPath(net.Network.Type, c.Path) + if err != nil { + return err + } + + net, err = injectRuntimeConfig(net, rt) + if err != nil { + return err + } + + return invoke.ExecPluginWithoutResult(pluginPath, net.Bytes, c.args("DEL", rt)) +} + +// GetVersionInfo reports which versions of the CNI spec are supported by +// the given plugin. +func (c *CNIConfig) GetVersionInfo(pluginType string) (version.PluginInfo, error) { + pluginPath, err := invoke.FindInPath(pluginType, c.Path) + if err != nil { + return nil, err + } + + return invoke.GetVersionInfo(pluginPath) +} + +// ===== +func (c *CNIConfig) args(action string, rt *RuntimeConf) *invoke.Args { + return &invoke.Args{ + Command: action, + ContainerID: rt.ContainerID, + NetNS: rt.NetNS, + PluginArgs: rt.Args, + IfName: rt.IfName, + Path: strings.Join(c.Path, string(os.PathListSeparator)), + } +} diff --git a/vendor/github.com/containernetworking/cni/libcni/conf.go b/vendor/github.com/containernetworking/cni/libcni/conf.go new file mode 100644 index 000000000000..9834d715b59f --- /dev/null +++ b/vendor/github.com/containernetworking/cni/libcni/conf.go @@ -0,0 +1,259 @@ +// Copyright 2015 CNI authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package libcni + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "sort" +) + +type NotFoundError struct { + Dir string + Name string +} + +func (e NotFoundError) Error() string { + return fmt.Sprintf(`no net configuration with name "%s" in %s`, e.Name, e.Dir) +} + +type NoConfigsFoundError struct { + Dir string +} + +func (e NoConfigsFoundError) Error() string { + return fmt.Sprintf(`no net configurations found in %s`, e.Dir) +} + +func ConfFromBytes(bytes []byte) (*NetworkConfig, error) { + conf := &NetworkConfig{Bytes: bytes} + if err := json.Unmarshal(bytes, &conf.Network); err != nil { + return nil, fmt.Errorf("error parsing configuration: %s", err) + } + if conf.Network.Type == "" { + return nil, fmt.Errorf("error parsing configuration: missing 'type'") + } + return conf, nil +} + +func ConfFromFile(filename string) (*NetworkConfig, error) { + bytes, err := ioutil.ReadFile(filename) + if err != nil { + return nil, fmt.Errorf("error reading %s: %s", filename, err) + } + return ConfFromBytes(bytes) +} + +func ConfListFromBytes(bytes []byte) (*NetworkConfigList, error) { + rawList := make(map[string]interface{}) + if err := json.Unmarshal(bytes, &rawList); err != nil { + return nil, fmt.Errorf("error parsing configuration list: %s", err) + } + + rawName, ok := rawList["name"] + if !ok { + return nil, fmt.Errorf("error parsing configuration list: no name") + } + name, ok := rawName.(string) + if !ok { + return nil, fmt.Errorf("error parsing configuration list: invalid name type %T", rawName) + } + + var cniVersion string + rawVersion, ok := rawList["cniVersion"] + if ok { + cniVersion, ok = rawVersion.(string) + if !ok { + return nil, fmt.Errorf("error parsing configuration list: invalid cniVersion type %T", rawVersion) + } + } + + list := &NetworkConfigList{ + Name: name, + CNIVersion: cniVersion, + Bytes: bytes, + } + + var plugins []interface{} + plug, ok := rawList["plugins"] + if !ok { + return nil, fmt.Errorf("error parsing configuration list: no 'plugins' key") + } + plugins, ok = plug.([]interface{}) + if !ok { + return nil, fmt.Errorf("error parsing configuration list: invalid 'plugins' type %T", plug) + } + if len(plugins) == 0 { + return nil, fmt.Errorf("error parsing configuration list: no plugins in list") + } + + for i, conf := range plugins { + newBytes, err := json.Marshal(conf) + if err != nil { + return nil, fmt.Errorf("Failed to marshal plugin config %d: %v", i, err) + } + netConf, err := ConfFromBytes(newBytes) + if err != nil { + return nil, fmt.Errorf("Failed to parse plugin config %d: %v", i, err) + } + list.Plugins = append(list.Plugins, netConf) + } + + return list, nil +} + +func ConfListFromFile(filename string) (*NetworkConfigList, error) { + bytes, err := ioutil.ReadFile(filename) + if err != nil { + return nil, fmt.Errorf("error reading %s: %s", filename, err) + } + return ConfListFromBytes(bytes) +} + +func ConfFiles(dir string, extensions []string) ([]string, error) { + // In part, adapted from rkt/networking/podenv.go#listFiles + files, err := ioutil.ReadDir(dir) + switch { + case err == nil: // break + case os.IsNotExist(err): + return nil, nil + default: + return nil, err + } + + confFiles := []string{} + for _, f := range files { + if f.IsDir() { + continue + } + fileExt := filepath.Ext(f.Name()) + for _, ext := range extensions { + if fileExt == ext { + confFiles = append(confFiles, filepath.Join(dir, f.Name())) + } + } + } + return confFiles, nil +} + +func LoadConf(dir, name string) (*NetworkConfig, error) { + files, err := ConfFiles(dir, []string{".conf", ".json"}) + switch { + case err != nil: + return nil, err + case len(files) == 0: + return nil, NoConfigsFoundError{Dir: dir} + } + sort.Strings(files) + + for _, confFile := range files { + conf, err := ConfFromFile(confFile) + if err != nil { + return nil, err + } + if conf.Network.Name == name { + return conf, nil + } + } + return nil, NotFoundError{dir, name} +} + +func LoadConfList(dir, name string) (*NetworkConfigList, error) { + files, err := ConfFiles(dir, []string{".conflist"}) + if err != nil { + return nil, err + } + sort.Strings(files) + + for _, confFile := range files { + conf, err := ConfListFromFile(confFile) + if err != nil { + return nil, err + } + if conf.Name == name { + return conf, nil + } + } + + // Try and load a network configuration file (instead of list) + // from the same name, then upconvert. + singleConf, err := LoadConf(dir, name) + if err != nil { + // A little extra logic so the error makes sense + if _, ok := err.(NoConfigsFoundError); len(files) != 0 && ok { + // Config lists found but no config files found + return nil, NotFoundError{dir, name} + } + + return nil, err + } + return ConfListFromConf(singleConf) +} + +func InjectConf(original *NetworkConfig, newValues map[string]interface{}) (*NetworkConfig, error) { + config := make(map[string]interface{}) + err := json.Unmarshal(original.Bytes, &config) + if err != nil { + return nil, fmt.Errorf("unmarshal existing network bytes: %s", err) + } + + for key, value := range newValues { + if key == "" { + return nil, fmt.Errorf("keys cannot be empty") + } + + if value == nil { + return nil, fmt.Errorf("key '%s' value must not be nil", key) + } + + config[key] = value + } + + newBytes, err := json.Marshal(config) + if err != nil { + return nil, err + } + + return ConfFromBytes(newBytes) +} + +// ConfListFromConf "upconverts" a network config in to a NetworkConfigList, +// with the single network as the only entry in the list. +func ConfListFromConf(original *NetworkConfig) (*NetworkConfigList, error) { + // Re-deserialize the config's json, then make a raw map configlist. + // This may seem a bit strange, but it's to make the Bytes fields + // actually make sense. Otherwise, the generated json is littered with + // golang default values. + + rawConfig := make(map[string]interface{}) + if err := json.Unmarshal(original.Bytes, &rawConfig); err != nil { + return nil, err + } + + rawConfigList := map[string]interface{}{ + "name": original.Network.Name, + "cniVersion": original.Network.CNIVersion, + "plugins": []interface{}{rawConfig}, + } + + b, err := json.Marshal(rawConfigList) + if err != nil { + return nil, err + } + return ConfListFromBytes(b) +} diff --git a/vendor/github.com/containernetworking/cni/pkg/invoke/args.go b/vendor/github.com/containernetworking/cni/pkg/invoke/args.go new file mode 100644 index 000000000000..39b639723051 --- /dev/null +++ b/vendor/github.com/containernetworking/cni/pkg/invoke/args.go @@ -0,0 +1,82 @@ +// Copyright 2015 CNI authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package invoke + +import ( + "os" + "strings" +) + +type CNIArgs interface { + // For use with os/exec; i.e., return nil to inherit the + // environment from this process + AsEnv() []string +} + +type inherited struct{} + +var inheritArgsFromEnv inherited + +func (_ *inherited) AsEnv() []string { + return nil +} + +func ArgsFromEnv() CNIArgs { + return &inheritArgsFromEnv +} + +type Args struct { + Command string + ContainerID string + NetNS string + PluginArgs [][2]string + PluginArgsStr string + IfName string + Path string +} + +// Args implements the CNIArgs interface +var _ CNIArgs = &Args{} + +func (args *Args) AsEnv() []string { + env := os.Environ() + pluginArgsStr := args.PluginArgsStr + if pluginArgsStr == "" { + pluginArgsStr = stringify(args.PluginArgs) + } + + // 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 +} + +// taken from rkt/networking/net_plugin.go +func stringify(pluginArgs [][2]string) string { + entries := make([]string, len(pluginArgs)) + + for i, kv := range pluginArgs { + entries[i] = strings.Join(kv[:], "=") + } + + return strings.Join(entries, ";") +} diff --git a/vendor/github.com/containernetworking/cni/pkg/invoke/delegate.go b/vendor/github.com/containernetworking/cni/pkg/invoke/delegate.go new file mode 100644 index 000000000000..c78a69eebc8e --- /dev/null +++ b/vendor/github.com/containernetworking/cni/pkg/invoke/delegate.go @@ -0,0 +1,53 @@ +// Copyright 2016 CNI authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package invoke + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/containernetworking/cni/pkg/types" +) + +func DelegateAdd(delegatePlugin string, netconf []byte) (types.Result, error) { + if os.Getenv("CNI_COMMAND") != "ADD" { + return nil, fmt.Errorf("CNI_COMMAND is not ADD") + } + + paths := filepath.SplitList(os.Getenv("CNI_PATH")) + + pluginPath, err := FindInPath(delegatePlugin, paths) + if err != nil { + return nil, err + } + + return ExecPluginWithResult(pluginPath, netconf, ArgsFromEnv()) +} + +func DelegateDel(delegatePlugin string, netconf []byte) error { + if os.Getenv("CNI_COMMAND") != "DEL" { + return fmt.Errorf("CNI_COMMAND is not DEL") + } + + paths := filepath.SplitList(os.Getenv("CNI_PATH")) + + pluginPath, err := FindInPath(delegatePlugin, paths) + if err != nil { + return err + } + + return ExecPluginWithoutResult(pluginPath, netconf, ArgsFromEnv()) +} diff --git a/vendor/github.com/containernetworking/cni/pkg/invoke/exec.go b/vendor/github.com/containernetworking/cni/pkg/invoke/exec.go new file mode 100644 index 000000000000..fc47e7c8253f --- /dev/null +++ b/vendor/github.com/containernetworking/cni/pkg/invoke/exec.go @@ -0,0 +1,95 @@ +// Copyright 2015 CNI authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package invoke + +import ( + "fmt" + "os" + + "github.com/containernetworking/cni/pkg/types" + "github.com/containernetworking/cni/pkg/version" +) + +func ExecPluginWithResult(pluginPath string, netconf []byte, args CNIArgs) (types.Result, error) { + return defaultPluginExec.WithResult(pluginPath, netconf, args) +} + +func ExecPluginWithoutResult(pluginPath string, netconf []byte, args CNIArgs) error { + return defaultPluginExec.WithoutResult(pluginPath, netconf, args) +} + +func GetVersionInfo(pluginPath string) (version.PluginInfo, error) { + return defaultPluginExec.GetVersionInfo(pluginPath) +} + +var defaultPluginExec = &PluginExec{ + RawExec: &RawExec{Stderr: os.Stderr}, + VersionDecoder: &version.PluginDecoder{}, +} + +type PluginExec struct { + RawExec interface { + ExecPlugin(pluginPath string, stdinData []byte, environ []string) ([]byte, error) + } + VersionDecoder interface { + Decode(jsonBytes []byte) (version.PluginInfo, error) + } +} + +func (e *PluginExec) WithResult(pluginPath string, netconf []byte, args CNIArgs) (types.Result, error) { + stdoutBytes, err := e.RawExec.ExecPlugin(pluginPath, netconf, args.AsEnv()) + if err != nil { + return nil, err + } + + // Plugin must return result in same version as specified in netconf + versionDecoder := &version.ConfigDecoder{} + confVersion, err := versionDecoder.Decode(netconf) + if err != nil { + return nil, err + } + + return version.NewResult(confVersion, stdoutBytes) +} + +func (e *PluginExec) WithoutResult(pluginPath string, netconf []byte, args CNIArgs) error { + _, err := e.RawExec.ExecPlugin(pluginPath, netconf, args.AsEnv()) + return err +} + +// GetVersionInfo returns the version information available about the plugin. +// For recent-enough plugins, it uses the information returned by the VERSION +// command. For older plugins which do not recognize that command, it reports +// version 0.1.0 +func (e *PluginExec) GetVersionInfo(pluginPath string) (version.PluginInfo, error) { + args := &Args{ + Command: "VERSION", + + // set fake values required by plugins built against an older version of skel + NetNS: "dummy", + IfName: "dummy", + Path: "dummy", + } + stdin := []byte(fmt.Sprintf(`{"cniVersion":%q}`, version.Current())) + stdoutBytes, err := e.RawExec.ExecPlugin(pluginPath, stdin, args.AsEnv()) + if err != nil { + if err.Error() == "unknown CNI_COMMAND: VERSION" { + return version.PluginSupports("0.1.0"), nil + } + return nil, err + } + + return e.VersionDecoder.Decode(stdoutBytes) +} diff --git a/vendor/github.com/containernetworking/cni/pkg/invoke/find.go b/vendor/github.com/containernetworking/cni/pkg/invoke/find.go new file mode 100644 index 000000000000..e815404c8591 --- /dev/null +++ b/vendor/github.com/containernetworking/cni/pkg/invoke/find.go @@ -0,0 +1,43 @@ +// Copyright 2015 CNI authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package invoke + +import ( + "fmt" + "os" + "path/filepath" +) + +// FindInPath returns the full path of the plugin by searching in the provided path +func FindInPath(plugin string, paths []string) (string, error) { + if plugin == "" { + return "", fmt.Errorf("no plugin name provided") + } + + if len(paths) == 0 { + return "", fmt.Errorf("no paths provided") + } + + for _, path := range paths { + for _, fe := range ExecutableFileExtensions { + fullpath := filepath.Join(path, plugin) + fe + if fi, err := os.Stat(fullpath); err == nil && fi.Mode().IsRegular() { + return fullpath, nil + } + } + } + + return "", fmt.Errorf("failed to find plugin %q in path %s", plugin, paths) +} 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 000000000000..bab5737a99d6 --- /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 000000000000..7665125b133c --- /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 new file mode 100644 index 000000000000..93f1e75d9fa7 --- /dev/null +++ b/vendor/github.com/containernetworking/cni/pkg/invoke/raw_exec.go @@ -0,0 +1,59 @@ +// Copyright 2016 CNI authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package invoke + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "os/exec" + + "github.com/containernetworking/cni/pkg/types" +) + +type RawExec struct { + Stderr io.Writer +} + +func (e *RawExec) ExecPlugin(pluginPath string, stdinData []byte, environ []string) ([]byte, error) { + stdout := &bytes.Buffer{} + + c := exec.Cmd{ + Env: environ, + Path: pluginPath, + Args: []string{pluginPath}, + Stdin: bytes.NewBuffer(stdinData), + Stdout: stdout, + Stderr: e.Stderr, + } + if err := c.Run(); err != nil { + return nil, pluginErr(err, stdout.Bytes()) + } + + return stdout.Bytes(), nil +} + +func pluginErr(err error, output []byte) error { + if _, ok := err.(*exec.ExitError); ok { + emsg := types.Error{} + if perr := json.Unmarshal(output, &emsg); perr != nil { + emsg.Msg = fmt.Sprintf("netplugin failed but error parsing its diagnostic message %q: %v", string(output), perr) + } + 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 000000000000..2833aba787f7 --- /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 new file mode 100644 index 000000000000..bd8640fc969a --- /dev/null +++ b/vendor/github.com/containernetworking/cni/pkg/types/args.go @@ -0,0 +1,112 @@ +// Copyright 2015 CNI authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package types + +import ( + "encoding" + "fmt" + "reflect" + "strings" +) + +// UnmarshallableBool typedef for builtin bool +// because builtin type's methods can't be declared +type UnmarshallableBool bool + +// UnmarshalText implements the encoding.TextUnmarshaler interface. +// Returns boolean true if the string is "1" or "[Tt]rue" +// Returns boolean false if the string is "0" or "[Ff]alse" +func (b *UnmarshallableBool) UnmarshalText(data []byte) error { + s := strings.ToLower(string(data)) + switch s { + case "1", "true": + *b = true + case "0", "false": + *b = false + default: + return fmt.Errorf("Boolean unmarshal error: invalid input %s", s) + } + return nil +} + +// UnmarshallableString typedef for builtin string +type UnmarshallableString string + +// UnmarshalText implements the encoding.TextUnmarshaler interface. +// Returns the string +func (s *UnmarshallableString) UnmarshalText(data []byte) error { + *s = UnmarshallableString(data) + return nil +} + +// CommonArgs contains the IgnoreUnknown argument +// and must be embedded by all Arg structs +type CommonArgs struct { + IgnoreUnknown UnmarshallableBool `json:"ignoreunknown,omitempty"` +} + +// GetKeyField is a helper function to receive Values +// Values that represent a pointer to a struct +func GetKeyField(keyString string, v reflect.Value) reflect.Value { + return v.Elem().FieldByName(keyString) +} + +// 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 == "" { + return nil + } + + containerValue := reflect.ValueOf(container) + + pairs := strings.Split(args, ";") + unknownArgs := []string{} + for _, pair := range pairs { + kv := strings.Split(pair, "=") + if len(kv) != 2 { + return fmt.Errorf("ARGS: invalid pair %q", pair) + } + keyString := kv[0] + valueString := kv[1] + keyField := GetKeyField(keyString, containerValue) + if !keyField.IsValid() { + unknownArgs = append(unknownArgs, pair) + continue + } + 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) + } + } + + isIgnoreUnknown := GetKeyField("IgnoreUnknown", containerValue).Bool() + if len(unknownArgs) > 0 && !isIgnoreUnknown { + return fmt.Errorf("ARGS: unknown args %q", unknownArgs) + } + return nil +} 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 000000000000..caac92ba77ca --- /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 new file mode 100644 index 000000000000..4684a320762f --- /dev/null +++ b/vendor/github.com/containernetworking/cni/pkg/types/types.go @@ -0,0 +1,191 @@ +// Copyright 2015 CNI authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package types + +import ( + "encoding/json" + "errors" + "fmt" + "net" + "os" +) + +// like net.IPNet but adds JSON marshalling and unmarshalling +type IPNet net.IPNet + +// ParseCIDR takes a string like "10.2.3.1/24" and +// return IPNet with "10.2.3.1" and /24 mask +func ParseCIDR(s string) (*net.IPNet, error) { + ip, ipn, err := net.ParseCIDR(s) + if err != nil { + return nil, err + } + + ipn.IP = ip + return ipn, nil +} + +func (n IPNet) MarshalJSON() ([]byte, error) { + return json.Marshal((*net.IPNet)(&n).String()) +} + +func (n *IPNet) UnmarshalJSON(data []byte) error { + var s string + if err := json.Unmarshal(data, &s); err != nil { + return err + } + + tmp, err := ParseCIDR(s) + if err != nil { + return err + } + + *n = IPNet(*tmp) + return nil +} + +// NetConf describes a network. +type NetConf struct { + CNIVersion string `json:"cniVersion,omitempty"` + + Name string `json:"name,omitempty"` + Type string `json:"type,omitempty"` + Capabilities map[string]bool `json:"capabilities,omitempty"` + IPAM IPAM `json:"ipam,omitempty"` + DNS DNS `json:"dns"` +} + +type IPAM struct { + Type string `json:"type,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"` +} + +type ResultFactoryFunc func([]byte) (Result, error) + +// Result is an interface that provides the result of plugin execution +type Result interface { + // The highest CNI specification result verison the result supports + // without having to convert + Version() string + + // Returns the result converted into the requested CNI specification + // result version, or an error if conversion failed + GetAsVersion(version string) (Result, error) + + // Prints the result in JSON format to stdout + Print() error + + // Returns a JSON string representation of the result + String() string +} + +func PrintResult(result Result, version string) error { + newResult, err := result.GetAsVersion(version) + if err != nil { + return err + } + return newResult.Print() +} + +// DNS contains values interesting for DNS resolvers +type DNS struct { + Nameservers []string `json:"nameservers,omitempty"` + Domain string `json:"domain,omitempty"` + Search []string `json:"search,omitempty"` + Options []string `json:"options,omitempty"` +} + +type Route struct { + Dst net.IPNet + GW net.IP +} + +func (r *Route) String() string { + return fmt.Sprintf("%+v", *r) +} + +// Well known error codes +// see https://github.com/containernetworking/cni/blob/master/SPEC.md#well-known-error-codes +const ( + ErrUnknown uint = iota // 0 + ErrIncompatibleCNIVersion // 1 + ErrUnsupportedField // 2 +) + +type Error struct { + Code uint `json:"code"` + Msg string `json:"msg"` + Details string `json:"details,omitempty"` +} + +func (e *Error) Error() string { + details := "" + if e.Details != "" { + details = fmt.Sprintf("; %v", e.Details) + } + return fmt.Sprintf("%v%v", e.Msg, details) +} + +func (e *Error) Print() error { + return prettyPrint(e) +} + +// net.IPNet is not JSON (un)marshallable so this duality is needed +// for our custom IPNet type + +// JSON (un)marshallable types +type route struct { + Dst IPNet `json:"dst"` + GW net.IP `json:"gw,omitempty"` +} + +func (r *Route) UnmarshalJSON(data []byte) error { + rt := route{} + if err := json.Unmarshal(data, &rt); err != nil { + return err + } + + r.Dst = net.IPNet(rt.Dst) + r.GW = rt.GW + return nil +} + +func (r Route) MarshalJSON() ([]byte, error) { + rt := route{ + Dst: IPNet(r.Dst), + GW: r.GW, + } + + return json.Marshal(rt) +} + +func prettyPrint(obj interface{}) error { + data, err := json.MarshalIndent(obj, "", " ") + if err != nil { + return err + } + _, err = os.Stdout.Write(data) + return err +} + +// NotImplementedError is used to indicate that a method is not implemented for the given platform +var NotImplementedError = errors.New("Not Implemented") diff --git a/vendor/github.com/containernetworking/cni/pkg/version/conf.go b/vendor/github.com/containernetworking/cni/pkg/version/conf.go new file mode 100644 index 000000000000..3cca58bbeb8d --- /dev/null +++ b/vendor/github.com/containernetworking/cni/pkg/version/conf.go @@ -0,0 +1,37 @@ +// Copyright 2016 CNI authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package version + +import ( + "encoding/json" + "fmt" +) + +// ConfigDecoder can decode the CNI version available in network config data +type ConfigDecoder struct{} + +func (*ConfigDecoder) Decode(jsonBytes []byte) (string, error) { + var conf struct { + CNIVersion string `json:"cniVersion"` + } + err := json.Unmarshal(jsonBytes, &conf) + if err != nil { + return "", fmt.Errorf("decoding version from network config: %s", err) + } + if conf.CNIVersion == "" { + return "0.1.0", nil + } + return conf.CNIVersion, nil +} diff --git a/vendor/github.com/containernetworking/cni/pkg/version/plugin.go b/vendor/github.com/containernetworking/cni/pkg/version/plugin.go new file mode 100644 index 000000000000..8a46728105cc --- /dev/null +++ b/vendor/github.com/containernetworking/cni/pkg/version/plugin.go @@ -0,0 +1,81 @@ +// Copyright 2016 CNI authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package version + +import ( + "encoding/json" + "fmt" + "io" +) + +// PluginInfo reports information about CNI versioning +type PluginInfo interface { + // SupportedVersions returns one or more CNI spec versions that the plugin + // supports. If input is provided in one of these versions, then the plugin + // promises to use the same CNI version in its response + SupportedVersions() []string + + // Encode writes this CNI version information as JSON to the given Writer + Encode(io.Writer) error +} + +type pluginInfo struct { + CNIVersion_ string `json:"cniVersion"` + SupportedVersions_ []string `json:"supportedVersions,omitempty"` +} + +// pluginInfo implements the PluginInfo interface +var _ PluginInfo = &pluginInfo{} + +func (p *pluginInfo) Encode(w io.Writer) error { + return json.NewEncoder(w).Encode(p) +} + +func (p *pluginInfo) SupportedVersions() []string { + return p.SupportedVersions_ +} + +// PluginSupports returns a new PluginInfo that will report the given versions +// as supported +func PluginSupports(supportedVersions ...string) PluginInfo { + if len(supportedVersions) < 1 { + panic("programmer error: you must support at least one version") + } + return &pluginInfo{ + CNIVersion_: Current(), + SupportedVersions_: supportedVersions, + } +} + +// PluginDecoder can decode the response returned by a plugin's VERSION command +type PluginDecoder struct{} + +func (*PluginDecoder) Decode(jsonBytes []byte) (PluginInfo, error) { + var info pluginInfo + err := json.Unmarshal(jsonBytes, &info) + if err != nil { + return nil, fmt.Errorf("decoding version info: %s", err) + } + if info.CNIVersion_ == "" { + return nil, fmt.Errorf("decoding version info: missing field cniVersion") + } + if len(info.SupportedVersions_) == 0 { + if info.CNIVersion_ == "0.2.0" { + return PluginSupports("0.1.0", "0.2.0"), nil + } + return nil, fmt.Errorf("decoding version info: missing field supportedVersions") + } + return &info, nil +} diff --git a/vendor/github.com/containernetworking/cni/pkg/version/reconcile.go b/vendor/github.com/containernetworking/cni/pkg/version/reconcile.go new file mode 100644 index 000000000000..25c3810b2aaf --- /dev/null +++ b/vendor/github.com/containernetworking/cni/pkg/version/reconcile.go @@ -0,0 +1,49 @@ +// Copyright 2016 CNI authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package version + +import "fmt" + +type ErrorIncompatible struct { + Config string + Supported []string +} + +func (e *ErrorIncompatible) Details() string { + return fmt.Sprintf("config is %q, plugin supports %q", e.Config, e.Supported) +} + +func (e *ErrorIncompatible) Error() string { + return fmt.Sprintf("incompatible CNI versions: %s", e.Details()) +} + +type Reconciler struct{} + +func (r *Reconciler) Check(configVersion string, pluginInfo PluginInfo) *ErrorIncompatible { + return r.CheckRaw(configVersion, pluginInfo.SupportedVersions()) +} + +func (*Reconciler) CheckRaw(configVersion string, supportedVersions []string) *ErrorIncompatible { + for _, supportedVersion := range supportedVersions { + if configVersion == supportedVersion { + return nil + } + } + + return &ErrorIncompatible{ + Config: configVersion, + Supported: supportedVersions, + } +} diff --git a/vendor/github.com/containernetworking/cni/pkg/version/version.go b/vendor/github.com/containernetworking/cni/pkg/version/version.go new file mode 100644 index 000000000000..efe8ea8716df --- /dev/null +++ b/vendor/github.com/containernetworking/cni/pkg/version/version.go @@ -0,0 +1,61 @@ +// Copyright 2016 CNI authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package version + +import ( + "fmt" + + "github.com/containernetworking/cni/pkg/types" + "github.com/containernetworking/cni/pkg/types/020" + "github.com/containernetworking/cni/pkg/types/current" +) + +// Current reports the version of the CNI spec implemented by this library +func Current() string { + return "0.3.1" +} + +// Legacy PluginInfo describes a plugin that is backwards compatible with the +// CNI spec version 0.1.0. In particular, a runtime compiled against the 0.1.0 +// library ought to work correctly with a plugin that reports support for +// Legacy versions. +// +// Any future CNI spec versions which meet this definition should be added to +// this list. +var Legacy = PluginSupports("0.1.0", "0.2.0") +var All = PluginSupports("0.1.0", "0.2.0", "0.3.0", "0.3.1") + +var resultFactories = []struct { + supportedVersions []string + newResult types.ResultFactoryFunc +}{ + {current.SupportedVersions, current.NewResult}, + {types020.SupportedVersions, types020.NewResult}, +} + +// Finds a Result object matching the requested version (if any) and asks +// that object to parse the plugin result, returning an error if parsing failed. +func NewResult(version string, resultBytes []byte) (types.Result, error) { + reconciler := &Reconciler{} + for _, resultFactory := range resultFactories { + err := reconciler.CheckRaw(version, resultFactory.supportedVersions) + if err == nil { + // Result supports this version + return resultFactory.newResult(resultBytes) + } + } + + return nil, fmt.Errorf("unsupported CNI result version %q", version) +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 889c9fbe52ef..38eb4b9226e1 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -100,12 +100,21 @@ github.com/containerd/continuity/driver github.com/containerd/continuity/proto # github.com/containerd/fifo v0.0.0-20180307165137-3d5202aec260 github.com/containerd/fifo +# github.com/containerd/go-cni v0.0.0-20190610170741-5a4663dad645 +github.com/containerd/go-cni # github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3 github.com/containerd/go-runc # github.com/containerd/ttrpc v0.0.0-20190411181408-699c4e40d1e7 github.com/containerd/ttrpc # github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd github.com/containerd/typeurl +# github.com/containernetworking/cni v0.6.1-0.20180218032124-142cde0c766c +github.com/containernetworking/cni/libcni +github.com/containernetworking/cni/pkg/types +github.com/containernetworking/cni/pkg/types/current +github.com/containernetworking/cni/pkg/invoke +github.com/containernetworking/cni/pkg/version +github.com/containernetworking/cni/pkg/types/020 # github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e github.com/coreos/go-systemd/daemon # github.com/davecgh/go-spew v1.1.1 diff --git a/worker/containerd/containerd.go b/worker/containerd/containerd.go index 936f2cb26506..7d62642002e0 100644 --- a/worker/containerd/containerd.go +++ b/worker/containerd/containerd.go @@ -27,16 +27,16 @@ import ( ) // NewWorkerOpt creates a WorkerOpt. -func NewWorkerOpt(root string, address, snapshotterName, ns string, labels map[string]string, dns *oci.DNSConfig, opts ...containerd.ClientOpt) (base.WorkerOpt, error) { +func NewWorkerOpt(root string, address, snapshotterName, ns string, labels map[string]string, dns *oci.DNSConfig, nopt network.Opt, opts ...containerd.ClientOpt) (base.WorkerOpt, error) { opts = append(opts, containerd.WithDefaultNamespace(ns)) client, err := containerd.New(address, opts...) if err != nil { return base.WorkerOpt{}, errors.Wrapf(err, "failed to connect client to %q . make sure containerd is running", address) } - return newContainerd(root, client, snapshotterName, ns, labels, dns) + return newContainerd(root, client, snapshotterName, ns, labels, dns, nopt) } -func newContainerd(root string, client *containerd.Client, snapshotterName, ns string, labels map[string]string, dns *oci.DNSConfig) (base.WorkerOpt, error) { +func newContainerd(root string, client *containerd.Client, snapshotterName, ns string, labels map[string]string, dns *oci.DNSConfig, nopt network.Opt) (base.WorkerOpt, error) { if strings.Contains(snapshotterName, "/") { return base.WorkerOpt{}, errors.Errorf("bad snapshotter name: %q", snapshotterName) } @@ -103,11 +103,16 @@ func newContainerd(root string, client *containerd.Client, snapshotterName, ns s } } + np, err := network.Providers(nopt) + if err != nil { + return base.WorkerOpt{}, err + } + opt := base.WorkerOpt{ ID: id, Labels: xlabels, MetadataStore: md, - Executor: containerdexecutor.New(client, root, "", network.Default(), dns), + Executor: containerdexecutor.New(client, root, "", np, dns), Snapshotter: containerdsnapshot.NewSnapshotter(snapshotterName, client.SnapshotService(snapshotterName), cs, md, ns, gc, nil), ContentStore: cs, Applier: winlayers.NewFileSystemApplierWithWindows(cs, df), diff --git a/worker/runc/runc.go b/worker/runc/runc.go index 34ce4f0105a9..ddf075720f5d 100644 --- a/worker/runc/runc.go +++ b/worker/runc/runc.go @@ -34,7 +34,7 @@ type SnapshotterFactory struct { } // NewWorkerOpt creates a WorkerOpt. -func NewWorkerOpt(root string, snFactory SnapshotterFactory, rootless bool, processMode oci.ProcessMode, labels map[string]string, idmap *idtools.IdentityMapping, dns *oci.DNSConfig) (base.WorkerOpt, error) { +func NewWorkerOpt(root string, snFactory SnapshotterFactory, rootless bool, processMode oci.ProcessMode, labels map[string]string, idmap *idtools.IdentityMapping, nopt network.Opt, dns *oci.DNSConfig) (base.WorkerOpt, error) { var opt base.WorkerOpt name := "runc-" + snFactory.Name root = filepath.Join(root, name) @@ -45,6 +45,12 @@ func NewWorkerOpt(root string, snFactory SnapshotterFactory, rootless bool, proc if err != nil { return opt, err } + + np, err := network.Providers(nopt) + if err != nil { + return opt, err + } + exe, err := runcexecutor.New(runcexecutor.Opt{ // Root directory Root: filepath.Join(root, "executor"), @@ -53,7 +59,7 @@ func NewWorkerOpt(root string, snFactory SnapshotterFactory, rootless bool, proc ProcessMode: processMode, IdentityMapping: idmap, DNS: dns, - }, network.Default()) + }, np) if err != nil { return opt, err } From e7759a861b771943e422f9930f8dd2a387babf28 Mon Sep 17 00:00:00 2001 From: Tonis Tiigi <tonistiigi@gmail.com> Date: Wed, 10 Jul 2019 16:55:04 -0700 Subject: [PATCH 2/6] client: add cni network tests Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com> --- client/client_test.go | 80 +++++++++++++++++++++++ cmd/buildkitd/main_oci_worker.go | 3 + hack/dockerfiles/test.buildkit.Dockerfile | 5 +- hack/fixtures/cni.json | 13 ++++ util/testutil/echoserver/server.go | 33 ++++++++++ worker/runc/runc_test.go | 3 +- 6 files changed, 134 insertions(+), 3 deletions(-) create mode 100644 hack/fixtures/cni.json create mode 100644 util/testutil/echoserver/server.go diff --git a/client/client_test.go b/client/client_test.go index 1de618f67df1..9445db16142f 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -36,6 +36,7 @@ import ( "github.com/moby/buildkit/util/contentutil" "github.com/moby/buildkit/util/entitlements" "github.com/moby/buildkit/util/testutil" + "github.com/moby/buildkit/util/testutil/echoserver" "github.com/moby/buildkit/util/testutil/httpserver" "github.com/moby/buildkit/util/testutil/integration" ocispec "github.com/opencontainers/image-spec/specs-go/v1" @@ -97,6 +98,7 @@ func TestClientIntegration(t *testing.T) { testPushByDigest, testBasicInlineCacheImportExport, testExportBusyboxLocal, + testBridgeNetworking, }, mirrors) integration.Run(t, []integration.Test{ @@ -109,12 +111,75 @@ func TestClientIntegration(t *testing.T) { "insecure": securityInsecure, }), ) + + integration.Run(t, []integration.Test{ + testHostNetworking, + }, + mirrors, + integration.WithMatrix("netmode", map[string]interface{}{ + "default": defaultNetwork, + "host": hostNetwork, + }), + ) } func newContainerd(cdAddress string) (*containerd.Client, error) { return containerd.New(cdAddress, containerd.WithTimeout(60*time.Second)) } +func testBridgeNetworking(t *testing.T, sb integration.Sandbox) { + if os.Getenv("BUILDKIT_RUN_NETWORK_INTEGRATION_TESTS") == "" { + t.SkipNow() + } + if sb.Rootless() { + t.SkipNow() + } + c, err := New(context.TODO(), sb.Address()) + require.NoError(t, err) + defer c.Close() + + s, err := echoserver.NewTestServer("foo") + require.NoError(t, err) + addrParts := strings.Split(s.Addr().String(), ":") + + def, err := llb.Image("busybox").Run(llb.Shlexf("sh -c 'nc 127.0.0.1 %s | grep foo'", addrParts[len(addrParts)-1])).Marshal() + require.NoError(t, err) + + _, err = c.Solve(context.TODO(), def, SolveOpt{}, nil) + require.Error(t, err) +} +func testHostNetworking(t *testing.T, sb integration.Sandbox) { + if os.Getenv("BUILDKIT_RUN_NETWORK_INTEGRATION_TESTS") == "" { + t.SkipNow() + } + netMode := sb.Value("netmode") + var allowedEntitlements []entitlements.Entitlement + if netMode == hostNetwork { + allowedEntitlements = []entitlements.Entitlement{entitlements.EntitlementNetworkHost} + } + c, err := New(context.TODO(), sb.Address()) + require.NoError(t, err) + defer c.Close() + + s, err := echoserver.NewTestServer("foo") + require.NoError(t, err) + addrParts := strings.Split(s.Addr().String(), ":") + + def, err := llb.Image("busybox").Run(llb.Shlexf("sh -c 'nc 127.0.0.1 %s | grep foo'", addrParts[len(addrParts)-1]), llb.Network(llb.NetModeHost)).Marshal() + require.NoError(t, err) + + _, err = c.Solve(context.TODO(), def, SolveOpt{ + AllowedEntitlements: allowedEntitlements, + }, nil) + if netMode == hostNetwork { + require.NoError(t, err) + t.Logf("host-noerror") + } else { + require.Error(t, err) + t.Logf("bridge-error") + } +} + // #877 func testExportBusyboxLocal(t *testing.T, sb integration.Sandbox) { c, err := New(context.TODO(), sb.Address()) @@ -2452,3 +2517,18 @@ func (*secModeInsecure) UpdateConfigFile(in string) string { var securitySandbox integration.ConfigUpdater = &secModeSandbox{} var securityInsecure integration.ConfigUpdater = &secModeInsecure{} + +type netModeHost struct{} + +func (*netModeHost) UpdateConfigFile(in string) string { + return in + "\n\ninsecure-entitlements = [\"network.host\"]\n" +} + +type netModeDefault struct{} + +func (*netModeDefault) UpdateConfigFile(in string) string { + return in +} + +var hostNetwork integration.ConfigUpdater = &netModeHost{} +var defaultNetwork integration.ConfigUpdater = &netModeDefault{} diff --git a/cmd/buildkitd/main_oci_worker.go b/cmd/buildkitd/main_oci_worker.go index c450b5a9f9fe..bd2b03e37952 100644 --- a/cmd/buildkitd/main_oci_worker.go +++ b/cmd/buildkitd/main_oci_worker.go @@ -206,6 +206,9 @@ func ociWorkerInitializer(c *cli.Context, common workerInitializerOpt) ([]worker if cfg.Rootless { logrus.Debugf("running in rootless mode") + if common.config.Workers.OCI.NetworkConfig.Mode == "auto" { + common.config.Workers.OCI.NetworkConfig.Mode = "host" + } } processMode := oci.ProcessSandbox diff --git a/hack/dockerfiles/test.buildkit.Dockerfile b/hack/dockerfiles/test.buildkit.Dockerfile index 9ef4bf256b84..08c98ae4ae3b 100644 --- a/hack/dockerfiles/test.buildkit.Dockerfile +++ b/hack/dockerfiles/test.buildkit.Dockerfile @@ -198,7 +198,7 @@ VOLUME /var/lib/containerd VOLUME /run/containerd ENTRYPOINT ["containerd"] -FROM alpine AS cni-plugins +FROM --platform=$BUILDPLATFORM alpine AS cni-plugins RUN apk add --no-cache curl ENV CNI_VERSION=v0.8.1 ARG TARGETOS @@ -220,10 +220,11 @@ COPY --from=containerd10 /out/containerd* /opt/containerd-1.0/bin/ COPY --from=registry /bin/registry /usr/bin COPY --from=runc /usr/bin/runc /usr/bin COPY --from=containerd /out/containerd* /usr/bin/ -COPY --from=cni-plugins /opt/cni/bin/bridge /opt/cni/bin/host-local /opt/cni/bin +COPY --from=cni-plugins /opt/cni/bin/bridge /opt/cni/bin/host-local /opt/cni/bin/loopback /opt/cni/bin/ COPY hack/fixtures/cni.json /etc/buildkit/cni.json COPY --from=binaries / /usr/bin/ COPY . . +ENV BUILDKIT_RUN_NETWORK_INTEGRATION_TESTS=1 FROM integration-tests AS dev-env VOLUME /var/lib/buildkit diff --git a/hack/fixtures/cni.json b/hack/fixtures/cni.json new file mode 100644 index 000000000000..682412708b06 --- /dev/null +++ b/hack/fixtures/cni.json @@ -0,0 +1,13 @@ +{ + "name": "buildkit", + "type": "bridge", + "bridge": "buildkit0", + "isDefaultGateway": true, + "forceAddress": false, + "ipMasq": true, + "hairpinMode": true, + "ipam": { + "type": "host-local", + "subnet": "10.10.0.0/16" + } +} \ No newline at end of file diff --git a/util/testutil/echoserver/server.go b/util/testutil/echoserver/server.go new file mode 100644 index 000000000000..efeac5ca38cf --- /dev/null +++ b/util/testutil/echoserver/server.go @@ -0,0 +1,33 @@ +package echoserver + +import ( + "io" + "net" +) + +type TestServer interface { + io.Closer + Addr() net.Addr +} + +func NewTestServer(response string) (TestServer, error) { + ln, err := net.Listen("tcp", ":") + if err != nil { + return nil, err + } + go func() { + for { + conn, err := ln.Accept() + if err != nil { + break + } + go handleConnection(conn, response) + } + }() + return ln, nil +} + +func handleConnection(c net.Conn, response string) { + c.Write([]byte(response)) + c.Close() +} diff --git a/worker/runc/runc_test.go b/worker/runc/runc_test.go index 4060f2441a3f..f9f483c5696e 100644 --- a/worker/runc/runc_test.go +++ b/worker/runc/runc_test.go @@ -23,6 +23,7 @@ import ( "github.com/moby/buildkit/session" "github.com/moby/buildkit/snapshot" "github.com/moby/buildkit/source" + "github.com/moby/buildkit/util/network" "github.com/moby/buildkit/worker/base" "github.com/stretchr/testify/require" ) @@ -39,7 +40,7 @@ func newWorkerOpt(t *testing.T, processMode oci.ProcessMode) (base.WorkerOpt, fu }, } rootless := false - workerOpt, err := NewWorkerOpt(tmpdir, snFactory, rootless, processMode, nil, nil, nil) + workerOpt, err := NewWorkerOpt(tmpdir, snFactory, rootless, processMode, nil, nil, network.Opt{Mode: "host"}, nil) require.NoError(t, err) return workerOpt, cleanup From 653c91e1149275e8d3bc2ac6f29d609822cc681e Mon Sep 17 00:00:00 2001 From: Tonis Tiigi <tonistiigi@gmail.com> Date: Thu, 11 Jul 2019 16:47:45 -0700 Subject: [PATCH 3/6] network: add non-lazy cni network init Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com> --- hack/dockerfiles/test.buildkit.Dockerfile | 2 +- util/network/cni.go | 22 +++++++++++++++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/hack/dockerfiles/test.buildkit.Dockerfile b/hack/dockerfiles/test.buildkit.Dockerfile index 08c98ae4ae3b..5a1652f0a461 100644 --- a/hack/dockerfiles/test.buildkit.Dockerfile +++ b/hack/dockerfiles/test.buildkit.Dockerfile @@ -224,7 +224,7 @@ COPY --from=cni-plugins /opt/cni/bin/bridge /opt/cni/bin/host-local /opt/cni/bin COPY hack/fixtures/cni.json /etc/buildkit/cni.json COPY --from=binaries / /usr/bin/ COPY . . -ENV BUILDKIT_RUN_NETWORK_INTEGRATION_TESTS=1 +ENV BUILDKIT_RUN_NETWORK_INTEGRATION_TESTS=1 BUILDKIT_CNI_INIT_LOCK_PATH=/run/buildkit_cni_bridge.lock FROM integration-tests AS dev-env VOLUME /var/lib/buildkit diff --git a/util/network/cni.go b/util/network/cni.go index 087803c3a8d8..58955dd28420 100644 --- a/util/network/cni.go +++ b/util/network/cni.go @@ -7,6 +7,7 @@ import ( "github.com/containerd/containerd/oci" "github.com/containerd/go-cni" + "github.com/gofrs/flock" "github.com/moby/buildkit/identity" "github.com/moby/buildkit/util/network/netns_create" specs "github.com/opencontainers/runtime-spec/specs-go" @@ -36,7 +37,11 @@ func NewCNIProvider(opt Opt) (Provider, error) { return nil, err } - return &cniProvider{CNI: cniHandle, root: opt.Root}, nil + cp := &cniProvider{CNI: cniHandle, root: opt.Root} + if err := cp.initNetwork(); err != nil { + return nil, err + } + return cp, nil } type cniProvider struct { @@ -44,6 +49,21 @@ type cniProvider struct { root string } +func (c *cniProvider) initNetwork() error { + if v := os.Getenv("BUILDKIT_CNI_INIT_LOCK_PATH"); v != "" { + l := flock.New(v) + if err := l.Lock(); err != nil { + return err + } + defer l.Unlock() + } + ns, err := c.New() + if err != nil { + return err + } + return ns.Close() +} + func (c *cniProvider) New() (Namespace, error) { id := identity.NewID() nsPath := filepath.Join(c.root, "net/cni", id) From e38512c0567f5a830369f940686444b95de32acf Mon Sep 17 00:00:00 2001 From: Tonis Tiigi <tonistiigi@gmail.com> Date: Fri, 12 Jul 2019 10:22:25 -0700 Subject: [PATCH 4/6] testutil: add logs printing on error Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com> --- util/testutil/integration/containerd.go | 2 ++ util/testutil/integration/oci.go | 20 +++++++++++++------- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/util/testutil/integration/containerd.go b/util/testutil/integration/containerd.go index db2dac98d6e6..c1157dd1d7f6 100644 --- a/util/testutil/integration/containerd.go +++ b/util/testutil/integration/containerd.go @@ -4,6 +4,7 @@ import ( "bytes" "fmt" "io/ioutil" + "log" "os" "os/exec" "path/filepath" @@ -154,6 +155,7 @@ disabled_plugins = ["cri"] buildkitdSock, stop, err := runBuildkitd(buildkitdArgs, logs, 0, 0) if err != nil { + printLogs(logs, log.Println) return nil, nil, err } deferF.append(stop) diff --git a/util/testutil/integration/oci.go b/util/testutil/integration/oci.go index 4868ec037b06..23d42fe099bd 100644 --- a/util/testutil/integration/oci.go +++ b/util/testutil/integration/oci.go @@ -5,6 +5,7 @@ import ( "bytes" "fmt" "io/ioutil" + "log" "os" "os/exec" "path/filepath" @@ -98,6 +99,7 @@ func (s *oci) New(opt ...SandboxOpt) (Sandbox, func() error, error) { buildkitdSock, stop, err := runBuildkitd(buildkitdArgs, logs, s.uid, s.gid) if err != nil { deferF.F()() + printLogs(logs, log.Println) return nil, nil, err } @@ -106,6 +108,16 @@ func (s *oci) New(opt ...SandboxOpt) (Sandbox, func() error, error) { return &sandbox{address: buildkitdSock, mv: c.mv, logs: logs, cleanup: deferF, rootless: s.uid != 0}, deferF.F(), nil } +func printLogs(logs map[string]*bytes.Buffer, f func(args ...interface{})) { + for name, l := range logs { + f(name) + s := bufio.NewScanner(l) + for s.Scan() { + f(s.Text()) + } + } +} + type sandbox struct { address string logs map[string]*bytes.Buffer @@ -119,13 +131,7 @@ func (sb *sandbox) Address() string { } func (sb *sandbox) PrintLogs(t *testing.T) { - for name, l := range sb.logs { - t.Log(name) - s := bufio.NewScanner(l) - for s.Scan() { - t.Log(s.Text()) - } - } + printLogs(sb.logs, t.Log) } func (sb *sandbox) NewRegistry() (string, error) { From 954b73dae1a9eba4230460027e3bb922a4ca64e1 Mon Sep 17 00:00:00 2001 From: Tonis Tiigi <tonistiigi@gmail.com> Date: Fri, 12 Jul 2019 14:57:10 -0700 Subject: [PATCH 5/6] network: move ns creation from reexec to linkname Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com> --- cmd/buildkitd/main_unix.go | 4 --- util/network/allowempty.s | 0 util/network/cni.go | 5 +-- util/network/cni_unsafe.go | 16 +++++++++ util/network/createns_linux.go | 59 +++++++++++++++++++++++++++++++ util/network/createns_nolinux.go | 9 +++++ util/network/netns_create/hook.go | 51 -------------------------- 7 files changed, 87 insertions(+), 57 deletions(-) create mode 100644 util/network/allowempty.s create mode 100644 util/network/cni_unsafe.go create mode 100644 util/network/createns_linux.go create mode 100644 util/network/createns_nolinux.go delete mode 100644 util/network/netns_create/hook.go diff --git a/cmd/buildkitd/main_unix.go b/cmd/buildkitd/main_unix.go index 649fc0f5ba51..93533adb1fdf 100644 --- a/cmd/buildkitd/main_unix.go +++ b/cmd/buildkitd/main_unix.go @@ -4,12 +4,8 @@ package main import ( "syscall" - - "github.com/moby/buildkit/util/network/netns_create" ) func init() { - netns_create.Handle() - syscall.Umask(0) } diff --git a/util/network/allowempty.s b/util/network/allowempty.s new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/util/network/cni.go b/util/network/cni.go index 58955dd28420..1b5e9d89afdd 100644 --- a/util/network/cni.go +++ b/util/network/cni.go @@ -9,7 +9,6 @@ import ( "github.com/containerd/go-cni" "github.com/gofrs/flock" "github.com/moby/buildkit/identity" - "github.com/moby/buildkit/util/network/netns_create" specs "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" "golang.org/x/sys/unix" @@ -71,11 +70,13 @@ func (c *cniProvider) New() (Namespace, error) { return nil, err } - if err := netns_create.CreateNetNS(nsPath); err != nil { + if err := createNetNS(nsPath); err != nil { + os.RemoveAll(filepath.Dir(nsPath)) return nil, err } if _, err := c.CNI.Setup(id, nsPath); err != nil { + os.RemoveAll(filepath.Dir(nsPath)) return nil, errors.Wrap(err, "CNI setup error") } diff --git a/util/network/cni_unsafe.go b/util/network/cni_unsafe.go new file mode 100644 index 000000000000..05cfef85b107 --- /dev/null +++ b/util/network/cni_unsafe.go @@ -0,0 +1,16 @@ +// +build linux + +package network + +import ( + _ "unsafe" // required for go:linkname. +) + +//go:linkname beforeFork syscall.runtime_BeforeFork +func beforeFork() + +//go:linkname afterFork syscall.runtime_AfterFork +func afterFork() + +//go:linkname afterForkInChild syscall.runtime_AfterForkInChild +func afterForkInChild() diff --git a/util/network/createns_linux.go b/util/network/createns_linux.go new file mode 100644 index 000000000000..3b6172705811 --- /dev/null +++ b/util/network/createns_linux.go @@ -0,0 +1,59 @@ +// +build linux + +package network + +import ( + "os" + "syscall" + "unsafe" + + "github.com/pkg/errors" + "golang.org/x/sys/unix" +) + +func createNetNS(p string) error { + f, err := os.Create(p) + if err != nil { + return err + } + if err := f.Close(); err != nil { + return err + } + procNetNSBytes, err := syscall.BytePtrFromString("/proc/self/ns/net") + if err != nil { + return err + } + pBytes, err := syscall.BytePtrFromString(p) + if err != nil { + return err + } + beforeFork() + + pid, _, errno := syscall.RawSyscall6(syscall.SYS_CLONE, uintptr(syscall.SIGCHLD)|unix.CLONE_NEWNET, 0, 0, 0, 0, 0) + if errno != 0 { + afterFork() + return errno + } + + if pid != 0 { + afterFork() + var ws unix.WaitStatus + _, err = unix.Wait4(int(pid), &ws, 0, nil) + for err == syscall.EINTR { + _, err = unix.Wait4(int(pid), &ws, 0, nil) + } + + if err != nil { + return errors.Wrapf(err, "failed to find pid=%d process", pid) + } + errno = syscall.Errno(ws.ExitStatus()) + if errno != 0 { + return errors.Wrap(errno, "failed to mount") + } + return nil + } + afterForkInChild() + _, _, errno = syscall.RawSyscall6(syscall.SYS_MOUNT, uintptr(unsafe.Pointer(procNetNSBytes)), uintptr(unsafe.Pointer(pBytes)), 0, uintptr(unix.MS_BIND), 0, 0) + syscall.RawSyscall(syscall.SYS_EXIT, uintptr(errno), 0, 0) + panic("unreachable") +} diff --git a/util/network/createns_nolinux.go b/util/network/createns_nolinux.go new file mode 100644 index 000000000000..baa2473fda95 --- /dev/null +++ b/util/network/createns_nolinux.go @@ -0,0 +1,9 @@ +// +build !linux + +package network + +import "github.com/pkg/errors" + +func createNetNS(p string) error { + return errors.Errorf("creating netns for cni not supported") +} diff --git a/util/network/netns_create/hook.go b/util/network/netns_create/hook.go deleted file mode 100644 index d2a060c7b7d4..000000000000 --- a/util/network/netns_create/hook.go +++ /dev/null @@ -1,51 +0,0 @@ -package netns_create - -import ( - "log" - "os" - "os/exec" - "syscall" - - "github.com/pkg/errors" - "golang.org/x/sys/unix" -) - -const envKey = "BUILDKIT_CREATE_NS_PATH" - -func Handle() { - if path := os.Getenv(envKey); path != "" { - if err := handle(path); err != nil { - log.Printf("%v", err) - os.Exit(1) - } - os.Exit(0) - } -} - -func handle(path string) error { - f, err := os.Create(path) - if err != nil { - return err - } - if err := f.Close(); err != nil { - return err - } - if err := unix.Mount("/proc/self/ns/net", path, "none", unix.MS_BIND, ""); err != nil { - return err - } - return nil -} - -func CreateNetNS(path string) error { - cmd := exec.Command("/proc/self/exe") - cmd.Env = []string{envKey + "=" + path} - cmd.SysProcAttr = &syscall.SysProcAttr{ - Cloneflags: unix.CLONE_NEWNET, - } - out, err := cmd.CombinedOutput() - if err != nil { - return errors.Wrap(err, string(out)) - - } - return nil -} From acbdfc28514092c0830fd2597e1d5dd1180684dd Mon Sep 17 00:00:00 2001 From: Tonis Tiigi <tonistiigi@gmail.com> Date: Mon, 15 Jul 2019 10:53:18 -0700 Subject: [PATCH 6/6] util: add warning if network fallback is used Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com> --- util/network/network.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/util/network/network.go b/util/network/network.go index 824d3edf3b7c..1770e53bf8b0 100644 --- a/util/network/network.go +++ b/util/network/network.go @@ -7,6 +7,7 @@ import ( "github.com/moby/buildkit/solver/pb" specs "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" + "github.com/sirupsen/logrus" ) type Opt struct { @@ -36,6 +37,7 @@ func Providers(opt Opt) (map[pb.NetMode]Provider, error) { } defaultProvider = cniProvider } else { + logrus.Warnf("using host network as the default") defaultProvider = NewHostProvider() } default: