...

Source file src/github.com/chaos-mesh/chaos-mesh/pkg/bpm/bpm.go

Documentation: github.com/chaos-mesh/chaos-mesh/pkg/bpm

     1  // Copyright 2020 Chaos Mesh Authors.
     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  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package bpm
    15  
    16  import (
    17  	"context"
    18  	"fmt"
    19  	"os"
    20  	"os/exec"
    21  	"sync"
    22  	"syscall"
    23  
    24  	"github.com/shirou/gopsutil/process"
    25  
    26  	ctrl "sigs.k8s.io/controller-runtime"
    27  )
    28  
    29  var log = ctrl.Log.WithName("background-process-manager")
    30  
    31  type NsType string
    32  
    33  const (
    34  	MountNS NsType = "mnt"
    35  	// uts namespace is not supported yet
    36  	// UtsNS   NsType = "uts"
    37  	IpcNS NsType = "ipc"
    38  	NetNS NsType = "net"
    39  	PidNS NsType = "pid"
    40  	// user namespace is not supported yet
    41  	// UserNS  NsType = "user"
    42  )
    43  
    44  var nsArgMap = map[NsType]string{
    45  	MountNS: "m",
    46  	// uts namespace is not supported by nsexec yet
    47  	// UtsNS:   "u",
    48  	IpcNS: "i",
    49  	NetNS: "n",
    50  	PidNS: "p",
    51  	// user namespace is not supported by nsexec yet
    52  	// UserNS:  "U",
    53  }
    54  
    55  const (
    56  	pausePath  = "/usr/local/bin/pause"
    57  	nsexecPath = "/usr/local/bin/nsexec"
    58  
    59  	DefaultProcPrefix = "/proc"
    60  )
    61  
    62  // ProcessPair is an identifier for process
    63  type ProcessPair struct {
    64  	Pid        int
    65  	CreateTime int64
    66  }
    67  
    68  // BackgroundProcessManager manages all background processes
    69  type BackgroundProcessManager struct {
    70  	deathSig    *sync.Map
    71  	identifiers *sync.Map
    72  }
    73  
    74  // NewBackgroundProcessManager creates a background process manager
    75  func NewBackgroundProcessManager() BackgroundProcessManager {
    76  	return BackgroundProcessManager{
    77  		deathSig:    &sync.Map{},
    78  		identifiers: &sync.Map{},
    79  	}
    80  }
    81  
    82  // StartProcess manages a process in manager
    83  func (m *BackgroundProcessManager) StartProcess(cmd *ManagedProcess) error {
    84  	var identifierLock *sync.Mutex
    85  	if cmd.Identifier != nil {
    86  		lock, _ := m.identifiers.LoadOrStore(*cmd.Identifier, &sync.Mutex{})
    87  
    88  		identifierLock = lock.(*sync.Mutex)
    89  
    90  		identifierLock.Lock()
    91  	}
    92  
    93  	err := cmd.Start()
    94  	if err != nil {
    95  		log.Error(err, "fail to start process")
    96  		return err
    97  	}
    98  
    99  	pid := cmd.Process.Pid
   100  	procState, err := process.NewProcess(int32(cmd.Process.Pid))
   101  	if err != nil {
   102  		return err
   103  	}
   104  	ct, err := procState.CreateTime()
   105  	if err != nil {
   106  		return err
   107  	}
   108  
   109  	pair := ProcessPair{
   110  		Pid:        pid,
   111  		CreateTime: ct,
   112  	}
   113  
   114  	channel, _ := m.deathSig.LoadOrStore(pair, make(chan bool, 1))
   115  	deathChannel := channel.(chan bool)
   116  
   117  	log := log.WithValues("pid", pid)
   118  
   119  	go func() {
   120  		err := cmd.Wait()
   121  		if err != nil {
   122  			err, ok := err.(*exec.ExitError)
   123  			if ok {
   124  				status := err.Sys().(syscall.WaitStatus)
   125  				if status.Signaled() && status.Signal() == syscall.SIGTERM {
   126  					log.Info("process stopped with SIGTERM signal")
   127  				}
   128  			} else {
   129  				log.Error(err, "process exited accidentally")
   130  			}
   131  		}
   132  
   133  		log.Info("process stopped")
   134  
   135  		deathChannel <- true
   136  		m.deathSig.Delete(pair)
   137  
   138  		if identifierLock != nil {
   139  			identifierLock.Unlock()
   140  			m.identifiers.Delete(*cmd.Identifier)
   141  		}
   142  	}()
   143  
   144  	return nil
   145  }
   146  
   147  // KillBackgroundProcess sends SIGTERM to process
   148  func (m *BackgroundProcessManager) KillBackgroundProcess(ctx context.Context, pid int, startTime int64) error {
   149  	log := log.WithValues("pid", pid)
   150  
   151  	p, err := os.FindProcess(int(pid))
   152  	if err != nil {
   153  		log.Error(err, "unreachable path. `os.FindProcess` will never return an error on unix")
   154  		return err
   155  	}
   156  
   157  	procState, err := process.NewProcess(int32(pid))
   158  	if err != nil {
   159  		// return successfully as the process has exited
   160  		return nil
   161  	}
   162  	ct, err := procState.CreateTime()
   163  	if err != nil {
   164  		log.Error(err, "fail to read create time")
   165  		// return successfully as the process has exited
   166  		return nil
   167  	}
   168  	if startTime != ct {
   169  		log.Info("process has already been killed", "startTime", ct, "expectedStartTime", startTime)
   170  		// return successfully as the process has exited
   171  		return nil
   172  	}
   173  
   174  	ppid, err := procState.Ppid()
   175  	if err != nil {
   176  		log.Error(err, "fail to read parent id")
   177  		// return successfully as the process has exited
   178  		return nil
   179  	}
   180  	if ppid != int32(os.Getpid()) {
   181  		log.Info("process has already been killed", "ppid", ppid)
   182  		// return successfully as the process has exited
   183  		return nil
   184  	}
   185  
   186  	err = p.Signal(syscall.SIGTERM)
   187  
   188  	if err != nil && err.Error() != "os: process already finished" {
   189  		log.Error(err, "error while killing process")
   190  		return err
   191  	}
   192  
   193  	pair := ProcessPair{
   194  		Pid:        pid,
   195  		CreateTime: ct,
   196  	}
   197  	channel, ok := m.deathSig.Load(pair)
   198  	if ok {
   199  		deathChannel := channel.(chan bool)
   200  		select {
   201  		case <-deathChannel:
   202  		case <-ctx.Done():
   203  			return ctx.Err()
   204  		}
   205  	}
   206  
   207  	log.Info("Successfully killed process")
   208  	return nil
   209  }
   210  
   211  // DefaultProcessBuilder returns the default process builder
   212  func DefaultProcessBuilder(cmd string, args ...string) *ProcessBuilder {
   213  	return &ProcessBuilder{
   214  		cmd:        cmd,
   215  		args:       args,
   216  		nsOptions:  []nsOption{},
   217  		pause:      false,
   218  		identifier: nil,
   219  		ctx:        context.Background(),
   220  	}
   221  }
   222  
   223  // ProcessBuilder builds a exec.Cmd for daemon
   224  type ProcessBuilder struct {
   225  	cmd  string
   226  	args []string
   227  
   228  	nsOptions []nsOption
   229  
   230  	pause    bool
   231  	localMnt bool
   232  
   233  	identifier *string
   234  
   235  	ctx context.Context
   236  }
   237  
   238  // GetNsPath returns corresponding namespace path
   239  func GetNsPath(pid uint32, typ NsType) string {
   240  	return fmt.Sprintf("%s/%d/ns/%s", DefaultProcPrefix, pid, string(typ))
   241  }
   242  
   243  // SetNS sets the namespace of the process
   244  func (b *ProcessBuilder) SetNS(pid uint32, typ NsType) *ProcessBuilder {
   245  	return b.SetNSOpt([]nsOption{{
   246  		Typ:  typ,
   247  		Path: GetNsPath(pid, typ),
   248  	}})
   249  }
   250  
   251  // SetNSOpt sets the namespace of the process
   252  func (b *ProcessBuilder) SetNSOpt(options []nsOption) *ProcessBuilder {
   253  	b.nsOptions = append(b.nsOptions, options...)
   254  
   255  	return b
   256  }
   257  
   258  // SetIdentifier sets the identifier of the process
   259  func (b *ProcessBuilder) SetIdentifier(id string) *ProcessBuilder {
   260  	b.identifier = &id
   261  
   262  	return b
   263  }
   264  
   265  // EnablePause enables pause for process
   266  func (b *ProcessBuilder) EnablePause() *ProcessBuilder {
   267  	b.pause = true
   268  
   269  	return b
   270  }
   271  
   272  func (b *ProcessBuilder) EnableLocalMnt() *ProcessBuilder {
   273  	b.localMnt = true
   274  
   275  	return b
   276  }
   277  
   278  // SetContext sets context for process
   279  func (b *ProcessBuilder) SetContext(ctx context.Context) *ProcessBuilder {
   280  	b.ctx = ctx
   281  
   282  	return b
   283  }
   284  
   285  type nsOption struct {
   286  	Typ  NsType
   287  	Path string
   288  }
   289  
   290  // ManagedProcess is a process which can be managed by backgroundProcessManager
   291  type ManagedProcess struct {
   292  	*exec.Cmd
   293  
   294  	// If the identifier is not nil, process manager should make sure no other
   295  	// process with this identifier is running when executing this command
   296  	Identifier *string
   297  }
   298