diff options
author | OpenShift Merge Robot <openshift-merge-robot@users.noreply.github.com> | 2018-10-11 10:40:37 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-10-11 10:40:37 -0700 |
commit | 6983e00a2808b29481d1cb460aab2eea1db6ef73 (patch) | |
tree | 2ee6bc368cc698e13d5dd5249061ecd2e2f93f6e | |
parent | 3c23bfca807eab55c145092c73dd8eb1e6599f38 (diff) | |
parent | 112e1402c9e2ee29a387cd84a973471c1888e4b9 (diff) | |
download | podman-6983e00a2808b29481d1cb460aab2eea1db6ef73.tar.gz podman-6983e00a2808b29481d1cb460aab2eea1db6ef73.tar.bz2 podman-6983e00a2808b29481d1cb460aab2eea1db6ef73.zip |
Merge pull request #1623 from mheon/static_ip
Add ability to specify static IPs with --ip flag
-rw-r--r-- | cmd/podman/common.go | 4 | ||||
-rw-r--r-- | completions/bash/podman | 1 | ||||
-rw-r--r-- | docs/podman-create.1.md | 4 | ||||
-rw-r--r-- | docs/podman-run.1.md | 4 | ||||
-rw-r--r-- | libpod/container.go | 8 | ||||
-rw-r--r-- | libpod/container_easyjson.go | 14 | ||||
-rw-r--r-- | libpod/networking_linux.go | 19 | ||||
-rw-r--r-- | libpod/options.go | 25 | ||||
-rw-r--r-- | libpod/pod_easyjson.go | 2 | ||||
-rw-r--r-- | pkg/spec/createconfig.go | 13 | ||||
-rw-r--r-- | test/e2e/run_staticip_test.go | 58 | ||||
-rw-r--r-- | vendor.conf | 2 | ||||
-rw-r--r-- | vendor/github.com/cri-o/ocicni/pkg/ocicni/ocicni.go | 41 | ||||
-rw-r--r-- | vendor/github.com/cri-o/ocicni/pkg/ocicni/types.go | 17 |
14 files changed, 189 insertions, 23 deletions
diff --git a/cmd/podman/common.go b/cmd/podman/common.go index 9ab0e57e5..c1e15e2fb 100644 --- a/cmd/podman/common.go +++ b/cmd/podman/common.go @@ -249,6 +249,10 @@ var createFlags = []cli.Flag{ Usage: "Keep STDIN open even if not attached", }, cli.StringFlag{ + Name: "ip", + Usage: "Specify a static IPv4 address for the container", + }, + cli.StringFlag{ Name: "ipc", Usage: "IPC namespace to use", }, diff --git a/completions/bash/podman b/completions/bash/podman index 604a25f5d..5cfed348f 100644 --- a/completions/bash/podman +++ b/completions/bash/podman @@ -1512,6 +1512,7 @@ _podman_container_run() { --hostname -h --image-volume --init-path + --ip --ipc --kernel-memory --label-file diff --git a/docs/podman-create.1.md b/docs/podman-create.1.md index 167fce5b9..509d8820f 100644 --- a/docs/podman-create.1.md +++ b/docs/podman-create.1.md @@ -286,7 +286,9 @@ Not implemented **--ip**="" -Not implemented +Specify a static IP address for the container, for example '10.88.64.128'. +Can only be used if no additional CNI networks to join were specified via '--network=<network-name>', and if the container is not joining another container's network namespace via '--network=container:<name|id>'. +The address must be within the default CNI network's pool (default 10.88.0.0/16). **--ipc**="" diff --git a/docs/podman-run.1.md b/docs/podman-run.1.md index b70396a6d..c303492e7 100644 --- a/docs/podman-run.1.md +++ b/docs/podman-run.1.md @@ -297,7 +297,9 @@ Not implemented **--ip**="" -Not implemented +Specify a static IP address for the container, for example '10.88.64.128'. +Can only be used if no additional CNI networks to join were specified via '--network=<network-name>', and if the container is not joining another container's network namespace via '--network=container:<name|id>'. +The address must be within the default CNI network's pool (default 10.88.0.0/16). **--ipc**="" diff --git a/libpod/container.go b/libpod/container.go index 55a0f3a2c..5997c0b66 100644 --- a/libpod/container.go +++ b/libpod/container.go @@ -269,9 +269,13 @@ type ContainerConfig struct { // Network Config // CreateNetNS indicates that libpod should create and configure a new - // network namespace for the container - // This cannot be set if NetNsCtr is also set + // network namespace for the container. + // This cannot be set if NetNsCtr is also set. CreateNetNS bool `json:"createNetNS"` + // StaticIP is a static IP to request for the container. + // This cannot be set unless CreateNetNS is set. + // If not set, the container will be dynamically assigned an IP by CNI. + StaticIP net.IP `json:"staticIP"` // PortMappings are the ports forwarded to the container's network // namespace // These are not used unless CreateNetNS is true diff --git a/libpod/container_easyjson.go b/libpod/container_easyjson.go index 916118aec..f78366065 100644 --- a/libpod/container_easyjson.go +++ b/libpod/container_easyjson.go @@ -1383,6 +1383,10 @@ func easyjson1dbef17bDecodeGithubComContainersLibpodLibpod2(in *jlexer.Lexer, ou } case "createNetNS": out.CreateNetNS = bool(in.Bool()) + case "staticIP": + if data := in.UnsafeBytes(); in.Ok() { + in.AddError((out.StaticIP).UnmarshalText(data)) + } case "portMappings": if in.IsNull() { in.Skip() @@ -2005,6 +2009,16 @@ func easyjson1dbef17bEncodeGithubComContainersLibpodLibpod2(out *jwriter.Writer, } out.Bool(bool(in.CreateNetNS)) } + { + const prefix string = ",\"staticIP\":" + if first { + first = false + out.RawString(prefix[1:]) + } else { + out.RawString(prefix) + } + out.RawText((in.StaticIP).MarshalText()) + } if len(in.PortMappings) != 0 { const prefix string = ",\"portMappings\":" if first { diff --git a/libpod/networking_linux.go b/libpod/networking_linux.go index 17e79aa62..acb4e2a90 100644 --- a/libpod/networking_linux.go +++ b/libpod/networking_linux.go @@ -5,6 +5,7 @@ package libpod import ( "crypto/rand" "fmt" + "net" "os" "os/exec" "path/filepath" @@ -25,8 +26,8 @@ import ( ) // Get an OCICNI network config -func getPodNetwork(id, name, nsPath string, networks []string, ports []ocicni.PortMapping) ocicni.PodNetwork { - return ocicni.PodNetwork{ +func (r *Runtime) getPodNetwork(id, name, nsPath string, networks []string, ports []ocicni.PortMapping, staticIP net.IP) ocicni.PodNetwork { + network := ocicni.PodNetwork{ Name: name, Namespace: name, // TODO is there something else we should put here? We don't know about Kube namespaces ID: id, @@ -34,11 +35,21 @@ func getPodNetwork(id, name, nsPath string, networks []string, ports []ocicni.Po PortMappings: ports, Networks: networks, } + + if staticIP != nil { + defaultNetwork := r.netPlugin.GetDefaultNetworkName() + + network.Networks = []string{defaultNetwork} + network.NetworkConfig = make(map[string]ocicni.NetworkConfig) + network.NetworkConfig[defaultNetwork] = ocicni.NetworkConfig{IP: staticIP.String()} + } + + return network } // Create and configure a new network namespace for a container func (r *Runtime) configureNetNS(ctr *Container, ctrNS ns.NetNS) (err error) { - podNetwork := getPodNetwork(ctr.ID(), ctr.Name(), ctrNS.Path(), ctr.config.Networks, ctr.config.PortMappings) + podNetwork := r.getPodNetwork(ctr.ID(), ctr.Name(), ctrNS.Path(), ctr.config.Networks, ctr.config.PortMappings, ctr.config.StaticIP) results, err := r.netPlugin.SetUpPod(podNetwork) if err != nil { @@ -216,7 +227,7 @@ func (r *Runtime) teardownNetNS(ctr *Container) error { logrus.Debugf("Tearing down network namespace at %s for container %s", ctr.state.NetNS.Path(), ctr.ID()) - podNetwork := getPodNetwork(ctr.ID(), ctr.Name(), ctr.state.NetNS.Path(), ctr.config.Networks, ctr.config.PortMappings) + podNetwork := r.getPodNetwork(ctr.ID(), ctr.Name(), ctr.state.NetNS.Path(), ctr.config.Networks, ctr.config.PortMappings, ctr.config.StaticIP) // The network may have already been torn down, so don't fail here, just log if err := r.netPlugin.TearDownPod(podNetwork); err != nil { diff --git a/libpod/options.go b/libpod/options.go index 977f3f4c2..9f966cead 100644 --- a/libpod/options.go +++ b/libpod/options.go @@ -828,6 +828,31 @@ func WithNetNS(portMappings []ocicni.PortMapping, postConfigureNetNS bool, netwo } } +// WithStaticIP indicates that the container should request a static IP from +// the CNI plugins. +// It cannot be set unless WithNetNS has already been passed. +// Further, it cannot be set if additional CNI networks to join have been +// specified. +func WithStaticIP(ip net.IP) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return ErrCtrFinalized + } + + if !ctr.config.CreateNetNS { + return errors.Wrapf(ErrInvalidArg, "cannot set a static IP if the container is not creating a network namespace") + } + + if len(ctr.config.Networks) != 0 { + return errors.Wrapf(ErrInvalidArg, "cannot set a static IP if joining additional CNI networks") + } + + ctr.config.StaticIP = ip + + return nil + } +} + // WithLogPath sets the path to the log file. func WithLogPath(path string) CtrCreateOption { return func(ctr *Container) error { diff --git a/libpod/pod_easyjson.go b/libpod/pod_easyjson.go index 2891e51f2..6c1c939f3 100644 --- a/libpod/pod_easyjson.go +++ b/libpod/pod_easyjson.go @@ -1,3 +1,5 @@ +// +build seccomp ostree selinux varlink exclude_graphdriver_devicemapper + // Code generated by easyjson for marshaling/unmarshaling. DO NOT EDIT. package libpod diff --git a/pkg/spec/createconfig.go b/pkg/spec/createconfig.go index 887ef8e95..e9a5dc9dc 100644 --- a/pkg/spec/createconfig.go +++ b/pkg/spec/createconfig.go @@ -2,6 +2,7 @@ package createconfig import ( "encoding/json" + "net" "os" "strconv" "strings" @@ -311,9 +312,6 @@ func (c *CreateConfig) GetContainerCreateOptions(runtime *libpod.Runtime) ([]lib var pod *libpod.Pod var err error - // Uncomment after talking to mheon about unimplemented funcs - // options = append(options, libpod.WithLabels(c.labels)) - if c.Interactive { options = append(options, libpod.WithStdin()) } @@ -442,6 +440,15 @@ func (c *CreateConfig) GetContainerCreateOptions(runtime *libpod.Runtime) ([]lib if logPath != "" { options = append(options, libpod.WithLogPath(logPath)) } + if c.IPAddress != "" { + ip := net.ParseIP(c.IPAddress) + if ip == nil { + return nil, errors.Wrapf(libpod.ErrInvalidArg, "cannot parse %s as IP address", c.IPAddress) + } else if ip.To4() == nil { + return nil, errors.Wrapf(libpod.ErrInvalidArg, "%s is not an IPv4 address", c.IPAddress) + } + options = append(options, libpod.WithStaticIP(ip)) + } options = append(options, libpod.WithPrivileged(c.Privileged)) diff --git a/test/e2e/run_staticip_test.go b/test/e2e/run_staticip_test.go new file mode 100644 index 000000000..b69d15cee --- /dev/null +++ b/test/e2e/run_staticip_test.go @@ -0,0 +1,58 @@ +package integration + +import ( + "fmt" + "os" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("Podman run with --ip flag", func() { + var ( + tempdir string + err error + podmanTest PodmanTest + ) + + BeforeEach(func() { + tempdir, err = CreateTempDirInTempDir() + if err != nil { + os.Exit(1) + } + podmanTest = PodmanCreate(tempdir) + podmanTest.RestoreAllArtifacts() + }) + + AfterEach(func() { + podmanTest.Cleanup() + f := CurrentGinkgoTestDescription() + timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) + GinkgoWriter.Write([]byte(timedResult)) + }) + + It("Podman run --ip with garbage address", func() { + result := podmanTest.Podman([]string{"run", "-ti", "--ip", "114232346", ALPINE, "ls"}) + result.WaitWithDefaultTimeout() + Expect(result.ExitCode()).ToNot(Equal(0)) + }) + + It("Podman run --ip with v6 address", func() { + result := podmanTest.Podman([]string{"run", "-ti", "--ip", "2001:db8:bad:beef::1", ALPINE, "ls"}) + result.WaitWithDefaultTimeout() + Expect(result.ExitCode()).ToNot(Equal(0)) + }) + + It("Podman run --ip with non-allocatable IP", func() { + result := podmanTest.Podman([]string{"run", "-ti", "--ip", "203.0.113.124", ALPINE, "ls"}) + result.WaitWithDefaultTimeout() + Expect(result.ExitCode()).ToNot(Equal(0)) + }) + + It("Podman run with specified static IP has correct IP", func() { + result := podmanTest.Podman([]string{"run", "-ti", "--ip", "10.88.64.128", ALPINE, "ip", "addr"}) + result.WaitWithDefaultTimeout() + Expect(result.ExitCode()).To(Equal(0)) + Expect(result.OutputToString()).To(ContainSubstring("10.88.64.128/16")) + }) +}) diff --git a/vendor.conf b/vendor.conf index d65014992..75b92e846 100644 --- a/vendor.conf +++ b/vendor.conf @@ -14,7 +14,7 @@ github.com/containers/image 918dbb93e6e099b196b498c38d079f6bb924d0c8 github.com/containers/storage 41294c85d97bef688e18f710402895dbecde3308 github.com/containers/psgo 5dde6da0bc8831b35243a847625bcf18183bd1ee github.com/coreos/go-systemd v14 -github.com/cri-o/ocicni master +github.com/cri-o/ocicni 2d2983e40c242322a56c22a903785e7f83eb378c github.com/cyphar/filepath-securejoin v0.2.1 github.com/davecgh/go-spew v1.1.0 github.com/docker/distribution 7a8efe719e55bbfaff7bc5718cdf0ed51ca821df diff --git a/vendor/github.com/cri-o/ocicni/pkg/ocicni/ocicni.go b/vendor/github.com/cri-o/ocicni/pkg/ocicni/ocicni.go index 33a3ae063..dfc216389 100644 --- a/vendor/github.com/cri-o/ocicni/pkg/ocicni/ocicni.go +++ b/vendor/github.com/cri-o/ocicni/pkg/ocicni/ocicni.go @@ -3,6 +3,7 @@ package ocicni import ( "errors" "fmt" + "net" "os" "path" "sort" @@ -351,14 +352,14 @@ func (plugin *cniNetworkPlugin) getNetwork(name string) (*cniNetwork, error) { return net, nil } -func (plugin *cniNetworkPlugin) getDefaultNetworkName() string { +func (plugin *cniNetworkPlugin) GetDefaultNetworkName() string { plugin.RLock() defer plugin.RUnlock() return plugin.defaultNetName } func (plugin *cniNetworkPlugin) getDefaultNetwork() *cniNetwork { - defaultNetName := plugin.getDefaultNetworkName() + defaultNetName := plugin.GetDefaultNetworkName() if defaultNetName == "" { return nil } @@ -383,7 +384,7 @@ func (plugin *cniNetworkPlugin) Name() string { func (plugin *cniNetworkPlugin) forEachNetwork(podNetwork *PodNetwork, forEachFunc func(*cniNetwork, string, *PodNetwork) error) error { networks := podNetwork.Networks if len(networks) == 0 { - networks = append(networks, plugin.getDefaultNetworkName()) + networks = append(networks, plugin.GetDefaultNetworkName()) } for i, netName := range networks { // Interface names start at "eth0" and count up for each network @@ -408,7 +409,7 @@ func (plugin *cniNetworkPlugin) SetUpPod(podNetwork PodNetwork) ([]cnitypes.Resu plugin.podLock(podNetwork).Lock() defer plugin.podUnlock(podNetwork) - _, err := plugin.loNetwork.addToNetwork(plugin.cacheDir, &podNetwork, "lo") + _, err := plugin.loNetwork.addToNetwork(plugin.cacheDir, &podNetwork, "lo", "") if err != nil { logrus.Errorf("Error while adding to cni lo network: %s", err) return nil, err @@ -416,7 +417,12 @@ func (plugin *cniNetworkPlugin) SetUpPod(podNetwork PodNetwork) ([]cnitypes.Resu results := make([]cnitypes.Result, 0) if err := plugin.forEachNetwork(&podNetwork, func(network *cniNetwork, ifName string, podNetwork *PodNetwork) error { - result, err := network.addToNetwork(plugin.cacheDir, podNetwork, ifName) + ip := "" + if conf, ok := podNetwork.NetworkConfig[network.name]; ok { + ip = conf.IP + } + + result, err := network.addToNetwork(plugin.cacheDir, podNetwork, ifName, ip) if err != nil { logrus.Errorf("Error while adding pod to CNI network %q: %s", network.name, err) return err @@ -439,7 +445,12 @@ func (plugin *cniNetworkPlugin) TearDownPod(podNetwork PodNetwork) error { defer plugin.podUnlock(podNetwork) return plugin.forEachNetwork(&podNetwork, func(network *cniNetwork, ifName string, podNetwork *PodNetwork) error { - if err := network.deleteFromNetwork(plugin.cacheDir, podNetwork, ifName); err != nil { + ip := "" + if conf, ok := podNetwork.NetworkConfig[network.name]; ok { + ip = conf.IP + } + + if err := network.deleteFromNetwork(plugin.cacheDir, podNetwork, ifName, ip); err != nil { logrus.Errorf("Error while removing pod from CNI network %q: %s", network.name, err) return err } @@ -491,8 +502,8 @@ func (plugin *cniNetworkPlugin) GetPodNetworkStatus(podNetwork PodNetwork) ([]cn return results, nil } -func (network *cniNetwork) addToNetwork(cacheDir string, podNetwork *PodNetwork, ifName string) (cnitypes.Result, error) { - rt, err := buildCNIRuntimeConf(cacheDir, podNetwork, ifName) +func (network *cniNetwork) addToNetwork(cacheDir string, podNetwork *PodNetwork, ifName, ip string) (cnitypes.Result, error) { + rt, err := buildCNIRuntimeConf(cacheDir, podNetwork, ifName, ip) if err != nil { logrus.Errorf("Error adding network: %v", err) return nil, err @@ -509,8 +520,8 @@ func (network *cniNetwork) addToNetwork(cacheDir string, podNetwork *PodNetwork, return res, nil } -func (network *cniNetwork) deleteFromNetwork(cacheDir string, podNetwork *PodNetwork, ifName string) error { - rt, err := buildCNIRuntimeConf(cacheDir, podNetwork, ifName) +func (network *cniNetwork) deleteFromNetwork(cacheDir string, podNetwork *PodNetwork, ifName, ip string) error { + rt, err := buildCNIRuntimeConf(cacheDir, podNetwork, ifName, ip) if err != nil { logrus.Errorf("Error deleting network: %v", err) return err @@ -526,7 +537,7 @@ func (network *cniNetwork) deleteFromNetwork(cacheDir string, podNetwork *PodNet return nil } -func buildCNIRuntimeConf(cacheDir string, podNetwork *PodNetwork, ifName string) (*libcni.RuntimeConf, error) { +func buildCNIRuntimeConf(cacheDir string, podNetwork *PodNetwork, ifName, ip string) (*libcni.RuntimeConf, error) { logrus.Infof("Got pod network %+v", podNetwork) rt := &libcni.RuntimeConf{ @@ -542,6 +553,14 @@ func buildCNIRuntimeConf(cacheDir string, podNetwork *PodNetwork, ifName string) }, } + // Add requested static IP to CNI_ARGS + if ip != "" { + if tstIP := net.ParseIP(ip); tstIP == nil { + return nil, fmt.Errorf("unable to parse IP address %q", ip) + } + rt.Args = append(rt.Args, [2]string{"IP", ip}) + } + if len(podNetwork.PortMappings) == 0 { return rt, nil } diff --git a/vendor/github.com/cri-o/ocicni/pkg/ocicni/types.go b/vendor/github.com/cri-o/ocicni/pkg/ocicni/types.go index 8ca61657a..d76094292 100644 --- a/vendor/github.com/cri-o/ocicni/pkg/ocicni/types.go +++ b/vendor/github.com/cri-o/ocicni/pkg/ocicni/types.go @@ -24,6 +24,14 @@ type PortMapping struct { HostIP string `json:"hostIP"` } +// NetworkConfig is additional configuration for a single CNI network. +type NetworkConfig struct { + // IP is a static IP to be specified in the network. Can only be used + // with the hostlocal IP allocator. If left unset, an IP will be + // dynamically allocated. + IP string +} + // PodNetwork configures the network of a pod sandbox. type PodNetwork struct { // Name is the name of the sandbox. @@ -40,6 +48,11 @@ type PodNetwork struct { // Networks is a list of CNI network names to attach to the sandbox // Leave this list empty to attach the default network to the sandbox Networks []string + + // NetworkConfig is configuration specific to a single CNI network. + // It is optional, and can be omitted for some or all specified networks + // without issue. + NetworkConfig map[string]NetworkConfig } // CNIPlugin is the interface that needs to be implemented by a plugin @@ -48,6 +61,10 @@ type CNIPlugin interface { // for a plugin by name, e.g. Name() string + // GetDefaultNetworkName returns the name of the plugin's default + // network. + GetDefaultNetworkName() string + // SetUpPod is the method called after the sandbox container of // the pod has been created but before the other containers of the // pod are launched. |