203 lines
		
	
	
		
			4.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			203 lines
		
	
	
		
			4.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| /*
 | |
|    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 main
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"encoding/json"
 | |
| 	"fmt"
 | |
| 	"io"
 | |
| 	"os"
 | |
| 	"path/filepath"
 | |
| 	"syscall"
 | |
| 
 | |
| 	"github.com/containerd/containerd/pkg/failpoint"
 | |
| 	"github.com/containerd/continuity"
 | |
| 	"github.com/containernetworking/cni/pkg/invoke"
 | |
| 	"github.com/containernetworking/cni/pkg/skel"
 | |
| 	"github.com/containernetworking/cni/pkg/version"
 | |
| )
 | |
| 
 | |
| const delegatedPlugin = "bridge"
 | |
| 
 | |
| type netConf struct {
 | |
| 	RuntimeConfig struct {
 | |
| 		PodAnnotations inheritedPodAnnotations `json:"io.kubernetes.cri.pod-annotations"`
 | |
| 	} `json:"runtimeConfig,omitempty"`
 | |
| }
 | |
| 
 | |
| type inheritedPodAnnotations struct {
 | |
| 	// FailpointConfPath represents filepath of failpoint settings.
 | |
| 	FailpointConfPath string `json:"failpoint.cni.containerd.io/confpath,omitempty"`
 | |
| }
 | |
| 
 | |
| // failpointConf is used to describe cmdAdd/cmdDel/cmdCheck command's failpoint.
 | |
| type failpointConf struct {
 | |
| 	Add   string `json:"cmdAdd,omitempty"`
 | |
| 	Del   string `json:"cmdDel,omitempty"`
 | |
| 	Check string `json:"cmdCheck,omitempty"`
 | |
| }
 | |
| 
 | |
| func main() {
 | |
| 	skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, "bridge with failpoint support")
 | |
| }
 | |
| 
 | |
| func cmdAdd(args *skel.CmdArgs) error {
 | |
| 	if err := handleFailpoint(args, "ADD"); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	result, err := invoke.DelegateAdd(context.TODO(), delegatedPlugin, args.StdinData, nil)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	return result.Print()
 | |
| }
 | |
| 
 | |
| func cmdCheck(args *skel.CmdArgs) error {
 | |
| 	if err := handleFailpoint(args, "CHECK"); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	return invoke.DelegateCheck(context.TODO(), delegatedPlugin, args.StdinData, nil)
 | |
| }
 | |
| 
 | |
| func cmdDel(args *skel.CmdArgs) error {
 | |
| 	if err := handleFailpoint(args, "DEL"); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	return invoke.DelegateDel(context.TODO(), delegatedPlugin, args.StdinData, nil)
 | |
| }
 | |
| 
 | |
| func handleFailpoint(args *skel.CmdArgs, cmdKind string) error {
 | |
| 	var conf netConf
 | |
| 	if err := json.Unmarshal(args.StdinData, &conf); err != nil {
 | |
| 		return fmt.Errorf("failed to parse network configuration: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	confPath := conf.RuntimeConfig.PodAnnotations.FailpointConfPath
 | |
| 	if len(confPath) == 0 {
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	control, err := newFailpointControl(confPath)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	evalFn, err := control.delegatedEvalFn(cmdKind)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	return evalFn()
 | |
| }
 | |
| 
 | |
| type failpointControl struct {
 | |
| 	confPath string
 | |
| }
 | |
| 
 | |
| func newFailpointControl(confPath string) (*failpointControl, error) {
 | |
| 	if !filepath.IsAbs(confPath) {
 | |
| 		return nil, fmt.Errorf("failpoint confPath(%s) is required to be absolute", confPath)
 | |
| 	}
 | |
| 
 | |
| 	return &failpointControl{
 | |
| 		confPath: confPath,
 | |
| 	}, nil
 | |
| }
 | |
| 
 | |
| func (c *failpointControl) delegatedEvalFn(cmdKind string) (failpoint.EvalFn, error) {
 | |
| 	var resFn failpoint.EvalFn = nopEvalFn
 | |
| 
 | |
| 	if err := c.updateTx(func(conf *failpointConf) error {
 | |
| 		var fpStr *string
 | |
| 
 | |
| 		switch cmdKind {
 | |
| 		case "ADD":
 | |
| 			fpStr = &conf.Add
 | |
| 		case "DEL":
 | |
| 			fpStr = &conf.Del
 | |
| 		case "CHECK":
 | |
| 			fpStr = &conf.Check
 | |
| 		}
 | |
| 
 | |
| 		if fpStr == nil || *fpStr == "" {
 | |
| 			return nil
 | |
| 		}
 | |
| 
 | |
| 		fp, err := failpoint.NewFailpoint(cmdKind, *fpStr)
 | |
| 		if err != nil {
 | |
| 			return fmt.Errorf("failed to parse failpoint %s: %w", *fpStr, err)
 | |
| 		}
 | |
| 
 | |
| 		resFn = fp.DelegatedEval()
 | |
| 
 | |
| 		*fpStr = fp.Marshal()
 | |
| 		return nil
 | |
| 
 | |
| 	}); err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	return resFn, nil
 | |
| }
 | |
| 
 | |
| func (c *failpointControl) updateTx(updateFn func(conf *failpointConf) error) error {
 | |
| 	f, err := os.OpenFile(c.confPath, os.O_RDWR, 0666)
 | |
| 	if err != nil {
 | |
| 		return fmt.Errorf("failed to open confPath %s: %w", c.confPath, err)
 | |
| 	}
 | |
| 	defer f.Close()
 | |
| 
 | |
| 	if err := flock(f.Fd()); err != nil {
 | |
| 		return fmt.Errorf("failed to lock failpoint setting %s: %w", c.confPath, err)
 | |
| 	}
 | |
| 	defer unflock(f.Fd())
 | |
| 
 | |
| 	data, err := io.ReadAll(f)
 | |
| 	if err != nil {
 | |
| 		return fmt.Errorf("failed to read failpoint setting %s: %w", c.confPath, err)
 | |
| 	}
 | |
| 
 | |
| 	var conf failpointConf
 | |
| 	if err := json.Unmarshal(data, &conf); err != nil {
 | |
| 		return fmt.Errorf("failed to unmarshal failpoint conf %s: %w", string(data), err)
 | |
| 	}
 | |
| 
 | |
| 	if err := updateFn(&conf); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	data, err = json.Marshal(conf)
 | |
| 	if err != nil {
 | |
| 		return fmt.Errorf("failed to marshal failpoint conf: %w", err)
 | |
| 	}
 | |
| 	return continuity.AtomicWriteFile(c.confPath, data, 0666)
 | |
| }
 | |
| 
 | |
| func nopEvalFn() error {
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func flock(fd uintptr) error {
 | |
| 	return syscall.Flock(int(fd), syscall.LOCK_EX)
 | |
| }
 | |
| 
 | |
| func unflock(fd uintptr) error {
 | |
| 	return syscall.Flock(int(fd), syscall.LOCK_UN)
 | |
| }
 | 
