github.com/elfadel/cilium@v1.6.12/pkg/datapath/loader/cache.go (about)

     1  // Copyright 2019 Authors of Cilium
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package loader
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"os"
    21  	"path/filepath"
    22  	"sync"
    23  
    24  	"github.com/cilium/cilium/common"
    25  	"github.com/cilium/cilium/pkg/controller"
    26  	"github.com/cilium/cilium/pkg/datapath"
    27  	"github.com/cilium/cilium/pkg/defaults"
    28  	"github.com/cilium/cilium/pkg/elf"
    29  	"github.com/cilium/cilium/pkg/lock"
    30  	"github.com/cilium/cilium/pkg/logging/logfields"
    31  	"github.com/cilium/cilium/pkg/option"
    32  	"github.com/cilium/cilium/pkg/serializer"
    33  
    34  	"github.com/fsnotify/fsnotify"
    35  	"github.com/sirupsen/logrus"
    36  )
    37  
    38  var (
    39  	once sync.Once
    40  
    41  	// templateCache is the cache of pre-compiled datapaths.
    42  	templateCache            *objectCache
    43  	templateWatcherQueueSize = 10
    44  
    45  	ignoredELFPrefixes = []string{
    46  		"2/",                    // Calls within the endpoint
    47  		"HOST_IP",               // Global
    48  		"IPV6_NODEPORT",         // Global
    49  		"ROUTER_IP",             // Global
    50  		"SNAT_IPV6_EXTERNAL",    // Global
    51  		"cilium_ct",             // All CT maps, including local
    52  		"cilium_encrypt_state",  // Global
    53  		"cilium_events",         // Global
    54  		"cilium_ipcache",        // Global
    55  		"cilium_lb",             // Global
    56  		"cilium_lxc",            // Global
    57  		"cilium_metrics",        // Global
    58  		"cilium_nodeport_neigh", // All nodeport neigh maps
    59  		"cilium_policy",         // Global
    60  		"cilium_proxy",          // Global
    61  		"cilium_signals",        // Global
    62  		"cilium_snat",           // All SNAT maps
    63  		"cilium_tunnel",         // Global
    64  		"from-container",        // Prog name
    65  		"to-container",          // Prog name
    66  	}
    67  )
    68  
    69  // Init initializes the datapath cache with base program hashes derived from
    70  // the LocalNodeConfiguration.
    71  func Init(dp datapath.Datapath, nodeCfg *datapath.LocalNodeConfiguration) {
    72  	once.Do(func() {
    73  		templateCache = NewObjectCache(dp, nodeCfg)
    74  		ignorePrefixes := ignoredELFPrefixes
    75  		if !option.Config.EnableIPv4 {
    76  			ignorePrefixes = append(ignorePrefixes, "LXC_IPV4")
    77  		}
    78  		if !option.Config.EnableIPv6 {
    79  			ignorePrefixes = append(ignorePrefixes, "LXC_IP_")
    80  		}
    81  		elf.IgnoreSymbolPrefixes(ignorePrefixes)
    82  	})
    83  	templateCache.Update(nodeCfg)
    84  }
    85  
    86  // RestoreTemplates populates the object cache from templates on the filesystem
    87  // at the specified path.
    88  func RestoreTemplates(stateDir string) error {
    89  	// Simplest implementation: Just garbage-collect everything.
    90  	// In future we should make this smarter.
    91  	path := filepath.Join(stateDir, defaults.TemplatesDir)
    92  	err := os.RemoveAll(path)
    93  	if err == nil || os.IsNotExist(err) {
    94  		return nil
    95  	}
    96  	return &os.PathError{
    97  		Op:   "failed to remove old BPF templates",
    98  		Path: path,
    99  		Err:  err,
   100  	}
   101  }
   102  
   103  // objectCache is a map from a hash of the datapath to the path on the
   104  // filesystem where its corresponding BPF object file exists.
   105  type objectCache struct {
   106  	lock.Mutex
   107  	datapath.Datapath
   108  
   109  	workingDirectory string
   110  	baseHash         *datapathHash
   111  
   112  	// newTemplates is notified whenever template is added to the objectCache.
   113  	newTemplates        chan string
   114  	templateWatcherDone chan struct{}
   115  
   116  	// toPath maps a hash to the filesystem path of the corresponding object.
   117  	toPath map[string]string
   118  
   119  	// compileQueue maps a hash to a queue which ensures that only one
   120  	// attempt is made concurrently to compile the corresponding template.
   121  	compileQueue map[string]*serializer.FunctionQueue
   122  }
   123  
   124  func newObjectCache(dp datapath.Datapath, nodeCfg *datapath.LocalNodeConfiguration, workingDir string) *objectCache {
   125  	oc := &objectCache{
   126  		Datapath:            dp,
   127  		workingDirectory:    workingDir,
   128  		newTemplates:        make(chan string, templateWatcherQueueSize),
   129  		templateWatcherDone: make(chan struct{}),
   130  		toPath:              make(map[string]string),
   131  		compileQueue:        make(map[string]*serializer.FunctionQueue),
   132  	}
   133  	oc.Update(nodeCfg)
   134  	controller.NewManager().UpdateController("template-dir-watcher",
   135  		controller.ControllerParams{
   136  			DoFunc: oc.watchTemplatesDirectory,
   137  			// No run interval but needs to re-run on errors.
   138  		})
   139  
   140  	return oc
   141  }
   142  
   143  // NewObjectCache creates a new cache for datapath objects, basing the hash
   144  // upon the configuration of the datapath and the specified node configuration.
   145  func NewObjectCache(dp datapath.Datapath, nodeCfg *datapath.LocalNodeConfiguration) *objectCache {
   146  	return newObjectCache(dp, nodeCfg, option.Config.StateDir)
   147  }
   148  
   149  // Update may be called to update the base hash for configuration of datapath
   150  // configuration that applies across the node.
   151  func (o *objectCache) Update(nodeCfg *datapath.LocalNodeConfiguration) {
   152  	newHash := hashDatapath(o.Datapath, nodeCfg, nil, nil)
   153  
   154  	o.Lock()
   155  	defer o.Unlock()
   156  	o.baseHash = newHash
   157  }
   158  
   159  // serialize finds the channel that serializes builds against the same hash.
   160  // Returns the channel and whether or not the caller needs to compile the
   161  // datapath for this hash.
   162  func (o *objectCache) serialize(hash string) (fq *serializer.FunctionQueue, found bool) {
   163  	o.Lock()
   164  	defer o.Unlock()
   165  
   166  	fq, compiled := o.compileQueue[hash]
   167  	if !compiled {
   168  		fq = serializer.NewFunctionQueue(1)
   169  		o.compileQueue[hash] = fq
   170  	}
   171  	return fq, compiled
   172  }
   173  
   174  func (o *objectCache) lookup(hash string) (string, bool) {
   175  	o.Lock()
   176  	defer o.Unlock()
   177  	path, exists := o.toPath[hash]
   178  	return path, exists
   179  }
   180  
   181  func (o *objectCache) insert(hash, objectPath string) error {
   182  	o.Lock()
   183  	defer o.Unlock()
   184  	o.toPath[hash] = objectPath
   185  
   186  	scopedLog := log.WithField(logfields.Path, objectPath)
   187  	select {
   188  	case o.newTemplates <- objectPath:
   189  	case <-o.templateWatcherDone:
   190  		// This means that the controller was stopped and Cilium is
   191  		// shutting down; don't bother complaining too loudly.
   192  		scopedLog.Debug("Failed to watch for template filesystem changes")
   193  	default:
   194  		// Unusual case; send on channel was blocked.
   195  		scopedLog.Warn("Failed to watch for template filesystem changes")
   196  	}
   197  	return nil
   198  }
   199  
   200  func (o *objectCache) delete(hash string) {
   201  	o.Lock()
   202  	defer o.Unlock()
   203  	delete(o.toPath, hash)
   204  	delete(o.compileQueue, hash)
   205  }
   206  
   207  // build attempts to compile and cache a datapath template object file
   208  // corresponding to the specified endpoint configuration.
   209  func (o *objectCache) build(ctx context.Context, cfg *templateCfg, hash string) error {
   210  	templatePath := filepath.Join(o.workingDirectory, defaults.TemplatesDir, hash)
   211  	headerPath := filepath.Join(templatePath, common.CHeaderFileName)
   212  	objectPath := filepath.Join(templatePath, endpointObj)
   213  
   214  	if err := os.MkdirAll(templatePath, defaults.StateDirRights); err != nil {
   215  		return &os.PathError{
   216  			Op:   "failed to create template directory",
   217  			Path: templatePath,
   218  			Err:  err,
   219  		}
   220  	}
   221  
   222  	f, err := os.Create(headerPath)
   223  	if err != nil {
   224  		return &os.PathError{
   225  			Op:   "failed to open template header for writing",
   226  			Path: headerPath,
   227  			Err:  err,
   228  		}
   229  	}
   230  
   231  	if err = o.Datapath.WriteEndpointConfig(f, cfg); err != nil {
   232  		return &os.PathError{
   233  			Op:   "failed to write template header",
   234  			Path: headerPath,
   235  			Err:  err,
   236  		}
   237  	}
   238  
   239  	cfg.stats.bpfCompilation.Start()
   240  	err = compileTemplate(ctx, templatePath)
   241  	cfg.stats.bpfCompilation.End(err == nil)
   242  	if err != nil {
   243  		return &os.PathError{
   244  			Op:   "failed to compile template program",
   245  			Path: templatePath,
   246  			Err:  err,
   247  		}
   248  	}
   249  
   250  	log.WithFields(logrus.Fields{
   251  		logfields.Path:               objectPath,
   252  		logfields.BPFCompilationTime: cfg.stats.bpfCompilation.Total(),
   253  	}).Info("Compiled new BPF template")
   254  
   255  	o.insert(hash, objectPath)
   256  	return nil
   257  }
   258  
   259  // fetchOrCompile attempts to fetch the path to the datapath object
   260  // corresponding to the provided endpoint configuration, or if this
   261  // configuration is not yet compiled, compiles it. It will block if multiple
   262  // threads attempt to concurrently fetchOrCompile a template binary for the
   263  // same set of EndpointConfiguration.
   264  //
   265  // Returns the path to the compiled template datapath object and whether the
   266  // object was compiled, or an error.
   267  func (o *objectCache) fetchOrCompile(ctx context.Context, cfg datapath.EndpointConfiguration, stats *SpanStat) (path string, compiled bool, err error) {
   268  	var hash string
   269  	hash, err = o.baseHash.sumEndpoint(o, cfg, false)
   270  	if err != nil {
   271  		return "", false, err
   272  	}
   273  
   274  	// Capture the time spent waiting for the template to compile.
   275  	if stats != nil {
   276  		stats.bpfWaitForELF.Start()
   277  		defer func() {
   278  			// Wrap to ensure that "err" is compared upon return.
   279  			stats.bpfWaitForELF.End(err == nil)
   280  		}()
   281  	}
   282  
   283  	scopedLog := log.WithField(logfields.BPFHeaderfileHash, hash)
   284  
   285  	// Serializes attempts to compile this cfg.
   286  	fq, compiled := o.serialize(hash)
   287  	if !compiled {
   288  		fq.Enqueue(func() error {
   289  			defer fq.Stop()
   290  			templateCfg := wrap(cfg, stats)
   291  			err := o.build(ctx, templateCfg, hash)
   292  			if err != nil {
   293  				scopedLog.WithError(err).Error("BPF template object creation failed")
   294  				o.Lock()
   295  				delete(o.compileQueue, hash)
   296  				o.Unlock()
   297  			}
   298  			return err
   299  		}, serializer.NoRetry)
   300  	}
   301  
   302  	// Wait until the build completes.
   303  	if err = fq.Wait(ctx); err != nil {
   304  		scopedLog.WithError(err).Warning("Error while waiting for BPF template compilation")
   305  		return "", false, fmt.Errorf("BPF template compilation failed: %s", err)
   306  	}
   307  
   308  	// Fetch the result of the compilation.
   309  	path, ok := o.lookup(hash)
   310  	if !ok {
   311  		err := fmt.Errorf("Could not locate previously compiled BPF template")
   312  		scopedLog.WithError(err).Warning("BPF template compilation unsuccessful")
   313  		return "", false, err
   314  	}
   315  
   316  	return path, !compiled, nil
   317  }
   318  
   319  func (o *objectCache) watchTemplatesDirectory(ctx context.Context) error {
   320  	templateWatcher, err := fsnotify.NewWatcher()
   321  	if err != nil {
   322  		return err
   323  	}
   324  	defer func() {
   325  		close(o.templateWatcherDone)
   326  		templateWatcher.Close()
   327  	}()
   328  
   329  	for {
   330  		select {
   331  		// Watch for new templates being compiled and add to the filesystem watcher
   332  		case templatePath := <-o.newTemplates:
   333  			if err = templateWatcher.Add(templatePath); err != nil {
   334  				log.WithFields(logrus.Fields{
   335  					logfields.Path: templatePath,
   336  				}).WithError(err).Warning("Failed to watch templates directory")
   337  			} else {
   338  				log.WithFields(logrus.Fields{
   339  					logfields.Path: templatePath,
   340  				}).Debug("Watching template path")
   341  			}
   342  		// Handle filesystem deletes for current templates
   343  		case event, open := <-templateWatcher.Events:
   344  			if !open {
   345  				break
   346  			}
   347  			if event.Op&fsnotify.Remove != 0 {
   348  				log.WithField(logfields.Path, event.Name).Debug("Detected template removal")
   349  				templateHash := filepath.Base(filepath.Dir(event.Name))
   350  				o.delete(templateHash)
   351  			} else {
   352  				log.WithField("event", event).Debug("Ignoring template FS event")
   353  			}
   354  		case err, _ = <-templateWatcher.Errors:
   355  			return err
   356  		case <-ctx.Done():
   357  			return ctx.Err()
   358  		}
   359  	}
   360  }
   361  
   362  // EndpointHash hashes the specified endpoint configuration with the current
   363  // datapath hash cache and returns the hash as string.
   364  func EndpointHash(cfg datapath.EndpointConfiguration) (string, error) {
   365  	return templateCache.baseHash.sumEndpoint(templateCache, cfg, true)
   366  }