diff --git a/libpod/container.go b/libpod/container.go index 44992c5347..d0eb6a9920 100644 --- a/libpod/container.go +++ b/libpod/container.go @@ -128,6 +128,11 @@ type Container struct { rootlessSlirpSyncR *os.File rootlessSlirpSyncW *os.File + + // A restored container should have the same IP address as before + // being checkpointed. If requestedIP is set it will be used instead + // of config.StaticIP. + requestedIP net.IP } // containerState contains the current state of the container diff --git a/libpod/container_easyjson.go b/libpod/container_easyjson.go index 50741df116..f1cb09bccd 100644 --- a/libpod/container_easyjson.go +++ b/libpod/container_easyjson.go @@ -1,6 +1,6 @@ // +build seccomp ostree selinux varlink exclude_graphdriver_devicemapper -// Code generated by easyjson for marshaling/unmarshaling. DO NOT EDIT. +// Code generated by easyjson for marshaling/unmarshaling. DO NOT EDIT package libpod diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go index c66be10616..582a4c3e7b 100644 --- a/libpod/container_internal_linux.go +++ b/libpod/container_internal_linux.go @@ -549,10 +549,8 @@ func (c *Container) restore(ctx context.Context, options ContainerCheckpointOpti } } if IP != nil { - env := fmt.Sprintf("IP=%s", IP) // Tell CNI which IP address we want. - os.Setenv("CNI_ARGS", env) - logrus.Debugf("Restoring container with %s", env) + c.requestedIP = IP } } @@ -568,12 +566,6 @@ func (c *Container) restore(ctx context.Context, options ContainerCheckpointOpti return err } - // TODO: use existing way to request static IPs, once it is merged in ocicni - // https://github.com/cri-o/ocicni/pull/23/ - - // CNI_ARGS was used to request a certain IP address. Unconditionally remove it. - os.Unsetenv("CNI_ARGS") - // Read config jsonPath := filepath.Join(c.bundlePath(), "config.json") logrus.Debugf("generate.NewFromFile at %v", jsonPath) diff --git a/libpod/networking_linux.go b/libpod/networking_linux.go index 43d0a61a48..a343bee6ab 100644 --- a/libpod/networking_linux.go +++ b/libpod/networking_linux.go @@ -50,7 +50,16 @@ func (r *Runtime) getPodNetwork(id, name, nsPath string, networks []string, port // Create and configure a new network namespace for a container func (r *Runtime) configureNetNS(ctr *Container, ctrNS ns.NetNS) ([]*cnitypes.Result, error) { - podNetwork := r.getPodNetwork(ctr.ID(), ctr.Name(), ctrNS.Path(), ctr.config.Networks, ctr.config.PortMappings, ctr.config.StaticIP) + var requestedIP net.IP + if ctr.requestedIP != nil { + requestedIP = ctr.requestedIP + // cancel request for a specific IP in case the container is reused later + ctr.requestedIP = nil + } else { + requestedIP = ctr.config.StaticIP + } + + podNetwork := r.getPodNetwork(ctr.ID(), ctr.Name(), ctrNS.Path(), ctr.config.Networks, ctr.config.PortMappings, requestedIP) results, err := r.netPlugin.SetUpPod(podNetwork) if err != nil { @@ -258,7 +267,16 @@ 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 := r.getPodNetwork(ctr.ID(), ctr.Name(), ctr.state.NetNS.Path(), ctr.config.Networks, ctr.config.PortMappings, ctr.config.StaticIP) + var requestedIP net.IP + if ctr.requestedIP != nil { + requestedIP = ctr.requestedIP + // cancel request for a specific IP in case the container is reused later + ctr.requestedIP = nil + } else { + requestedIP = ctr.config.StaticIP + } + + podNetwork := r.getPodNetwork(ctr.ID(), ctr.Name(), ctr.state.NetNS.Path(), ctr.config.Networks, ctr.config.PortMappings, requestedIP) // 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/test/e2e/checkpoint_test.go b/test/e2e/checkpoint_test.go index 4e892d11c2..ca2f35fc9e 100644 --- a/test/e2e/checkpoint_test.go +++ b/test/e2e/checkpoint_test.go @@ -199,14 +199,17 @@ var _ = Describe("Podman checkpoint", func() { }) It("podman checkpoint container with established tcp connections", func() { - Skip("Seems to not work (yet) in CI") podmanTest.RestoreArtifact(redis) - session := podmanTest.Podman([]string{"run", "-it", "--security-opt", "seccomp=unconfined", "--network", "host", "-d", redis}) + session := podmanTest.Podman([]string{"run", "-it", "--security-opt", "seccomp=unconfined", "-d", redis}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) + IP := podmanTest.Podman([]string{"inspect", "-l", "--format={{.NetworkSettings.IPAddress}}"}) + IP.WaitWithDefaultTimeout() + Expect(IP.ExitCode()).To(Equal(0)) + // Open a network connection to the redis server - conn, err := net.Dial("tcp", "127.0.0.1:6379") + conn, err := net.Dial("tcp", IP.OutputToString()+":6379") if err != nil { os.Exit(1) } @@ -287,4 +290,40 @@ var _ = Describe("Podman checkpoint", func() { Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0)) }) + It("podman checkpoint and restore container with same IP", func() { + session := podmanTest.Podman([]string{"run", "-it", "--security-opt", "seccomp=unconfined", "--name", "test_name", "-d", ALPINE, "top"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + IPBefore := podmanTest.Podman([]string{"inspect", "-l", "--format={{.NetworkSettings.IPAddress}}"}) + IPBefore.WaitWithDefaultTimeout() + Expect(IPBefore.ExitCode()).To(Equal(0)) + + result := podmanTest.Podman([]string{"container", "checkpoint", "test_name"}) + result.WaitWithDefaultTimeout() + + Expect(result.ExitCode()).To(Equal(0)) + Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0)) + Expect(podmanTest.GetContainerStatus()).To(ContainSubstring("Exited")) + + result = podmanTest.Podman([]string{"container", "restore", "test_name"}) + result.WaitWithDefaultTimeout() + + IPAfter := podmanTest.Podman([]string{"inspect", "-l", "--format={{.NetworkSettings.IPAddress}}"}) + IPAfter.WaitWithDefaultTimeout() + Expect(IPAfter.ExitCode()).To(Equal(0)) + + // Check that IP address did not change between checkpointing and restoring + Expect(IPBefore.OutputToString()).To(Equal(IPAfter.OutputToString())) + + Expect(result.ExitCode()).To(Equal(0)) + Expect(podmanTest.NumberOfContainersRunning()).To(Equal(1)) + Expect(podmanTest.GetContainerStatus()).To(ContainSubstring("Up")) + + result = podmanTest.Podman([]string{"rm", "-fa"}) + result.WaitWithDefaultTimeout() + Expect(result.ExitCode()).To(Equal(0)) + Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0)) + }) + })