ocicni: Support asynchronous network config creation

We need to support cases where InitCNI() is called before
any CNI configuration files have been installed. This is
for example happening when deploying a k8s cluster with kubeadm.
kubeadm will start the DNS pod and it is left to the caller to
pick a network overlay and create the corresponding pods, that
will typically install a CNI configuration file first.

Here we address that issue by doing 2 things:

- Not returning an error when the default CNI config files
  directory is empty.
- If it is empty, we start a monitoring thread (fsnotify based)
  that will synchronize the network configuration when a CNI
  file is installed there.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
This commit is contained in:
Samuel Ortiz 2017-04-06 11:23:05 +02:00
parent bc4ac7ce04
commit 63c7a7c99b

View file

@ -6,11 +6,11 @@ import (
"os/exec" "os/exec"
"sort" "sort"
"sync" "sync"
"time"
"github.com/Sirupsen/logrus" "github.com/Sirupsen/logrus"
"github.com/containernetworking/cni/libcni" "github.com/containernetworking/cni/libcni"
cnitypes "github.com/containernetworking/cni/pkg/types" cnitypes "github.com/containernetworking/cni/pkg/types"
"github.com/fsnotify/fsnotify"
) )
type cniNetworkPlugin struct { type cniNetworkPlugin struct {
@ -23,6 +23,8 @@ type cniNetworkPlugin struct {
pluginDir string pluginDir string
cniDirs []string cniDirs []string
vendorCNIDirPrefix string vendorCNIDirPrefix string
monitorNetDirChan chan struct{}
} }
type cniNetwork struct { type cniNetwork struct {
@ -31,6 +33,49 @@ type cniNetwork struct {
CNIConfig libcni.CNI CNIConfig libcni.CNI
} }
var errMissingDefaultNetwork = errors.New("Missing CNI default network")
func (plugin *cniNetworkPlugin) monitorNetDir() {
watcher, err := fsnotify.NewWatcher()
if err != nil {
logrus.Errorf("could not create new watcher %v", err)
return
}
defer watcher.Close()
go func() {
for {
select {
case event := <-watcher.Events:
logrus.Debugf("CNI monitoring event %v", event)
if event.Op&fsnotify.Create != fsnotify.Create {
continue
}
if err = plugin.syncNetworkConfig(); err == nil {
logrus.Debugf("CNI asynchronous setting succeeded")
close(plugin.monitorNetDirChan)
return
}
logrus.Errorf("CNI setting failed, continue monitoring: %v", err)
case err := <-watcher.Errors:
logrus.Errorf("CNI monitoring error %v", err)
close(plugin.monitorNetDirChan)
return
}
}
}()
if err = watcher.Add(plugin.pluginDir); err != nil {
logrus.Error(err)
return
}
<-plugin.monitorNetDirChan
}
// InitCNI takes the plugin directory and cni directories where the cni files should be searched for // InitCNI takes the plugin directory and cni directories where the cni files should be searched for
// Returns a valid plugin object and any error // Returns a valid plugin object and any error
func InitCNI(pluginDir string, cniDirs ...string) (CNIPlugin, error) { func InitCNI(pluginDir string, cniDirs ...string) (CNIPlugin, error) {
@ -44,19 +89,16 @@ func InitCNI(pluginDir string, cniDirs ...string) (CNIPlugin, error) {
// check if a default network exists, otherwise dump the CNI search and return a noop plugin // check if a default network exists, otherwise dump the CNI search and return a noop plugin
_, err = getDefaultCNINetwork(plugin.pluginDir, plugin.cniDirs, plugin.vendorCNIDirPrefix) _, err = getDefaultCNINetwork(plugin.pluginDir, plugin.cniDirs, plugin.vendorCNIDirPrefix)
if err != nil { if err != nil {
logrus.Warningf("Error in finding usable CNI plugin - %v", err) if err != errMissingDefaultNetwork {
// create a noop plugin instead logrus.Warningf("Error in finding usable CNI plugin - %v", err)
return &cniNoOp{}, nil // create a noop plugin instead
return &cniNoOp{}, nil
}
// We do not have a default network, we start the monitoring thread.
go plugin.monitorNetDir()
} }
// sync network config from pluginDir periodically to detect network config updates
go func() {
t := time.NewTimer(10 * time.Second)
for {
plugin.syncNetworkConfig()
<-t.C
}
}()
return plugin, nil return plugin, nil
} }
@ -67,10 +109,13 @@ func probeNetworkPluginsWithVendorCNIDirPrefix(pluginDir string, cniDirs []strin
pluginDir: pluginDir, pluginDir: pluginDir,
cniDirs: cniDirs, cniDirs: cniDirs,
vendorCNIDirPrefix: vendorCNIDirPrefix, vendorCNIDirPrefix: vendorCNIDirPrefix,
monitorNetDirChan: make(chan struct{}),
} }
// sync NetworkConfig in best effort during probing. // sync NetworkConfig in best effort during probing.
plugin.syncNetworkConfig() if err := plugin.syncNetworkConfig(); err != nil {
logrus.Error(err)
}
return plugin return plugin
} }
@ -87,7 +132,7 @@ func getDefaultCNINetwork(pluginDir string, cniDirs []string, vendorCNIDirPrefix
case err != nil: case err != nil:
return nil, err return nil, err
case len(files) == 0: case len(files) == 0:
return nil, fmt.Errorf("No networks found in %s", pluginDir) return nil, errMissingDefaultNetwork
} }
sort.Strings(files) sort.Strings(files)
@ -142,13 +187,15 @@ func getLoNetwork(cniDirs []string, vendorDirPrefix string) *cniNetwork {
return loNetwork return loNetwork
} }
func (plugin *cniNetworkPlugin) syncNetworkConfig() { func (plugin *cniNetworkPlugin) syncNetworkConfig() error {
network, err := getDefaultCNINetwork(plugin.pluginDir, plugin.cniDirs, plugin.vendorCNIDirPrefix) network, err := getDefaultCNINetwork(plugin.pluginDir, plugin.cniDirs, plugin.vendorCNIDirPrefix)
if err != nil { if err != nil {
logrus.Errorf("error updating cni config: %s", err) logrus.Errorf("error updating cni config: %s", err)
return return err
} }
plugin.setDefaultNetwork(network) plugin.setDefaultNetwork(network)
return nil
} }
func (plugin *cniNetworkPlugin) getDefaultNetwork() *cniNetwork { func (plugin *cniNetworkPlugin) getDefaultNetwork() *cniNetwork {