Merge pull request #4451 from giuseppe/set-mac

podman: add support for specifying MAC
This commit is contained in:
OpenShift Merge Robot
2019-11-07 20:26:14 +01:00
committed by GitHub
28 changed files with 841 additions and 186 deletions

View File

@ -522,6 +522,9 @@ vendor:
$(GO) mod vendor && \
$(GO) mod verify
vendor-in-container:
podman run --privileged --rm --env HOME=/root -v `pwd`:/src -w /src docker.io/library/golang:1.12 make vendor
.PHONY: \
.gopathok \
binaries \

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

@ -33,6 +33,9 @@
{
"type": "firewall",
"backend": "iptables"
},
{
"type": "tuning"
}
]
}

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

4
go.mod
View File

@ -9,7 +9,7 @@ require (
github.com/checkpoint-restore/go-criu v0.0.0-20190109184317-bdb7599cd87b
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd // indirect
github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc // indirect
github.com/containernetworking/cni v0.7.1
github.com/containernetworking/cni v0.7.2-0.20190904153231-83439463f784
github.com/containernetworking/plugins v0.8.2
github.com/containers/buildah v1.11.5-0.20191031204705-20e92ffe0982
github.com/containers/image/v5 v5.0.0
@ -17,7 +17,7 @@ require (
github.com/containers/storage v1.13.5
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f // indirect
github.com/cri-o/ocicni v0.1.1-0.20190702175919-7762645d18ca
github.com/cri-o/ocicni v0.1.1-0.20190920040751-deac903fd99b
github.com/cyphar/filepath-securejoin v0.2.2
github.com/davecgh/go-spew v1.1.1
github.com/docker/distribution v2.7.1+incompatible

4
go.sum
View File

@ -53,6 +53,8 @@ github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL
github.com/containernetworking/cni v0.7.0/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY=
github.com/containernetworking/cni v0.7.1 h1:fE3r16wpSEyaqY4Z4oFrLMmIGfBYIKpPrHK31EJ9FzE=
github.com/containernetworking/cni v0.7.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY=
github.com/containernetworking/cni v0.7.2-0.20190904153231-83439463f784 h1:rqUVLD8I859xRgUx/WMC3v7QAFqbLKZbs+0kqYboRJc=
github.com/containernetworking/cni v0.7.2-0.20190904153231-83439463f784/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY=
github.com/containernetworking/plugins v0.8.2 h1:5lnwfsAYO+V7yXhysJKy3E1A2Gy9oVut031zfdOzI9w=
github.com/containernetworking/plugins v0.8.2/go.mod h1:TxALKWZpWL79BC3GOYKJzzXr7U8R23PdhwaLp6F3adc=
github.com/containers/buildah v1.11.4-0.20191028173731-21b4778b359e h1:iDavHEx5Yr7o+0l6495Ya6N0YEPplIUZuWC2e14baDM=
@ -83,6 +85,8 @@ github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfc
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/cri-o/ocicni v0.1.1-0.20190702175919-7762645d18ca h1:CJstDqYy9ClWuPcDHMTCAiUS+ckekluYetGR2iYYWuo=
github.com/cri-o/ocicni v0.1.1-0.20190702175919-7762645d18ca/go.mod h1:BO0al9TKber3XUTucLzKgoG5sq8qiOB41H7zSdfw6r8=
github.com/cri-o/ocicni v0.1.1-0.20190920040751-deac903fd99b h1:SgS+WV10y2Bubuy2HquSBori6DXj9sqRN77Hgs5H7Qc=
github.com/cri-o/ocicni v0.1.1-0.20190920040751-deac903fd99b/go.mod h1:ZOuIEOp/3MB1eCBWANnNxM3zUA3NWh76wSRCsnKAg2c=
github.com/cyphar/filepath-securejoin v0.2.2 h1:jCwT2GTP+PY5nBz3c/YL5PAIbusElVrPujOBSCj8xRg=
github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4=
github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1SMSibvLzxjeJLnrYEVLULFNiHY9YfQ=

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,23 +28,34 @@ 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,
Namespace: name, // TODO is there something else we should put here? We don't know about Kube namespaces
ID: id,
NetNS: nsPath,
Networks: networks,
RuntimeConfig: map[string]ocicni.RuntimeConfig{
defaultNetwork: {PortMappings: ports},
},
}
if staticIP != nil {
network.Networks = []string{defaultNetwork}
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))
for i, netName := range networks {
network.Networks[i].Name = netName
}
}
@ -62,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 {
@ -78,10 +98,10 @@ func (r *Runtime) configureNetNS(ctr *Container, ctrNS ns.NetNS) ([]*cnitypes.Re
networkStatus := make([]*cnitypes.Result, 0)
for idx, r := range results {
logrus.Debugf("[%d] CNI result: %v", idx, r.String())
resultCurrent, err := cnitypes.GetResult(r)
logrus.Debugf("[%d] CNI result: %v", idx, r.Result.String())
resultCurrent, err := cnitypes.GetResult(r.Result)
if err != nil {
return nil, errors.Wrapf(err, "error parsing CNI plugin result %q: %v", r.String(), err)
return nil, errors.Wrapf(err, "error parsing CNI plugin result %q: %v", r.Result.String(), err)
}
networkStatus = append(networkStatus, resultCurrent)
}
@ -443,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

@ -538,12 +538,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"))

View File

@ -0,0 +1,46 @@
// +build !remoteclient
package integration
import (
"os"
. "github.com/containers/libpod/test/utils"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Podman run with --mac-address flag", func() {
var (
tempdir string
err error
podmanTest *PodmanTestIntegration
)
BeforeEach(func() {
SkipIfRootless()
tempdir, err = CreateTempDirInTempDir()
if err != nil {
os.Exit(1)
}
podmanTest = PodmanTestCreate(tempdir)
podmanTest.Setup()
podmanTest.SeedImages()
// Cleanup the CNI networks used by the tests
os.RemoveAll("/var/lib/cni/networks/podman")
})
AfterEach(func() {
podmanTest.Cleanup()
f := CurrentGinkgoTestDescription()
processTestResult(f)
})
It("Podman run --mac-address", func() {
result := podmanTest.Podman([]string{"run", "--mac-address", "92:d0:c6:0a:29:34", ALPINE, "ip", "addr"})
result.WaitWithDefaultTimeout()
Expect(result.ExitCode()).To(Equal(0))
Expect(result.OutputToString()).To(ContainSubstring("92:d0:c6:0a:29:34"))
})
})

View File

@ -25,6 +25,7 @@ import (
"github.com/containernetworking/cni/pkg/invoke"
"github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/cni/pkg/utils"
"github.com/containernetworking/cni/pkg/version"
)
@ -32,6 +33,10 @@ var (
CacheDir = "/var/lib/cni"
)
const (
CNICacheV1 = "cniCacheV1"
)
// A RuntimeConf holds the arguments to one invocation of a CNI plugin
// excepting the network configuration, with the nested exception that
// the `runtimeConfig` from the network configuration is included
@ -48,7 +53,7 @@ type RuntimeConf struct {
// to the plugin
CapabilityArgs map[string]interface{}
// A cache directory in which to library data. Defaults to CacheDir
// DEPRECATED. Will be removed in a future release.
CacheDir string
}
@ -70,19 +75,22 @@ type CNI interface {
CheckNetworkList(ctx context.Context, net *NetworkConfigList, rt *RuntimeConf) error
DelNetworkList(ctx context.Context, net *NetworkConfigList, rt *RuntimeConf) error
GetNetworkListCachedResult(net *NetworkConfigList, rt *RuntimeConf) (types.Result, error)
GetNetworkListCachedConfig(net *NetworkConfigList, rt *RuntimeConf) ([]byte, *RuntimeConf, error)
AddNetwork(ctx context.Context, net *NetworkConfig, rt *RuntimeConf) (types.Result, error)
CheckNetwork(ctx context.Context, net *NetworkConfig, rt *RuntimeConf) error
DelNetwork(ctx context.Context, net *NetworkConfig, rt *RuntimeConf) error
GetNetworkCachedResult(net *NetworkConfig, rt *RuntimeConf) (types.Result, error)
GetNetworkCachedConfig(net *NetworkConfig, rt *RuntimeConf) ([]byte, *RuntimeConf, error)
ValidateNetworkList(ctx context.Context, net *NetworkConfigList) ([]string, error)
ValidateNetwork(ctx context.Context, net *NetworkConfig) ([]string, error)
}
type CNIConfig struct {
Path []string
exec invoke.Exec
Path []string
exec invoke.Exec
cacheDir string
}
// CNIConfig implements the CNI interface
@ -92,9 +100,18 @@ var _ CNI = &CNIConfig{}
// in the given paths and use the given exec interface to run those plugins,
// or if the exec interface is not given, will use a default exec handler.
func NewCNIConfig(path []string, exec invoke.Exec) *CNIConfig {
return NewCNIConfigWithCacheDir(path, "", exec)
}
// NewCNIConfigWithCacheDir returns a new CNIConfig object that will search for plugins
// in the given paths use the given exec interface to run those plugins,
// or if the exec interface is not given, will use a default exec handler.
// The given cache directory will be used for temporary data storage when needed.
func NewCNIConfigWithCacheDir(path []string, cacheDir string, exec invoke.Exec) *CNIConfig {
return &CNIConfig{
Path: path,
exec: exec,
Path: path,
cacheDir: cacheDir,
exec: exec,
}
}
@ -165,33 +182,122 @@ func (c *CNIConfig) ensureExec() invoke.Exec {
return c.exec
}
func getResultCacheFilePath(netName string, rt *RuntimeConf) string {
cacheDir := rt.CacheDir
if cacheDir == "" {
cacheDir = CacheDir
}
return filepath.Join(cacheDir, "results", fmt.Sprintf("%s-%s-%s", netName, rt.ContainerID, rt.IfName))
type cachedInfo struct {
Kind string `json:"kind"`
ContainerID string `json:"containerId"`
Config []byte `json:"config"`
IfName string `json:"ifName"`
NetworkName string `json:"networkName"`
CniArgs [][2]string `json:"cniArgs,omitempty"`
CapabilityArgs map[string]interface{} `json:"capabilityArgs,omitempty"`
RawResult map[string]interface{} `json:"result,omitempty"`
Result types.Result `json:"-"`
}
func setCachedResult(result types.Result, netName string, rt *RuntimeConf) error {
// getCacheDir returns the cache directory in this order:
// 1) global cacheDir from CNIConfig object
// 2) deprecated cacheDir from RuntimeConf object
// 3) fall back to default cache directory
func (c *CNIConfig) getCacheDir(rt *RuntimeConf) string {
if c.cacheDir != "" {
return c.cacheDir
}
if rt.CacheDir != "" {
return rt.CacheDir
}
return CacheDir
}
func (c *CNIConfig) getCacheFilePath(netName string, rt *RuntimeConf) (string, error) {
if netName == "" || rt.ContainerID == "" || rt.IfName == "" {
return "", fmt.Errorf("cache file path requires network name (%q), container ID (%q), and interface name (%q)", netName, rt.ContainerID, rt.IfName)
}
return filepath.Join(c.getCacheDir(rt), "results", fmt.Sprintf("%s-%s-%s", netName, rt.ContainerID, rt.IfName)), nil
}
func (c *CNIConfig) cacheAdd(result types.Result, config []byte, netName string, rt *RuntimeConf) error {
cached := cachedInfo{
Kind: CNICacheV1,
ContainerID: rt.ContainerID,
Config: config,
IfName: rt.IfName,
NetworkName: netName,
CniArgs: rt.Args,
CapabilityArgs: rt.CapabilityArgs,
}
// We need to get type.Result into cachedInfo as JSON map
// Marshal to []byte, then Unmarshal into cached.RawResult
data, err := json.Marshal(result)
if err != nil {
return err
}
fname := getResultCacheFilePath(netName, rt)
err = json.Unmarshal(data, &cached.RawResult)
if err != nil {
return err
}
newBytes, err := json.Marshal(&cached)
if err != nil {
return err
}
fname, err := c.getCacheFilePath(netName, rt)
if err != nil {
return err
}
if err := os.MkdirAll(filepath.Dir(fname), 0700); err != nil {
return err
}
return ioutil.WriteFile(fname, data, 0600)
return ioutil.WriteFile(fname, newBytes, 0600)
}
func delCachedResult(netName string, rt *RuntimeConf) error {
fname := getResultCacheFilePath(netName, rt)
func (c *CNIConfig) cacheDel(netName string, rt *RuntimeConf) error {
fname, err := c.getCacheFilePath(netName, rt)
if err != nil {
// Ignore error
return nil
}
return os.Remove(fname)
}
func getCachedResult(netName, cniVersion string, rt *RuntimeConf) (types.Result, error) {
fname := getResultCacheFilePath(netName, rt)
func (c *CNIConfig) getCachedConfig(netName string, rt *RuntimeConf) ([]byte, *RuntimeConf, error) {
var bytes []byte
fname, err := c.getCacheFilePath(netName, rt)
if err != nil {
return nil, nil, err
}
bytes, err = ioutil.ReadFile(fname)
if err != nil {
// Ignore read errors; the cached result may not exist on-disk
return nil, nil, nil
}
unmarshaled := cachedInfo{}
if err := json.Unmarshal(bytes, &unmarshaled); err != nil {
return nil, nil, fmt.Errorf("failed to unmarshal cached network %q config: %v", netName, err)
}
if unmarshaled.Kind != CNICacheV1 {
return nil, nil, fmt.Errorf("read cached network %q config has wrong kind: %v", netName, unmarshaled.Kind)
}
newRt := *rt
if unmarshaled.CniArgs != nil {
newRt.Args = unmarshaled.CniArgs
}
newRt.CapabilityArgs = unmarshaled.CapabilityArgs
return unmarshaled.Config, &newRt, nil
}
func (c *CNIConfig) getLegacyCachedResult(netName, cniVersion string, rt *RuntimeConf) (types.Result, error) {
fname, err := c.getCacheFilePath(netName, rt)
if err != nil {
return nil, err
}
data, err := ioutil.ReadFile(fname)
if err != nil {
// Ignore read errors; the cached result may not exist on-disk
@ -222,16 +328,73 @@ func getCachedResult(netName, cniVersion string, rt *RuntimeConf) (types.Result,
return result, err
}
func (c *CNIConfig) getCachedResult(netName, cniVersion string, rt *RuntimeConf) (types.Result, error) {
fname, err := c.getCacheFilePath(netName, rt)
if err != nil {
return nil, err
}
fdata, err := ioutil.ReadFile(fname)
if err != nil {
// Ignore read errors; the cached result may not exist on-disk
return nil, nil
}
cachedInfo := cachedInfo{}
if err := json.Unmarshal(fdata, &cachedInfo); err != nil || cachedInfo.Kind != CNICacheV1 {
return c.getLegacyCachedResult(netName, cniVersion, rt)
}
newBytes, err := json.Marshal(&cachedInfo.RawResult)
if err != nil {
return nil, fmt.Errorf("failed to marshal cached network %q config: %v", netName, err)
}
// Read the version of the cached result
decoder := version.ConfigDecoder{}
resultCniVersion, err := decoder.Decode(newBytes)
if err != nil {
return nil, err
}
// Ensure we can understand the result
result, err := version.NewResult(resultCniVersion, newBytes)
if err != nil {
return nil, err
}
// Convert to the config version to ensure plugins get prevResult
// in the same version as the config. The cached result version
// should match the config version unless the config was changed
// while the container was running.
result, err = result.GetAsVersion(cniVersion)
if err != nil && resultCniVersion != cniVersion {
return nil, fmt.Errorf("failed to convert cached result version %q to config version %q: %v", resultCniVersion, cniVersion, err)
}
return result, err
}
// GetNetworkListCachedResult returns the cached Result of the previous
// previous AddNetworkList() operation for a network list, or an error.
// AddNetworkList() operation for a network list, or an error.
func (c *CNIConfig) GetNetworkListCachedResult(list *NetworkConfigList, rt *RuntimeConf) (types.Result, error) {
return getCachedResult(list.Name, list.CNIVersion, rt)
return c.getCachedResult(list.Name, list.CNIVersion, rt)
}
// GetNetworkCachedResult returns the cached Result of the previous
// previous AddNetwork() operation for a network, or an error.
// AddNetwork() operation for a network, or an error.
func (c *CNIConfig) GetNetworkCachedResult(net *NetworkConfig, rt *RuntimeConf) (types.Result, error) {
return getCachedResult(net.Network.Name, net.Network.CNIVersion, rt)
return c.getCachedResult(net.Network.Name, net.Network.CNIVersion, rt)
}
// GetNetworkListCachedConfig copies the input RuntimeConf to output
// RuntimeConf with fields updated with info from the cached Config.
func (c *CNIConfig) GetNetworkListCachedConfig(list *NetworkConfigList, rt *RuntimeConf) ([]byte, *RuntimeConf, error) {
return c.getCachedConfig(list.Name, rt)
}
// GetNetworkCachedConfig copies the input RuntimeConf to output
// RuntimeConf with fields updated with info from the cached Config.
func (c *CNIConfig) GetNetworkCachedConfig(net *NetworkConfig, rt *RuntimeConf) ([]byte, *RuntimeConf, error) {
return c.getCachedConfig(net.Network.Name, rt)
}
func (c *CNIConfig) addNetwork(ctx context.Context, name, cniVersion string, net *NetworkConfig, prevResult types.Result, rt *RuntimeConf) (types.Result, error) {
@ -240,6 +403,12 @@ func (c *CNIConfig) addNetwork(ctx context.Context, name, cniVersion string, net
if err != nil {
return nil, err
}
if err := utils.ValidateContainerID(rt.ContainerID); err != nil {
return nil, err
}
if err := utils.ValidateNetworkName(name); err != nil {
return nil, err
}
newConf, err := buildOneConfig(name, cniVersion, net, prevResult, rt)
if err != nil {
@ -260,7 +429,7 @@ func (c *CNIConfig) AddNetworkList(ctx context.Context, list *NetworkConfigList,
}
}
if err = setCachedResult(result, list.Name, rt); err != nil {
if err = c.cacheAdd(result, list.Bytes, list.Name, rt); err != nil {
return nil, fmt.Errorf("failed to set network %q cached result: %v", list.Name, err)
}
@ -295,7 +464,7 @@ func (c *CNIConfig) CheckNetworkList(ctx context.Context, list *NetworkConfigLis
return nil
}
cachedResult, err := getCachedResult(list.Name, list.CNIVersion, rt)
cachedResult, err := c.getCachedResult(list.Name, list.CNIVersion, rt)
if err != nil {
return fmt.Errorf("failed to get network %q cached result: %v", list.Name, err)
}
@ -332,7 +501,7 @@ func (c *CNIConfig) DelNetworkList(ctx context.Context, list *NetworkConfigList,
if gtet, err := version.GreaterThanOrEqualTo(list.CNIVersion, "0.4.0"); err != nil {
return err
} else if gtet {
cachedResult, err = getCachedResult(list.Name, list.CNIVersion, rt)
cachedResult, err = c.getCachedResult(list.Name, list.CNIVersion, rt)
if err != nil {
return fmt.Errorf("failed to get network %q cached result: %v", list.Name, err)
}
@ -344,7 +513,7 @@ func (c *CNIConfig) DelNetworkList(ctx context.Context, list *NetworkConfigList,
return err
}
}
_ = delCachedResult(list.Name, rt)
_ = c.cacheDel(list.Name, rt)
return nil
}
@ -356,7 +525,7 @@ func (c *CNIConfig) AddNetwork(ctx context.Context, net *NetworkConfig, rt *Runt
return nil, err
}
if err = setCachedResult(result, net.Network.Name, rt); err != nil {
if err = c.cacheAdd(result, net.Bytes, net.Network.Name, rt); err != nil {
return nil, fmt.Errorf("failed to set network %q cached result: %v", net.Network.Name, err)
}
@ -372,7 +541,7 @@ func (c *CNIConfig) CheckNetwork(ctx context.Context, net *NetworkConfig, rt *Ru
return fmt.Errorf("configuration version %q does not support the CHECK command", net.Network.CNIVersion)
}
cachedResult, err := getCachedResult(net.Network.Name, net.Network.CNIVersion, rt)
cachedResult, err := c.getCachedResult(net.Network.Name, net.Network.CNIVersion, rt)
if err != nil {
return fmt.Errorf("failed to get network %q cached result: %v", net.Network.Name, err)
}
@ -387,7 +556,7 @@ func (c *CNIConfig) DelNetwork(ctx context.Context, net *NetworkConfig, rt *Runt
if gtet, err := version.GreaterThanOrEqualTo(net.Network.CNIVersion, "0.4.0"); err != nil {
return err
} else if gtet {
cachedResult, err = getCachedResult(net.Network.Name, net.Network.CNIVersion, rt)
cachedResult, err = c.getCachedResult(net.Network.Name, net.Network.CNIVersion, rt)
if err != nil {
return fmt.Errorf("failed to get network %q cached result: %v", net.Network.Name, err)
}
@ -396,7 +565,7 @@ func (c *CNIConfig) DelNetwork(ctx context.Context, net *NetworkConfig, rt *Runt
if err := c.delNetwork(ctx, net.Network.Name, net.Network.CNIVersion, net, cachedResult, rt); err != nil {
return err
}
_ = delCachedResult(net.Network.Name, rt)
_ = c.cacheDel(net.Network.Name, rt)
return nil
}
@ -455,7 +624,8 @@ func (c *CNIConfig) ValidateNetwork(ctx context.Context, net *NetworkConfig) ([]
// validatePlugin checks that an individual plugin's configuration is sane
func (c *CNIConfig) validatePlugin(ctx context.Context, pluginName, expectedVersion string) error {
pluginPath, err := invoke.FindInPath(pluginName, c.Path)
c.ensureExec()
pluginPath, err := c.exec.FindInPath(pluginName, c.Path)
if err != nil {
return err
}

View File

@ -114,11 +114,11 @@ func ConfListFromBytes(bytes []byte) (*NetworkConfigList, error) {
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)
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)
return nil, fmt.Errorf("failed to parse plugin config %d: %v", i, err)
}
list.Plugins = append(list.Plugins, netConf)
}

View File

@ -32,7 +32,7 @@ type inherited struct{}
var inheritArgsFromEnv inherited
func (_ *inherited) AsEnv() []string {
func (*inherited) AsEnv() []string {
return nil
}

View File

@ -36,7 +36,7 @@ func (b *UnmarshallableBool) UnmarshalText(data []byte) error {
case "0", "false":
*b = false
default:
return fmt.Errorf("Boolean unmarshal error: invalid input %s", s)
return fmt.Errorf("boolean unmarshal error: invalid input %s", s)
}
return nil
}

View File

@ -16,7 +16,6 @@ package types
import (
"encoding/json"
"errors"
"fmt"
"io"
"net"
@ -134,9 +133,16 @@ func (r *Route) String() string {
// 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
ErrUnknown uint = iota // 0
ErrIncompatibleCNIVersion // 1
ErrUnsupportedField // 2
ErrUnknownContainer // 3
ErrInvalidEnvironmentVariables // 4
ErrIOFailure // 5
ErrDecodingFailure // 6
ErrInvalidNetworkConfig // 7
ErrTryAgainLater uint = 11
ErrInternal uint = 999
)
type Error struct {
@ -145,6 +151,14 @@ type Error struct {
Details string `json:"details,omitempty"`
}
func NewError(code uint, msg, details string) *Error {
return &Error{
Code: code,
Msg: msg,
Details: details,
}
}
func (e *Error) Error() string {
details := ""
if e.Details != "" {
@ -194,6 +208,3 @@ func prettyPrint(obj interface{}) error {
_, 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")

View File

@ -0,0 +1,51 @@
// Copyright 2019 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 utils
import (
"regexp"
"github.com/containernetworking/cni/pkg/types"
)
// cniValidNameChars is the regexp used to validate valid characters in
// containerID and networkName
const cniValidNameChars = `[a-zA-Z0-9][a-zA-Z0-9_.\-]`
var cniReg = regexp.MustCompile(`^` + cniValidNameChars + `*$`)
// ValidateContainerID will validate that the supplied containerID is not empty does not contain invalid characters
func ValidateContainerID(containerID string) *types.Error {
if containerID == "" {
return types.NewError(types.ErrUnknownContainer, "missing containerID", "")
}
if !cniReg.MatchString(containerID) {
return types.NewError(types.ErrInvalidEnvironmentVariables, "invalid characters in containerID", containerID)
}
return nil
}
// ValidateNetworkName will validate that the supplied networkName does not contain invalid characters
func ValidateNetworkName(networkName string) *types.Error {
if networkName == "" {
return types.NewError(types.ErrInvalidNetworkConfig, "missing network name:", "")
}
if !cniReg.MatchString(networkName) {
return types.NewError(types.ErrInvalidNetworkConfig, "invalid characters found in network name", networkName)
}
return nil
}

View File

@ -2,11 +2,14 @@ package ocicni
import (
"context"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net"
"os"
"path"
"path/filepath"
"sort"
"strings"
"sync"
@ -21,10 +24,11 @@ import (
)
type cniNetworkPlugin struct {
cniConfig *libcni.CNIConfig
loNetwork *cniNetwork
sync.RWMutex
defaultNetName string
defaultNetName netName
networks map[string]*cniNetwork
nsManager *nsManager
@ -47,11 +51,15 @@ type cniNetworkPlugin struct {
cacheDir string
}
type netName struct {
name string
changeable bool
}
type cniNetwork struct {
name string
filePath string
NetworkConfig *libcni.NetworkConfigList
CNIConfig *libcni.CNIConfig
name string
filePath string
config *libcni.NetworkConfigList
}
var errMissingDefaultNetwork = errors.New("Missing CNI default network")
@ -186,6 +194,8 @@ func (plugin *cniNetworkPlugin) monitorConfDir(start *sync.WaitGroup) {
// If defaultNetName is not empty, a CNI config with that network name will
// be used as the default CNI network, and container network operations will
// fail until that network config is present and valid.
// If defaultNetName is empty, CNI config files should be reloaded real-time and
// defaultNetName should be changeable and determined by file sorting.
func InitCNI(defaultNetName string, confDir string, binDirs ...string) (CNIPlugin, error) {
return initCNI(nil, "", defaultNetName, confDir, binDirs...)
}
@ -198,17 +208,24 @@ func initCNI(exec cniinvoke.Exec, cacheDir, defaultNetName string, confDir strin
if len(binDirs) == 0 {
binDirs = []string{DefaultBinDir}
}
plugin := &cniNetworkPlugin{
defaultNetName: defaultNetName,
networks: make(map[string]*cniNetwork),
loNetwork: getLoNetwork(exec, binDirs),
confDir: confDir,
binDirs: binDirs,
shutdownChan: make(chan struct{}),
done: &sync.WaitGroup{},
pods: make(map[string]*podLock),
exec: exec,
cacheDir: cacheDir,
cniConfig: libcni.NewCNIConfig(binDirs, exec),
defaultNetName: netName{
name: defaultNetName,
// If defaultNetName is not assigned in initialization,
// it should be changeable
changeable: defaultNetName == "",
},
networks: make(map[string]*cniNetwork),
loNetwork: getLoNetwork(),
confDir: confDir,
binDirs: binDirs,
shutdownChan: make(chan struct{}),
done: &sync.WaitGroup{},
pods: make(map[string]*podLock),
exec: exec,
cacheDir: cacheDir,
}
if exec == nil {
@ -246,7 +263,7 @@ func (plugin *cniNetworkPlugin) Shutdown() error {
return nil
}
func loadNetworks(exec cniinvoke.Exec, confDir string, binDirs []string) (map[string]*cniNetwork, string, error) {
func loadNetworks(confDir string, cni *libcni.CNIConfig) (map[string]*cniNetwork, string, error) {
files, err := libcni.ConfFiles(confDir, []string{".conf", ".conflist", ".json"})
if err != nil {
return nil, "", err
@ -284,17 +301,30 @@ func loadNetworks(exec cniinvoke.Exec, confDir string, binDirs []string) (map[st
logrus.Warningf("CNI config list %s has no networks, skipping", confFile)
continue
}
// Validation on CNI config should be done to pre-check presence
// of plugins which are necessary.
if _, err := cni.ValidateNetworkList(context.TODO(), confList); err != nil {
logrus.Warningf("Error validating CNI config file %s: %v", confFile, err)
continue
}
if confList.Name == "" {
confList.Name = path.Base(confFile)
}
cniNet := &cniNetwork{
name: confList.Name,
filePath: confFile,
config: confList,
}
logrus.Infof("Found CNI network %s (type=%v) at %s", confList.Name, confList.Plugins[0].Network.Type, confFile)
networks[confList.Name] = &cniNetwork{
name: confList.Name,
filePath: confFile,
NetworkConfig: confList,
CNIConfig: libcni.NewCNIConfig(binDirs, exec),
if _, ok := networks[confList.Name]; !ok {
networks[confList.Name] = cniNet
} else {
logrus.Infof("Ignore CNI network %s (type=%v) at %s because already exists", confList.Name, confList.Plugins[0].Network.Type, confFile)
}
if defaultNetName == "" {
@ -305,39 +335,49 @@ func loadNetworks(exec cniinvoke.Exec, confDir string, binDirs []string) (map[st
return networks, defaultNetName, nil
}
func getLoNetwork(exec cniinvoke.Exec, binDirs []string) *cniNetwork {
loConfig, err := libcni.ConfListFromBytes([]byte(`{
"cniVersion": "0.2.0",
"name": "cni-loopback",
const (
loIfname string = "lo"
loNetname string = "cni-loopback"
)
func getLoNetwork() *cniNetwork {
loConfig, err := libcni.ConfListFromBytes([]byte(fmt.Sprintf(`{
"cniVersion": "0.3.1",
"name": "%s",
"plugins": [{
"type": "loopback"
}]
}`))
}`, loNetname)))
if err != nil {
// The hardcoded config above should always be valid and unit tests will
// catch this
panic(err)
}
loNetwork := &cniNetwork{
name: "lo",
NetworkConfig: loConfig,
CNIConfig: libcni.NewCNIConfig(binDirs, exec),
name: loIfname,
config: loConfig,
}
return loNetwork
}
func (plugin *cniNetworkPlugin) syncNetworkConfig() error {
networks, defaultNetName, err := loadNetworks(plugin.exec, plugin.confDir, plugin.binDirs)
networks, defaultNetName, err := loadNetworks(plugin.confDir, plugin.cniConfig)
if err != nil {
return err
}
plugin.Lock()
defer plugin.Unlock()
if plugin.defaultNetName == "" {
plugin.defaultNetName = defaultNetName
// Update defaultNetName if it is changeable
if plugin.defaultNetName.changeable {
plugin.defaultNetName.name = defaultNetName
logrus.Infof("Update default CNI network name to %s", defaultNetName)
} else {
logrus.Warnf("Default CNI network name %s is unchangeable", plugin.defaultNetName.name)
}
plugin.networks = networks
return nil
@ -356,7 +396,7 @@ func (plugin *cniNetworkPlugin) getNetwork(name string) (*cniNetwork, error) {
func (plugin *cniNetworkPlugin) GetDefaultNetworkName() string {
plugin.RLock()
defer plugin.RUnlock()
return plugin.defaultNetName
return plugin.defaultNetName.name
}
func (plugin *cniNetworkPlugin) getDefaultNetwork() *cniNetwork {
@ -382,27 +422,120 @@ func (plugin *cniNetworkPlugin) Name() string {
return CNIPluginName
}
func (plugin *cniNetworkPlugin) forEachNetwork(podNetwork *PodNetwork, forEachFunc func(*cniNetwork, string, *PodNetwork, RuntimeConfig) error) error {
func (plugin *cniNetworkPlugin) loadNetworkFromCache(name string, rt *libcni.RuntimeConf) (*cniNetwork, *libcni.RuntimeConf, error) {
cniNet := &cniNetwork{
name: name,
config: &libcni.NetworkConfigList{
Name: name,
},
}
var confBytes []byte
var err error
confBytes, rt, err = plugin.cniConfig.GetNetworkListCachedConfig(cniNet.config, rt)
if err != nil {
return nil, nil, err
} else if confBytes == nil {
return nil, nil, fmt.Errorf("network %q not found in CNI cache", name)
}
cniNet.config, err = libcni.ConfListFromBytes(confBytes)
if err != nil {
// Might be a plain NetworkConfig
netConf, err := libcni.ConfFromBytes(confBytes)
if err != nil {
return nil, nil, err
}
// Up-convert to a NetworkConfigList
cniNet.config, err = libcni.ConfListFromConf(netConf)
if err != nil {
return nil, nil, err
}
}
return cniNet, rt, nil
}
type forEachNetworkFn func(*cniNetwork, *PodNetwork, *libcni.RuntimeConf) error
func (plugin *cniNetworkPlugin) forEachNetwork(podNetwork *PodNetwork, fromCache bool, actionFn forEachNetworkFn) error {
networks := podNetwork.Networks
if len(networks) == 0 {
networks = append(networks, plugin.GetDefaultNetworkName())
networks = append(networks, NetAttachment{
Name: plugin.GetDefaultNetworkName(),
})
}
for i, netName := range networks {
// Interface names start at "eth0" and count up for each network
ifName := fmt.Sprintf("eth%d", i)
network, err := plugin.getNetwork(netName)
allIfNames := make(map[string]bool)
for _, req := range networks {
if req.Ifname != "" {
// Make sure the requested name isn't already assigned
if allIfNames[req.Ifname] {
return fmt.Errorf("network %q requested interface name %q already assigned", req.Name, req.Ifname)
}
allIfNames[req.Ifname] = true
}
}
for _, network := range networks {
ifName := network.Ifname
if ifName == "" {
for i := 0; i < 10000; i++ {
candidate := fmt.Sprintf("eth%d", i)
if !allIfNames[candidate] {
allIfNames[candidate] = true
ifName = candidate
break
}
}
if ifName == "" {
return fmt.Errorf("failed to find free interface name for network %q", network.Name)
}
}
rt, err := buildCNIRuntimeConf(plugin.cacheDir, podNetwork, ifName, podNetwork.RuntimeConfig[network.Name])
if err != nil {
logrus.Errorf(err.Error())
logrus.Errorf("error building CNI runtime config: %v", err)
return err
}
if err := forEachFunc(network, ifName, podNetwork, podNetwork.RuntimeConfig[netName]); err != nil {
var cniNet *cniNetwork
if fromCache {
var newRt *libcni.RuntimeConf
cniNet, newRt, err = plugin.loadNetworkFromCache(network.Name, rt)
if err != nil {
logrus.Errorf("error loading cached network config: %v", err)
// fall back to loading from existing plugins on disk
} else {
// Use the updated RuntimeConf
rt = newRt
}
}
if cniNet == nil {
cniNet, err = plugin.getNetwork(network.Name)
if err != nil {
logrus.Errorf(err.Error())
return err
}
}
if err := actionFn(cniNet, podNetwork, rt); err != nil {
return err
}
}
return nil
}
func (plugin *cniNetworkPlugin) SetUpPod(podNetwork PodNetwork) ([]cnitypes.Result, error) {
func buildLoopbackRuntimeConf(cacheDir string, podNetwork *PodNetwork) *libcni.RuntimeConf {
return &libcni.RuntimeConf{
ContainerID: podNetwork.ID,
NetNS: podNetwork.NetNS,
CacheDir: cacheDir,
IfName: loIfname,
}
}
func (plugin *cniNetworkPlugin) SetUpPod(podNetwork PodNetwork) ([]NetResult, error) {
if err := plugin.networksAvailable(&podNetwork); err != nil {
return nil, err
}
@ -410,20 +543,26 @@ func (plugin *cniNetworkPlugin) SetUpPod(podNetwork PodNetwork) ([]cnitypes.Resu
plugin.podLock(podNetwork).Lock()
defer plugin.podUnlock(podNetwork)
_, err := plugin.loNetwork.addToNetwork(plugin.cacheDir, &podNetwork, "lo", RuntimeConfig{})
if err != nil {
loRt := buildLoopbackRuntimeConf(plugin.cacheDir, &podNetwork)
if _, err := plugin.loNetwork.addToNetwork(loRt, plugin.cniConfig); err != nil {
logrus.Errorf("Error while adding to cni lo network: %s", err)
return nil, err
}
results := make([]cnitypes.Result, 0)
if err := plugin.forEachNetwork(&podNetwork, func(network *cniNetwork, ifName string, podNetwork *PodNetwork, runtimeConfig RuntimeConfig) error {
result, err := network.addToNetwork(plugin.cacheDir, podNetwork, ifName, runtimeConfig)
results := make([]NetResult, 0)
if err := plugin.forEachNetwork(&podNetwork, false, func(network *cniNetwork, podNetwork *PodNetwork, rt *libcni.RuntimeConf) error {
result, err := network.addToNetwork(rt, plugin.cniConfig)
if err != nil {
logrus.Errorf("Error while adding pod to CNI network %q: %s", network.name, err)
return err
}
results = append(results, result)
results = append(results, NetResult{
Result: result,
NetAttachment: NetAttachment{
Name: network.name,
Ifname: rt.IfName,
},
})
return nil
}); err != nil {
return nil, err
@ -432,16 +571,99 @@ func (plugin *cniNetworkPlugin) SetUpPod(podNetwork PodNetwork) ([]cnitypes.Resu
return results, nil
}
func (plugin *cniNetworkPlugin) getCachedNetworkInfo(containerID string) ([]NetAttachment, error) {
cacheDir := libcni.CacheDir
if plugin.cacheDir != "" {
cacheDir = plugin.cacheDir
}
dirPath := filepath.Join(cacheDir, "results")
entries, err := ioutil.ReadDir(dirPath)
if err != nil {
return nil, err
}
fileNames := make([]string, 0, len(entries))
for _, e := range entries {
fileNames = append(fileNames, e.Name())
}
sort.Strings(fileNames)
attachments := []NetAttachment{}
for _, fname := range fileNames {
part := fmt.Sprintf("-%s-", containerID)
pos := strings.Index(fname, part)
if pos <= 0 || pos+len(part) >= len(fname) {
continue
}
cacheFile := filepath.Join(dirPath, fname)
bytes, err := ioutil.ReadFile(cacheFile)
if err != nil {
logrus.Warningf("failed to read CNI cache file %s: %v", cacheFile, err)
continue
}
cachedInfo := struct {
Kind string `json:"kind"`
IfName string `json:"ifName"`
ContainerID string `json:"containerID"`
NetName string `json:"networkName"`
}{}
if err := json.Unmarshal(bytes, &cachedInfo); err != nil {
logrus.Warningf("failed to unmarshal CNI cache file %s: %v", cacheFile, err)
continue
}
if cachedInfo.Kind != libcni.CNICacheV1 {
logrus.Warningf("unknown CNI cache file %s kind %q", cacheFile, cachedInfo.Kind)
continue
}
if cachedInfo.ContainerID != containerID {
continue
}
// Ignore the loopback interface; it's handled separately
if cachedInfo.IfName == loIfname && cachedInfo.NetName == loNetname {
continue
}
if cachedInfo.IfName == "" || cachedInfo.NetName == "" {
logrus.Warningf("missing CNI cache file %s ifname %q or netname %q", cacheFile, cachedInfo.IfName, cachedInfo.NetName)
continue
}
attachments = append(attachments, NetAttachment{
Name: cachedInfo.NetName,
Ifname: cachedInfo.IfName,
})
}
return attachments, nil
}
// TearDownPod tears down pod networks. Prefers cached pod attachment information
// but falls back to given network attachment information.
func (plugin *cniNetworkPlugin) TearDownPod(podNetwork PodNetwork) error {
if len(podNetwork.Networks) == 0 {
attachments, err := plugin.getCachedNetworkInfo(podNetwork.ID)
if err == nil && len(attachments) > 0 {
podNetwork.Networks = attachments
}
}
if err := plugin.networksAvailable(&podNetwork); err != nil {
return err
}
loRt := buildLoopbackRuntimeConf(plugin.cacheDir, &podNetwork)
if err := plugin.loNetwork.deleteFromNetwork(loRt, plugin.cniConfig); err != nil {
logrus.Errorf("Error while removing pod from CNI lo network: %v", err)
// Loopback teardown errors are not fatal
}
plugin.podLock(podNetwork).Lock()
defer plugin.podUnlock(podNetwork)
return plugin.forEachNetwork(&podNetwork, func(network *cniNetwork, ifName string, podNetwork *PodNetwork, runtimeConfig RuntimeConfig) error {
if err := network.deleteFromNetwork(plugin.cacheDir, podNetwork, ifName, runtimeConfig); err != nil {
return plugin.forEachNetwork(&podNetwork, true, func(network *cniNetwork, podNetwork *PodNetwork, rt *libcni.RuntimeConf) error {
if err := network.deleteFromNetwork(rt, plugin.cniConfig); err != nil {
logrus.Errorf("Error while removing pod from CNI network %q: %s", network.name, err)
return err
}
@ -451,19 +673,25 @@ func (plugin *cniNetworkPlugin) TearDownPod(podNetwork PodNetwork) error {
// GetPodNetworkStatus returns IP addressing and interface details for all
// networks attached to the pod.
func (plugin *cniNetworkPlugin) GetPodNetworkStatus(podNetwork PodNetwork) ([]cnitypes.Result, error) {
func (plugin *cniNetworkPlugin) GetPodNetworkStatus(podNetwork PodNetwork) ([]NetResult, error) {
plugin.podLock(podNetwork).Lock()
defer plugin.podUnlock(podNetwork)
results := make([]cnitypes.Result, 0)
if err := plugin.forEachNetwork(&podNetwork, func(network *cniNetwork, ifName string, podNetwork *PodNetwork, runtimeConfig RuntimeConfig) error {
result, err := network.checkNetwork(plugin.cacheDir, podNetwork, ifName, runtimeConfig, plugin.nsManager)
results := make([]NetResult, 0)
if err := plugin.forEachNetwork(&podNetwork, true, func(network *cniNetwork, podNetwork *PodNetwork, rt *libcni.RuntimeConf) error {
result, err := network.checkNetwork(rt, plugin.cniConfig, plugin.nsManager, podNetwork.NetNS)
if err != nil {
logrus.Errorf("Error while checking pod to CNI network %q: %s", network.name, err)
return err
}
if result != nil {
results = append(results, result)
results = append(results, NetResult{
Result: result,
NetAttachment: NetAttachment{
Name: network.name,
Ifname: rt.IfName,
},
})
}
return nil
}); err != nil {
@ -473,16 +701,9 @@ func (plugin *cniNetworkPlugin) GetPodNetworkStatus(podNetwork PodNetwork) ([]cn
return results, nil
}
func (network *cniNetwork) addToNetwork(cacheDir string, podNetwork *PodNetwork, ifName string, runtimeConfig RuntimeConfig) (cnitypes.Result, error) {
rt, err := buildCNIRuntimeConf(cacheDir, podNetwork, ifName, runtimeConfig)
if err != nil {
logrus.Errorf("Error adding network: %v", err)
return nil, err
}
netconf, cninet := network.NetworkConfig, network.CNIConfig
logrus.Infof("About to add CNI network %s (type=%v)", netconf.Name, netconf.Plugins[0].Network.Type)
res, err := cninet.AddNetworkList(context.Background(), netconf, rt)
func (network *cniNetwork) addToNetwork(rt *libcni.RuntimeConf, cni *libcni.CNIConfig) (cnitypes.Result, error) {
logrus.Infof("About to add CNI network %s (type=%v)", network.name, network.config.Plugins[0].Network.Type)
res, err := cni.AddNetworkList(context.Background(), network.config, rt)
if err != nil {
logrus.Errorf("Error adding network: %v", err)
return nil, err
@ -491,18 +712,10 @@ func (network *cniNetwork) addToNetwork(cacheDir string, podNetwork *PodNetwork,
return res, nil
}
func (network *cniNetwork) checkNetwork(cacheDir string, podNetwork *PodNetwork, ifName string, runtimeConfig RuntimeConfig, nsManager *nsManager) (cnitypes.Result, error) {
func (network *cniNetwork) checkNetwork(rt *libcni.RuntimeConf, cni *libcni.CNIConfig, nsManager *nsManager, netns string) (cnitypes.Result, error) {
logrus.Infof("About to check CNI network %s (type=%v)", network.name, network.config.Plugins[0].Network.Type)
rt, err := buildCNIRuntimeConf(cacheDir, podNetwork, ifName, runtimeConfig)
if err != nil {
logrus.Errorf("Error checking network: %v", err)
return nil, err
}
netconf, cninet := network.NetworkConfig, network.CNIConfig
logrus.Infof("About to check CNI network %s (type=%v)", netconf.Name, netconf.Plugins[0].Network.Type)
gtet, err := cniversion.GreaterThanOrEqualTo(netconf.CNIVersion, "0.4.0")
gtet, err := cniversion.GreaterThanOrEqualTo(network.config.CNIVersion, "0.4.0")
if err != nil {
return nil, err
}
@ -511,15 +724,15 @@ func (network *cniNetwork) checkNetwork(cacheDir string, podNetwork *PodNetwork,
// When CNIVersion supports Check, use it. Otherwise fall back on what was done initially.
if gtet {
err = cninet.CheckNetworkList(context.Background(), netconf, rt)
logrus.Infof("Checking CNI network %s (config version=%v)", netconf.Name, netconf.CNIVersion)
err = cni.CheckNetworkList(context.Background(), network.config, rt)
logrus.Infof("Checking CNI network %s (config version=%v)", network.name, network.config.CNIVersion)
if err != nil {
logrus.Errorf("Error checking network: %v", err)
return nil, err
}
}
result, err = cninet.GetNetworkListCachedResult(netconf, rt)
result, err = cni.GetNetworkListCachedResult(network.config, rt)
if err != nil {
logrus.Errorf("Error GetNetworkListCachedResult: %v", err)
return nil, err
@ -528,19 +741,19 @@ func (network *cniNetwork) checkNetwork(cacheDir string, podNetwork *PodNetwork,
}
// result doesn't exist, create one
logrus.Infof("Checking CNI network %s (config version=%v) nsManager=%v", netconf.Name, netconf.CNIVersion, nsManager)
logrus.Infof("Checking CNI network %s (config version=%v) nsManager=%v", network.name, network.config.CNIVersion, nsManager)
var cniInterface *cnicurrent.Interface
ips := []*cnicurrent.IPConfig{}
errs := []error{}
for _, version := range []string{"4", "6"} {
ip, mac, err := getContainerDetails(nsManager, podNetwork.NetNS, ifName, "-"+version)
ip, mac, err := getContainerDetails(nsManager, netns, rt.IfName, "-"+version)
if err == nil {
if cniInterface == nil {
cniInterface = &cnicurrent.Interface{
Name: ifName,
Name: rt.IfName,
Mac: mac.String(),
Sandbox: podNetwork.NetNS,
Sandbox: netns,
}
}
ips = append(ips, &cnicurrent.IPConfig{
@ -557,25 +770,23 @@ func (network *cniNetwork) checkNetwork(cacheDir string, podNetwork *PodNetwork,
}
result = &cnicurrent.Result{
CNIVersion: netconf.CNIVersion,
CNIVersion: network.config.CNIVersion,
Interfaces: []*cnicurrent.Interface{cniInterface},
IPs: ips,
}
return result, nil
}
func (network *cniNetwork) deleteFromNetwork(cacheDir string, podNetwork *PodNetwork, ifName string, runtimeConfig RuntimeConfig) error {
rt, err := buildCNIRuntimeConf(cacheDir, podNetwork, ifName, runtimeConfig)
// Result must be the same CNIVersion as the CNI config
converted, err := result.GetAsVersion(network.config.CNIVersion)
if err != nil {
logrus.Errorf("Error deleting network: %v", err)
return err
return nil, err
}
netconf, cninet := network.NetworkConfig, network.CNIConfig
logrus.Infof("About to del CNI network %s (type=%v)", netconf.Name, netconf.Plugins[0].Network.Type)
err = cninet.DelNetworkList(context.Background(), netconf, rt)
if err != nil {
return converted, nil
}
func (network *cniNetwork) deleteFromNetwork(rt *libcni.RuntimeConf, cni *libcni.CNIConfig) error {
logrus.Infof("About to del CNI network %s (type=%v)", network.name, network.config.Plugins[0].Network.Type)
if err := cni.DelNetworkList(context.Background(), network.config, rt); err != nil {
logrus.Errorf("Error deleting network: %v", err)
return err
}
@ -608,6 +819,16 @@ func buildCNIRuntimeConf(cacheDir string, podNetwork *PodNetwork, ifName string,
rt.Args = append(rt.Args, [2]string{"IP", ip})
}
// Add the requested static MAC to CNI_ARGS
mac := runtimeConfig.MAC
if mac != "" {
_, err := net.ParseMAC(mac)
if err != nil {
return nil, fmt.Errorf("unable to parse MAC address %q: %v", mac, err)
}
rt.Args = append(rt.Args, [2]string{"MAC", mac})
}
// Set PortMappings in Capabilities
if len(runtimeConfig.PortMappings) != 0 {
rt.CapabilityArgs["portMappings"] = runtimeConfig.PortMappings

View File

@ -44,6 +44,9 @@ type RuntimeConfig struct {
// with the hostlocal IP allocator. If left unset, an IP will be
// dynamically allocated.
IP string
// MAC is a static MAC address to be assigned to the network interface.
// If left unset, a MAC will be dynamically allocated.
MAC string
// PortMappings is the port mapping of the sandbox.
PortMappings []PortMapping
// Bandwidth is the bandwidth limiting of the pod
@ -75,9 +78,10 @@ type PodNetwork struct {
// NetNS is the network namespace path of the sandbox.
NetNS string
// 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
// Networks is a list of CNI network names (and optional interface
// names) to attach to the sandbox. Leave this list empty to attach the
// default network to the sandbox
Networks []NetAttachment
// NetworkConfig is configuration specific to a single CNI network.
// It is optional, and can be omitted for some or all specified networks
@ -85,6 +89,24 @@ type PodNetwork struct {
RuntimeConfig map[string]RuntimeConfig
}
// NetAttachment describes a container network attachment
type NetAttachment struct {
// NetName contains the name of the CNI network to which the container
// should be or is attached
Name string
// Ifname contains the optional interface name of the attachment
Ifname string
}
// NetResult contains the result the network attachment operation
type NetResult struct {
// Result is the CNI Result
Result types.Result
// NetAttachment contains the network and interface names of this
// network attachment
NetAttachment
}
// CNIPlugin is the interface that needs to be implemented by a plugin
type CNIPlugin interface {
// Name returns the plugin's name. This will be used when searching
@ -98,13 +120,13 @@ type CNIPlugin interface {
// 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.
SetUpPod(network PodNetwork) ([]types.Result, error)
SetUpPod(network PodNetwork) ([]NetResult, error)
// TearDownPod is the method called before a pod's sandbox container will be deleted
TearDownPod(network PodNetwork) error
// Status is the method called to obtain the ipv4 or ipv6 addresses of the pod sandbox
GetPodNetworkStatus(network PodNetwork) ([]types.Result, error)
GetPodNetworkStatus(network PodNetwork) ([]NetResult, error)
// NetworkStatus returns error if the network plugin is in error state
Status() error

5
vendor/modules.txt vendored
View File

@ -42,13 +42,14 @@ github.com/containerd/containerd/errdefs
github.com/containerd/continuity/fs
github.com/containerd/continuity/sysx
github.com/containerd/continuity/syscallx
# github.com/containernetworking/cni v0.7.1
# github.com/containernetworking/cni v0.7.2-0.20190904153231-83439463f784
github.com/containernetworking/cni/pkg/types
github.com/containernetworking/cni/pkg/types/current
github.com/containernetworking/cni/pkg/version
github.com/containernetworking/cni/libcni
github.com/containernetworking/cni/pkg/invoke
github.com/containernetworking/cni/pkg/types/020
github.com/containernetworking/cni/pkg/utils
# github.com/containernetworking/plugins v0.8.2
github.com/containernetworking/plugins/pkg/ns
github.com/containernetworking/plugins/pkg/ip
@ -169,7 +170,7 @@ github.com/coreos/go-systemd/sdjournal
github.com/coreos/go-systemd/journal
# github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f
github.com/coreos/pkg/dlopen
# github.com/cri-o/ocicni v0.1.1-0.20190702175919-7762645d18ca
# github.com/cri-o/ocicni v0.1.1-0.20190920040751-deac903fd99b
github.com/cri-o/ocicni/pkg/ocicni
# github.com/cyphar/filepath-securejoin v0.2.2
github.com/cyphar/filepath-securejoin