CNI networks: reload networks if needed

The current implementation of the CNI network interface only loads the
networks on the first call and saves them in a map. This is done to safe
performance and not having to reload all configs every time which will be
costly for many networks.

The problem with this approach is that if a network is created by
another process it will not be picked up by the already running podman
process. This is not a problem for the short lived podman commands but
it is problematic for the podman service.

To make sure we always have the actual networks store the mtime of the
config directory. If it changed since the last read we have to read
again.

Fixes #11828

Signed-off-by: Paul Holzinger <pholzing@redhat.com>
This commit is contained in:
Paul Holzinger
2021-10-04 16:00:43 +02:00
parent 36821d302e
commit a726043d0b
4 changed files with 22 additions and 29 deletions

View File

@ -1020,28 +1020,6 @@ var _ = Describe("Config", func() {
Expect(err).To(HaveOccurred()) Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("subnet 10.10.0.0/24 is already used on the host or by another config")) Expect(err.Error()).To(ContainSubstring("subnet 10.10.0.0/24 is already used on the host or by another config"))
}) })
It("remove network should not error when config file does not exists on disk", func() {
name := "mynet"
network := types.Network{Name: name}
_, err := libpodNet.NetworkCreate(network)
Expect(err).To(BeNil())
path := filepath.Join(cniConfDir, name+".conflist")
Expect(path).To(BeARegularFile())
err = os.Remove(path)
Expect(err).To(BeNil())
Expect(path).ToNot(BeARegularFile())
err = libpodNet.NetworkRemove(name)
Expect(err).To(BeNil())
nets, err := libpodNet.NetworkList()
Expect(err).To(BeNil())
Expect(nets).To(HaveLen(1))
Expect(nets).ToNot(ContainElement(HaveNetworkName(name)))
})
}) })
Context("network load valid existing ones", func() { Context("network load valid existing ones", func() {

View File

@ -10,6 +10,7 @@ import (
"net" "net"
"os" "os"
"strings" "strings"
"time"
"github.com/containernetworking/cni/libcni" "github.com/containernetworking/cni/libcni"
"github.com/containers/podman/v3/libpod/define" "github.com/containers/podman/v3/libpod/define"
@ -40,6 +41,9 @@ type cniNetwork struct {
// lock is a internal lock for critical operations // lock is a internal lock for critical operations
lock lockfile.Locker lock lockfile.Locker
// modTime is the timestamp when the config dir was modified
modTime time.Time
// networks is a map with loaded networks, the key is the network name // networks is a map with loaded networks, the key is the network name
networks map[string]*network networks map[string]*network
} }
@ -113,10 +117,22 @@ func (n *cniNetwork) Drivers() []string {
} }
func (n *cniNetwork) loadNetworks() error { func (n *cniNetwork) loadNetworks() error {
// skip loading networks if they are already loaded // check the mod time of the config dir
if n.networks != nil { f, err := os.Stat(n.cniConfigDir)
if err != nil {
return err
}
modTime := f.ModTime()
// skip loading networks if they are already loaded and
// if the config dir was not modified since the last call
if n.networks != nil && modTime.Equal(n.modTime) {
return nil return nil
} }
// make sure the remove all networks before we reload them
n.networks = nil
n.modTime = modTime
// FIXME: do we have to support other file types as well, e.g. .conf? // FIXME: do we have to support other file types as well, e.g. .conf?
files, err := libcni.ConfFiles(n.cniConfigDir, []string{".conflist"}) files, err := libcni.ConfFiles(n.cniConfigDir, []string{".conflist"})
if err != nil { if err != nil {
@ -153,7 +169,7 @@ func (n *cniNetwork) loadNetworks() error {
logrus.Errorf("CNI config list %s could not be converted to a libpod config, skipping: %v", file, err) logrus.Errorf("CNI config list %s could not be converted to a libpod config, skipping: %v", file, err)
continue continue
} }
logrus.Tracef("Successfully loaded network %s: %v", net.Name, net) logrus.Debugf("Successfully loaded network %s: %v", net.Name, net)
networkInfo := network{ networkInfo := network{
filename: file, filename: file,
cniNet: conf, cniNet: conf,

View File

@ -489,8 +489,7 @@ func makeRuntime(ctx context.Context, runtime *Runtime) (retErr error) {
DefaultNetwork: runtime.config.Network.DefaultNetwork, DefaultNetwork: runtime.config.Network.DefaultNetwork,
DefaultSubnet: runtime.config.Network.DefaultSubnet, DefaultSubnet: runtime.config.Network.DefaultSubnet,
IsMachine: runtime.config.Engine.MachineEnabled, IsMachine: runtime.config.Engine.MachineEnabled,
// TODO use cni.lock LockFile: filepath.Join(runtime.config.Network.NetworkConfigDir, "cni.lock"),
LockFile: filepath.Join(runtime.config.Network.NetworkConfigDir, "cni1.lock"),
}) })
if err != nil { if err != nil {
return errors.Wrapf(err, "could not create network interface") return errors.Wrapf(err, "could not create network interface")

View File

@ -131,8 +131,8 @@ t DELETE libpod/networks/network2 200 \
.[0].Err=null .[0].Err=null
# test until filter - libpod api # test until filter - libpod api
t POST libpod/networks/create name='"network5"' labels='{"xyz":""}' 200 \ # create network via cli to test that the server can use it
.name=network5 podman network create --label xyz network5
# with date way back in the past, network should not be deleted # with date way back in the past, network should not be deleted
t POST libpod/networks/prune?filters='{"until":["500000"]}' 200 t POST libpod/networks/prune?filters='{"until":["500000"]}' 200