podman: add support for specifying MAC

I basically copied and adapted the statements for setting IP.

Closes #1136

Signed-off-by: Jakub Filak <jakub.filak@sap.com>
This commit is contained in:
Jakub Filak
2019-09-18 00:26:48 +02:00
committed by Giuseppe Scrivano
parent 455f5b7616
commit 2497b6c77b
14 changed files with 154 additions and 35 deletions

View File

@ -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 {

View File

@ -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", "",

View File

@ -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)
}

View File

@ -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:]

View File

@ -877,6 +877,7 @@ _podman_container_restore() {
--tcp-established
--ignore-rootfs
--ignore-static-ip
--ignore-static-mac
"
case "$prev" in
-i|--import)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)
}

View File

@ -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())

View File

@ -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 {

View File

@ -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 {

View File

@ -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

View File

@ -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"))