diff options
-rw-r--r-- | cmd/podman/cliconfig/config.go | 17 | ||||
-rw-r--r-- | cmd/podman/common.go | 2 | ||||
-rw-r--r-- | cmd/podman/restore.go | 1 | ||||
-rw-r--r-- | cmd/podman/shared/create.go | 4 | ||||
-rw-r--r-- | completions/bash/podman | 1 | ||||
-rw-r--r-- | docs/source/markdown/podman-container-restore.1.md | 9 | ||||
-rw-r--r-- | libpod/container.go | 8 | ||||
-rw-r--r-- | libpod/container_api.go | 5 | ||||
-rw-r--r-- | libpod/container_internal_linux.go | 50 | ||||
-rw-r--r-- | libpod/networking_linux.go | 35 | ||||
-rw-r--r-- | libpod/options.go | 25 | ||||
-rw-r--r-- | pkg/adapter/containers.go | 13 | ||||
-rw-r--r-- | pkg/spec/createconfig.go | 8 | ||||
-rw-r--r-- | test/e2e/checkpoint_test.go | 11 |
14 files changed, 154 insertions, 35 deletions
diff --git a/cmd/podman/cliconfig/config.go b/cmd/podman/cliconfig/config.go index 58d67ddc1..780b68333 100644 --- a/cmd/podman/cliconfig/config.go +++ b/cmd/podman/cliconfig/config.go @@ -467,14 +467,15 @@ type RestartValues struct { type RestoreValues struct { PodmanCommand - All bool - Keep bool - Latest bool - TcpEstablished bool - Import string - Name string - IgnoreRootfs bool - IgnoreStaticIP bool + All bool + Keep bool + Latest bool + TcpEstablished bool + Import string + Name string + IgnoreRootfs bool + IgnoreStaticIP bool + IgnoreStaticMAC bool } type RmValues struct { diff --git a/cmd/podman/common.go b/cmd/podman/common.go index 37511641b..5a8ef0fe6 100644 --- a/cmd/podman/common.go +++ b/cmd/podman/common.go @@ -328,7 +328,7 @@ func getCreateFlags(c *cliconfig.PodmanCommand) { ) createFlags.String( "mac-address", "", - "Container MAC address (e.g. 92:d0:c6:0a:29:33), not currently supported", + "Container MAC address (e.g. 92:d0:c6:0a:29:33)", ) createFlags.StringP( "memory", "m", "", diff --git a/cmd/podman/restore.go b/cmd/podman/restore.go index 90d0b2dc4..caefadb6d 100644 --- a/cmd/podman/restore.go +++ b/cmd/podman/restore.go @@ -47,6 +47,7 @@ func init() { flags.StringVarP(&restoreCommand.Name, "name", "n", "", "Specify new name for container restored from exported checkpoint (only works with --import)") flags.BoolVar(&restoreCommand.IgnoreRootfs, "ignore-rootfs", false, "Do not apply root file-system changes when importing from exported checkpoint") flags.BoolVar(&restoreCommand.IgnoreStaticIP, "ignore-static-ip", false, "Ignore IP address set via --static-ip") + flags.BoolVar(&restoreCommand.IgnoreStaticMAC, "ignore-static-mac", false, "Ignore MAC address set via --mac-address") markFlagHiddenForRemoteClient("latest", flags) } diff --git a/cmd/podman/shared/create.go b/cmd/podman/shared/create.go index dc343e694..6c74b8a9b 100644 --- a/cmd/podman/shared/create.go +++ b/cmd/podman/shared/create.go @@ -336,10 +336,6 @@ func ParseCreateOpts(ctx context.Context, c *GenericCLIResults, runtime *libpod. return nil, err } - if c.String("mac-address") != "" { - return nil, errors.Errorf("--mac-address option not currently supported") - } - imageID := "" inputCommand = c.InputArgs[1:] diff --git a/completions/bash/podman b/completions/bash/podman index 0abf9e738..4d552b0a7 100644 --- a/completions/bash/podman +++ b/completions/bash/podman @@ -877,6 +877,7 @@ _podman_container_restore() { --tcp-established --ignore-rootfs --ignore-static-ip + --ignore-static-mac " case "$prev" in -i|--import) diff --git a/docs/source/markdown/podman-container-restore.1.md b/docs/source/markdown/podman-container-restore.1.md index 1d2cf0b3e..d71daf4af 100644 --- a/docs/source/markdown/podman-container-restore.1.md +++ b/docs/source/markdown/podman-container-restore.1.md @@ -76,6 +76,15 @@ a container is restored multiple times from an exported checkpoint with **--name Using **--ignore-static-ip** tells Podman to ignore the IP address if it was configured with **--ip** during container creation. +**--ignore-static-mac** + +If the container was started with **--mac-address** the restored container also +tries to use that MAC address and restore fails if that MAC address is already +in use. This can happen, if a container is restored multiple times from an +exported checkpoint with **--name, -n**. + +Using **--ignore-static-mac** tells Podman to ignore the MAC address if it was +configured with **--mac-address** during container creation. ## EXAMPLE podman container restore mywebserver diff --git a/libpod/container.go b/libpod/container.go index 8e24391b9..4f7fc067e 100644 --- a/libpod/container.go +++ b/libpod/container.go @@ -138,6 +138,10 @@ type Container struct { // being checkpointed. If requestedIP is set it will be used instead // of config.StaticIP. requestedIP net.IP + // A restored container should have the same MAC address as before + // being checkpointed. If requestedMAC is set it will be used instead + // of config.StaticMAC. + requestedMAC net.HardwareAddr // This is true if a container is restored from a checkpoint. restoreFromCheckpoint bool @@ -296,6 +300,10 @@ type ContainerConfig struct { // 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"` + // StaticMAC is a static MAC to request for the container. + // This cannot be set unless CreateNetNS is set. + // If not set, the container will be dynamically assigned a MAC by CNI. + StaticMAC net.HardwareAddr `json:"staticMAC"` // PortMappings are the ports forwarded to the container's network // namespace // These are not used unless CreateNetNS is true diff --git a/libpod/container_api.go b/libpod/container_api.go index a6f5b54d5..b8cfe02f6 100644 --- a/libpod/container_api.go +++ b/libpod/container_api.go @@ -794,6 +794,11 @@ type ContainerCheckpointOptions struct { // important to be able to restore a container multiple // times with '--import --name'. IgnoreStaticIP bool + // IgnoreStaticMAC tells the API to ignore the MAC set + // during 'podman run' with '--mac-address'. This is especially + // important to be able to restore a container multiple + // times with '--import --name'. + IgnoreStaticMAC bool } // Checkpoint checkpoints a container diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go index 471648bc8..26d6771b0 100644 --- a/libpod/container_internal_linux.go +++ b/libpod/container_internal_linux.go @@ -794,6 +794,15 @@ func (c *Container) restore(ctx context.Context, options ContainerCheckpointOpti c.config.StaticIP = nil } + // If a container is restored multiple times from an exported checkpoint with + // the help of '--import --name', the restore will fail if during 'podman run' + // a static container MAC address was set with '--mac-address'. The user + // can tell the restore process to ignore the static MAC with + // '--ignore-static-mac' + if options.IgnoreStaticMAC { + c.config.StaticMAC = nil + } + // Read network configuration from checkpoint // Currently only one interface with one IP is supported. networkStatusFile, err := os.Open(filepath.Join(c.bundlePath(), "network.status")) @@ -803,9 +812,9 @@ func (c *Container) restore(ctx context.Context, options ContainerCheckpointOpti // TODO: This implicit restoring with or without IP depending on an // unrelated restore parameter (--name) does not seem like the // best solution. - if err == nil && options.Name == "" && !options.IgnoreStaticIP { + if err == nil && options.Name == "" && (!options.IgnoreStaticIP || !options.IgnoreStaticMAC) { // The file with the network.status does exist. Let's restore the - // container with the same IP address as during checkpointing. + // container with the same IP address / MAC address as during checkpointing. defer networkStatusFile.Close() var networkStatus []*cnitypes.Result networkJSON, err := ioutil.ReadAll(networkStatusFile) @@ -815,16 +824,35 @@ func (c *Container) restore(ctx context.Context, options ContainerCheckpointOpti if err := json.Unmarshal(networkJSON, &networkStatus); err != nil { return err } - // Take the first IP address - var IP net.IP - if len(networkStatus) > 0 { - if len(networkStatus[0].IPs) > 0 { - IP = networkStatus[0].IPs[0].Address.IP + if !options.IgnoreStaticIP { + // Take the first IP address + var IP net.IP + if len(networkStatus) > 0 { + if len(networkStatus[0].IPs) > 0 { + IP = networkStatus[0].IPs[0].Address.IP + } + } + if IP != nil { + // Tell CNI which IP address we want. + c.requestedIP = IP } } - if IP != nil { - // Tell CNI which IP address we want. - c.requestedIP = IP + if !options.IgnoreStaticMAC { + // Take the first device with a defined sandbox. + var MAC net.HardwareAddr + for _, n := range networkStatus[0].Interfaces { + if n.Sandbox != "" { + MAC, err = net.ParseMAC(n.Mac) + if err != nil { + return errors.Wrapf(err, "failed to parse MAC %v", n.Mac) + } + break + } + } + if MAC != nil { + // Tell CNI which MAC address we want. + c.requestedMAC = MAC + } } } @@ -1314,7 +1342,7 @@ func (c *Container) copyOwnerAndPerms(source, dest string) error { // Teardown CNI config on refresh func (c *Container) refreshCNI() error { // Let's try and delete any lingering network config... - podNetwork := c.runtime.getPodNetwork(c.ID(), c.config.Name, "", c.config.Networks, c.config.PortMappings, c.config.StaticIP) + podNetwork := c.runtime.getPodNetwork(c.ID(), c.config.Name, "", c.config.Networks, c.config.PortMappings, c.config.StaticIP, c.config.StaticMAC) return c.runtime.netPlugin.TearDownPod(podNetwork) } diff --git a/libpod/networking_linux.go b/libpod/networking_linux.go index 8172e3fb2..cba7b636a 100644 --- a/libpod/networking_linux.go +++ b/libpod/networking_linux.go @@ -28,7 +28,7 @@ import ( ) // Get an OCICNI network config -func (r *Runtime) getPodNetwork(id, name, nsPath string, networks []string, ports []ocicni.PortMapping, staticIP net.IP) ocicni.PodNetwork { +func (r *Runtime) getPodNetwork(id, name, nsPath string, networks []string, ports []ocicni.PortMapping, staticIP net.IP, staticMAC net.HardwareAddr) ocicni.PodNetwork { defaultNetwork := r.netPlugin.GetDefaultNetworkName() network := ocicni.PodNetwork{ Name: name, @@ -40,10 +40,17 @@ func (r *Runtime) getPodNetwork(id, name, nsPath string, networks []string, port }, } - if staticIP != nil { + if staticIP != nil || staticMAC != nil { network.Networks = []ocicni.NetAttachment{{Name: defaultNetwork}} + var rt ocicni.RuntimeConfig = ocicni.RuntimeConfig{PortMappings: ports} + if staticIP != nil { + rt.IP = staticIP.String() + } + if staticMAC != nil { + rt.MAC = staticMAC.String() + } network.RuntimeConfig = map[string]ocicni.RuntimeConfig{ - defaultNetwork: {IP: staticIP.String(), PortMappings: ports}, + defaultNetwork: rt, } } else { network.Networks = make([]ocicni.NetAttachment, len(networks)) @@ -66,7 +73,16 @@ func (r *Runtime) configureNetNS(ctr *Container, ctrNS ns.NetNS) ([]*cnitypes.Re requestedIP = ctr.config.StaticIP } - podNetwork := r.getPodNetwork(ctr.ID(), ctr.Name(), ctrNS.Path(), ctr.config.Networks, ctr.config.PortMappings, requestedIP) + var requestedMAC net.HardwareAddr + if ctr.requestedMAC != nil { + requestedMAC = ctr.requestedMAC + // cancel request for a specific MAC in case the container is reused later + ctr.requestedMAC = nil + } else { + requestedMAC = ctr.config.StaticMAC + } + + podNetwork := r.getPodNetwork(ctr.ID(), ctr.Name(), ctrNS.Path(), ctr.config.Networks, ctr.config.PortMappings, requestedIP, requestedMAC) results, err := r.netPlugin.SetUpPod(podNetwork) if err != nil { @@ -447,7 +463,16 @@ func (r *Runtime) teardownNetNS(ctr *Container) error { requestedIP = ctr.config.StaticIP } - podNetwork := r.getPodNetwork(ctr.ID(), ctr.Name(), ctr.state.NetNS.Path(), ctr.config.Networks, ctr.config.PortMappings, requestedIP) + var requestedMAC net.HardwareAddr + if ctr.requestedMAC != nil { + requestedMAC = ctr.requestedMAC + // cancel request for a specific MAC in case the container is reused later + ctr.requestedMAC = nil + } else { + requestedMAC = ctr.config.StaticMAC + } + + podNetwork := r.getPodNetwork(ctr.ID(), ctr.Name(), ctr.state.NetNS.Path(), ctr.config.Networks, ctr.config.PortMappings, requestedIP, requestedMAC) if err := r.netPlugin.TearDownPod(podNetwork); err != nil { return errors.Wrapf(err, "error tearing down CNI namespace configuration for container %s", ctr.ID()) diff --git a/libpod/options.go b/libpod/options.go index 66e8ef93c..00b5626b4 100644 --- a/libpod/options.go +++ b/libpod/options.go @@ -1052,6 +1052,31 @@ func WithStaticIP(ip net.IP) CtrCreateOption { } } +// WithStaticMAC indicates that the container should request a static MAC 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 WithStaticMAC(mac net.HardwareAddr) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + + if !ctr.config.CreateNetNS { + return errors.Wrapf(define.ErrInvalidArg, "cannot set a static MAC if the container is not creating a network namespace") + } + + if len(ctr.config.Networks) != 0 { + return errors.Wrapf(define.ErrInvalidArg, "cannot set a static MAC if joining additional CNI networks") + } + + ctr.config.StaticMAC = mac + + return nil + } +} + // WithLogDriver sets the log driver for the container func WithLogDriver(driver string) CtrCreateOption { return func(ctr *Container) error { diff --git a/pkg/adapter/containers.go b/pkg/adapter/containers.go index 64550f545..d8cd683d6 100644 --- a/pkg/adapter/containers.go +++ b/pkg/adapter/containers.go @@ -547,12 +547,13 @@ func (r *LocalRuntime) Restore(ctx context.Context, c *cliconfig.RestoreValues) ) options := libpod.ContainerCheckpointOptions{ - Keep: c.Keep, - TCPEstablished: c.TcpEstablished, - TargetFile: c.Import, - Name: c.Name, - IgnoreRootfs: c.IgnoreRootfs, - IgnoreStaticIP: c.IgnoreStaticIP, + Keep: c.Keep, + TCPEstablished: c.TcpEstablished, + TargetFile: c.Import, + Name: c.Name, + IgnoreRootfs: c.IgnoreRootfs, + IgnoreStaticIP: c.IgnoreStaticIP, + IgnoreStaticMAC: c.IgnoreStaticMAC, } filterFuncs = append(filterFuncs, func(c *libpod.Container) bool { diff --git a/pkg/spec/createconfig.go b/pkg/spec/createconfig.go index 2a8fe7332..e054b3b13 100644 --- a/pkg/spec/createconfig.go +++ b/pkg/spec/createconfig.go @@ -396,6 +396,14 @@ func (c *CreateConfig) getContainerCreateOptions(runtime *libpod.Runtime, pod *l options = append(options, libpod.WithStaticIP(ip)) } + if c.MacAddress != "" { + mac, err := net.ParseMAC(c.MacAddress) + if err != nil { + return nil, errors.Wrapf(define.ErrInvalidArg, "cannot parse %s as MAC address: %v", c.MacAddress, err) + } + options = append(options, libpod.WithStaticMAC(mac)) + } + options = append(options, libpod.WithPrivileged(c.Privileged)) useImageVolumes := c.ImageVolumeType == TypeBind diff --git a/test/e2e/checkpoint_test.go b/test/e2e/checkpoint_test.go index 4b43ceb30..2d3efcbef 100644 --- a/test/e2e/checkpoint_test.go +++ b/test/e2e/checkpoint_test.go @@ -334,6 +334,10 @@ var _ = Describe("Podman checkpoint", func() { IPBefore.WaitWithDefaultTimeout() Expect(IPBefore.ExitCode()).To(Equal(0)) + MACBefore := podmanTest.Podman([]string{"inspect", "-l", "--format={{.NetworkSettings.MacAddress}}"}) + MACBefore.WaitWithDefaultTimeout() + Expect(MACBefore.ExitCode()).To(Equal(0)) + result := podmanTest.Podman([]string{"container", "checkpoint", "test_name"}) result.WaitWithDefaultTimeout() @@ -348,9 +352,16 @@ var _ = Describe("Podman checkpoint", func() { IPAfter.WaitWithDefaultTimeout() Expect(IPAfter.ExitCode()).To(Equal(0)) + MACAfter := podmanTest.Podman([]string{"inspect", "-l", "--format={{.NetworkSettings.MacAddress}}"}) + MACAfter.WaitWithDefaultTimeout() + Expect(MACAfter.ExitCode()).To(Equal(0)) + // Check that IP address did not change between checkpointing and restoring Expect(IPBefore.OutputToString()).To(Equal(IPAfter.OutputToString())) + // Check that MAC address did not change between checkpointing and restoring + Expect(MACBefore.OutputToString()).To(Equal(MACAfter.OutputToString())) + Expect(result.ExitCode()).To(Equal(0)) Expect(podmanTest.NumberOfContainersRunning()).To(Equal(1)) Expect(podmanTest.GetContainerStatus()).To(ContainSubstring("Up")) |