| /* |
| Copyright The containerd 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 server |
| |
| import ( |
| "fmt" |
| "os" |
| "path/filepath" |
| "sync" |
| |
| "github.com/containerd/go-cni" |
| "github.com/containerd/log" |
| "github.com/fsnotify/fsnotify" |
| ) |
| |
| // cniNetConfSyncer is used to reload cni network conf triggered by fs change |
| // events. |
| type cniNetConfSyncer struct { |
| // only used for lastSyncStatus |
| sync.RWMutex |
| lastSyncStatus error |
| |
| watcher *fsnotify.Watcher |
| confDir string |
| netPlugin cni.CNI |
| loadOpts []cni.Opt |
| } |
| |
| // newCNINetConfSyncer creates cni network conf syncer. |
| func newCNINetConfSyncer(confDir string, netPlugin cni.CNI, loadOpts []cni.Opt) (*cniNetConfSyncer, error) { |
| watcher, err := fsnotify.NewWatcher() |
| if err != nil { |
| return nil, fmt.Errorf("failed to create fsnotify watcher: %w", err) |
| } |
| |
| // /etc/cni has to be readable for non-root users (0755), because /etc/cni/tuning/allowlist.conf is used for rootless mode too. |
| // This file was introduced in CNI plugins 1.2.0 (https://github.com/containernetworking/plugins/pull/693), and its path is hard-coded. |
| confDirParent := filepath.Dir(confDir) |
| if err := os.MkdirAll(confDirParent, 0755); err != nil { |
| return nil, fmt.Errorf("failed to create the parent of the cni conf dir=%s: %w", confDirParent, err) |
| } |
| |
| if err := os.MkdirAll(confDir, 0700); err != nil { |
| return nil, fmt.Errorf("failed to create cni conf dir=%s for watch: %w", confDir, err) |
| } |
| |
| if err := watcher.Add(confDir); err != nil { |
| return nil, fmt.Errorf("failed to watch cni conf dir %s: %w", confDir, err) |
| } |
| |
| syncer := &cniNetConfSyncer{ |
| watcher: watcher, |
| confDir: confDir, |
| netPlugin: netPlugin, |
| loadOpts: loadOpts, |
| } |
| |
| if err := syncer.netPlugin.Load(syncer.loadOpts...); err != nil { |
| log.L.WithError(err).Error("failed to load cni during init, please check CRI plugin status before setting up network for pods") |
| syncer.updateLastStatus(err) |
| } |
| return syncer, nil |
| } |
| |
| // syncLoop monitors any fs change events from cni conf dir and tries to reload |
| // cni configuration. |
| func (syncer *cniNetConfSyncer) syncLoop() error { |
| for { |
| select { |
| case event, ok := <-syncer.watcher.Events: |
| if !ok { |
| log.L.Debugf("cni watcher channel is closed") |
| return nil |
| } |
| // Only reload config when receiving write/rename/remove |
| // events |
| // |
| // TODO(fuweid): Might only reload target cni config |
| // files to prevent no-ops. |
| if event.Has(fsnotify.Chmod) || event.Has(fsnotify.Create) { |
| log.L.Debugf("ignore event from cni conf dir: %s", event) |
| continue |
| } |
| log.L.Debugf("receiving change event from cni conf dir: %s", event) |
| |
| // If the confDir is removed, stop watching. |
| if event.Name == syncer.confDir && (event.Has(fsnotify.Rename) || event.Has(fsnotify.Remove)) { |
| return fmt.Errorf("cni conf dir is removed, stop watching") |
| } |
| |
| lerr := syncer.netPlugin.Load(syncer.loadOpts...) |
| if lerr != nil { |
| log.L.WithError(lerr). |
| Errorf("failed to reload cni configuration after receiving fs change event(%s)", event) |
| } |
| syncer.updateLastStatus(lerr) |
| |
| case err := <-syncer.watcher.Errors: |
| if err != nil { |
| log.L.WithError(err).Error("failed to continue sync cni conf change") |
| return err |
| } |
| } |
| } |
| } |
| |
| // lastStatus retrieves last sync status. |
| func (syncer *cniNetConfSyncer) lastStatus() error { |
| syncer.RLock() |
| defer syncer.RUnlock() |
| return syncer.lastSyncStatus |
| } |
| |
| // updateLastStatus will be called after every single cni load. |
| func (syncer *cniNetConfSyncer) updateLastStatus(err error) { |
| syncer.Lock() |
| defer syncer.Unlock() |
| syncer.lastSyncStatus = err |
| } |
| |
| // stop stops watcher in the syncLoop. |
| func (syncer *cniNetConfSyncer) stop() error { |
| return syncer.watcher.Close() |
| } |