Repository: threefoldtech/zos
Pull request #1378 (2021-09-13 09:29:35)

Update 0-fs and use new 0-fs features. no pid tracking is needed
This will allow:
- ability to clean up stall mounts
- less tracking files


Patch:
diff --git a/cmds/identityd/main.go b/cmds/identityd/main.go
index 08efeb6f..dc9359ac 100644
--- a/cmds/identityd/main.go
+++ b/cmds/identityd/main.go
@@ -140,12 +140,6 @@ func main() {
 	// register the cancel function with defer if the process stops because of a update
 	defer cancel()
 
-	go func() {
-		if err := server.Run(ctx); err != nil && err != context.Canceled {
-			log.Error().Err(err).Msg("unexpected error")
-		}
-	}()
-
 	upgrader, err := upgrade.NewUpgrader(root, upgrade.NoSelfUpgrade(debug))
 	if err != nil {
 		log.Fatal().Err(err).Msg("failed to initialize upgrader")
@@ -153,6 +147,12 @@ func main() {
 
 	installBinaries(&boot, upgrader)
 
+	go func() {
+		if err := server.Run(ctx); err != nil && err != context.Canceled {
+			log.Error().Err(err).Msg("unexpected error")
+		}
+	}()
+
 	utils.OnDone(ctx, func(_ error) {
 		log.Info().Msg("received a termination signal")
 	})
diff --git a/etc/zinit/flistd.yaml b/etc/zinit/flistd.yaml
index fbec3b10..34f15ff9 100644
--- a/etc/zinit/flistd.yaml
+++ b/etc/zinit/flistd.yaml
@@ -1,3 +1,5 @@
 exec: flistd --broker unix://var/run/redis.sock --root /var/cache/modules/flistd
 after:
   - boot
+  # identityd is added to make sure all binaries are up to date
+  - identityd
diff --git a/etc/zinit/identityd.yaml b/etc/zinit/identityd.yaml
index d3ef153b..140c1d1d 100644
--- a/etc/zinit/identityd.yaml
+++ b/etc/zinit/identityd.yaml
@@ -1,3 +1,4 @@
 exec: identityd
+test: zbusdebug --module identityd
 after:
   - boot
diff --git a/pkg/container/container.go b/pkg/container/container.go
index 37f7668c..ebcc4f3c 100644
--- a/pkg/container/container.go
+++ b/pkg/container/container.go
@@ -278,9 +278,8 @@ func (c *Module) Run(ns string, data pkg.Container) (id pkg.ContainerID, err err
 			}
 
 			go func() {
-				if err := stats.Monitor(c.containerd, ns, data.Name, s); err != nil {
-					log.Error().Err(err).Str("name", data.Name).Msg("container monitor exited with error")
-				}
+				err := stats.Monitor(c.containerd, ns, data.Name, s)
+				log.Error().Err(err).Str("name", data.Name).Msg("container monitor exited with error")
 			}()
 
 		default:
diff --git a/pkg/flist/cleanup.go b/pkg/flist/cleanup.go
index db5ae31b..6beac58d 100644
--- a/pkg/flist/cleanup.go
+++ b/pkg/flist/cleanup.go
@@ -2,14 +2,11 @@ package flist
 
 import (
 	"context"
-	"fmt"
-	"io/ioutil"
 	"os"
 	"path/filepath"
 	"syscall"
 	"time"
 
-	"github.com/pkg/errors"
 	"github.com/rs/zerolog/log"
 	"github.com/threefoldtech/zos/pkg/app"
 )
@@ -19,135 +16,11 @@ import (
 type Cleaner interface {
 	// MountsCleaner runs the clean process, MountsCleaner should be
 	// blocking. Caller then can do `go MountsCleaner()` to run it in the background
-	MountsCleaner(ctx context.Context, every time.Duration)
 	CacheCleaner(ctx context.Context, every time.Duration, age time.Duration)
 }
 
 var _ Cleaner = (*flistModule)(nil)
 
-func (f *flistModule) listMounts() (map[string]int64, error) {
-	infos, err := ioutil.ReadDir(f.run)
-	if err != nil {
-		return nil, errors.Wrapf(err, "failed to list g8ufs process pids: %s", f.pid)
-	}
-
-	data := make(map[string]int64)
-	for _, info := range infos {
-		if info.IsDir() {
-			// what is that doing here?
-			continue
-		}
-
-		path := filepath.Join(f.run, info.Name())
-		pid, err := f.getPid(path)
-		if os.IsNotExist(err) {
-			pid = -1
-		} else if err != nil {
-			log.Error().Err(err).Str("pid-file", path).Msg("invalid pid file")
-		}
-
-		data[info.Name()] = pid
-	}
-
-	return data, nil
-}
-
-// cleanupMount forces clean up of a mount point
-func (f *flistModule) cleanupMount(ctx context.Context, name string) error {
-	path, err := f.mountpath(name)
-	if err != nil {
-		return err
-	}
-
-	// always clean up files
-	defer func() {
-		for _, file := range []string{
-			filepath.Join(f.pid, fmt.Sprintf("%s.pid", name)),
-			filepath.Join(f.run, name),
-			filepath.Join(f.log, fmt.Sprintf("%s.log", name)),
-		} {
-			if err := os.Remove(file); err != nil {
-				log.Warn().Err(err).Str("file", file).Msg("failed to delete pid file")
-			}
-		}
-	}()
-	// the following procedure will ignore errors
-	// because the only point of this is to force
-	// clean the entire mount.
-	// so no checks or error checking is done
-
-	if err := syscall.Unmount(path, syscall.MNT_DETACH); err != nil {
-		log.Warn().Err(err).Str("path", path).Msg("fail to unmount flist")
-	}
-
-	fs, err := f.storage.VolumeLookup(ctx, name)
-	if err != nil {
-		log.Warn().Err(err).Str("subvolume", name).Msg("subvolume does not exist")
-		return nil
-	}
-
-	ro := filepath.Join(fs.Path, "ro")
-	if err := syscall.Unmount(ro, syscall.MNT_DETACH); err != nil {
-		log.Warn().Err(err).Str("path", ro).Msg("fail to unmount ro layer")
-	}
-
-	return nil
-}
-
-func (f *flistModule) cleanupAll(ctx context.Context) error {
-	mounts, err := f.listMounts()
-	if err != nil {
-		return errors.Wrap(err, "failed to list current possible mounts")
-	}
-
-	for name, pid := range mounts {
-		switch pid {
-		case -1:
-			// process has shutdown gracefully
-			log.Debug().Str("name", name).Int64("pid", pid).Msg("attempt to clean up mount")
-			if err := f.cleanupMount(ctx, name); err != nil {
-				log.Error().Err(err).Str("name", name).Msg("failed to clean up mounts")
-			}
-		case 0:
-			// the file exists, but we can't read the file content
-			// for some reason!
-			// TODO: ?
-		default:
-			// valid PID.
-			_, err := f.getMountOptionsForPID(pid)
-			if err != nil {
-				// this is only possible if process does not exist.
-				// hence we need to clean up.
-				log.Debug().Str("name", name).Int64("pid", pid).Msg("attempt to clean up mount")
-				if err := f.cleanupMount(ctx, name); err != nil {
-					log.Error().Err(err).Str("name", name).Msg("failed to clean up mounts")
-				}
-			}
-			// nothing to do
-		}
-	}
-
-	return nil
-}
-
-// Cleaner runs forever, checks the tracker files for filesystem processes
-// that requires cleanup
-func (f *flistModule) MountsCleaner(ctx context.Context, every time.Duration) {
-	log := app.SampledLogger()
-
-	for {
-		select {
-		case <-ctx.Done():
-			return
-		case <-time.After(every):
-			log.Debug().Msg("running cleaner job")
-			if err := f.cleanupAll(ctx); err != nil {
-				log.Error().Err(err).Msg("failed to cleanup stall mounts")
-			}
-		}
-	}
-}
-
 func (f *flistModule) CacheCleaner(ctx context.Context, every time.Duration, age time.Duration) {
 	log := app.SampledLogger()
 
diff --git a/pkg/flist/flist.go b/pkg/flist/flist.go
index 58f466a8..7dc143b4 100644
--- a/pkg/flist/flist.go
+++ b/pkg/flist/flist.go
@@ -1,7 +1,6 @@
 package flist
 
 import (
-	"bufio"
 	"bytes"
 	"context"
 	"crypto/md5"
@@ -13,7 +12,6 @@ import (
 	"os/exec"
 	"path"
 	"path/filepath"
-	"strconv"
 	"strings"
 	"syscall"
 	"time"
@@ -28,9 +26,17 @@ import (
 
 const (
 	defaultRoot = "/var/cache/modules/flist"
+	mib         = 1024 * 1024
 )
 
-const mib = 1024 * 1024
+var (
+	// ErrAlreadyMounted is returned when checking if a path has already
+	// something mounted on it
+	ErrAlreadyMounted                  = errors.New("path is already mounted")
+	ErrNotMountPoint                   = errors.New("path is not a mountpoint")
+	ErrTransportEndpointIsNotConencted = errors.New("transport endpoint is not connected")
+	ErrZFSProcessNotFound              = errors.New("0-fs process not found")
+)
 
 type commander interface {
 	Command(name string, arg ...string) *exec.Cmd
@@ -89,14 +95,13 @@ type flistModule struct {
 	ro         string
 	pid        string
 	log        string
-	run        string
 
 	storage   volumeAllocator
 	commander commander
 	system    system
 }
 
-func newFlister(root string, storage volumeAllocator, commander commander, system system) pkg.Flister {
+func newFlister(root string, storage volumeAllocator, commander commander, system system) *flistModule {
 	if root == "" {
 		root = defaultRoot
 	}
@@ -111,7 +116,7 @@ func newFlister(root string, storage volumeAllocator, commander commander, syste
 	}
 
 	// prepare directory layout for the module
-	for _, path := range []string{"flist", "cache", "mountpoint", "ro", "pid", "log", "run"} {
+	for _, path := range []string{"flist", "cache", "mountpoint", "ro", "pid", "log"} {
 		p := filepath.Join(root, path)
 		if err := os.MkdirAll(p, 0755); err != nil {
 			panic(err)
@@ -130,7 +135,6 @@ func newFlister(root string, storage volumeAllocator, commander commander, syste
 		ro:         filepath.Join(root, "ro"),
 		pid:        filepath.Join(root, "pid"),
 		log:        filepath.Join(root, "log"),
-		run:        filepath.Join(root, "run"),
 
 		storage:   storage,
 		commander: commander,
@@ -200,7 +204,6 @@ func (f *flistModule) mountRO(url, storage string) (string, error) {
 		return "", err
 	}
 
-	pidPath := filepath.Join(f.pid, hash) + ".pid"
 	logPath := filepath.Join(f.log, hash) + ".log"
 	var args []string
 
@@ -209,7 +212,6 @@ func (f *flistModule) mountRO(url, storage string) (string, error) {
 		"-meta", flistPath,
 		"-storage-url", storage,
 		"-daemon",
-		"-pid", pidPath,
 		"-log", logPath,
 		// this is always read-only
 		"-ro",
@@ -225,35 +227,6 @@ func (f *flistModule) mountRO(url, storage string) (string, error) {
 		return "", err
 	}
 
-	// wait for the daemon to be ready
-	// we check the pid file is created
-	if err := waitPidFile(time.Second*5, pidPath, true); err != nil {
-		sublog.Error().Err(err).Msg("pid file of 0-fs daemon not created")
-		return "", err
-	}
-
-	// and scan the logs after "mount ready"
-	if err := waitMountedLog(time.Second*5, logPath); err != nil {
-		sublog.Error().Err(err).Msg("0-fs daemon did not start properly")
-		return "", err
-	}
-
-	syscall.Sync()
-
-	// the track file is a symlink to the process pid
-	// if the link is broken, then the fs has exited gracefully
-	// otherwise we can get the fs pid from the track path
-	// if the pid does not exist of the target does not exist
-	// we can clean up the named mount
-	trackPath := filepath.Join(f.run, hash)
-	if err = os.Remove(trackPath); err != nil && !os.IsNotExist(err) {
-		return "", errors.Wrap(err, "failed to clean up pid file")
-	}
-
-	if err = os.Symlink(pidPath, trackPath); err != nil {
-		sublog.Error().Err(err).Msg("failed track fs pid")
-	}
-
 	syscall.Sync()
 
 	return mountpoint, nil
@@ -423,11 +396,6 @@ func (f *flistModule) flistMountpath(hash string) (string, error) {
 	return mountpath, nil
 }
 
-// ErrAlreadyMounted is returned when checking if a path has already
-// something mounted on it
-var ErrAlreadyMounted = errors.New("path is already mounted")
-var ErrTransportEndpointIsNotConencted = errors.New("transport endpoint is not connected")
-
 // valid checks that this mount path is free, and can be used
 func (f *flistModule) valid(path string) error {
 	stat, err := os.Stat(path)
@@ -460,38 +428,16 @@ func (f *flistModule) waitMountpoint(path string, seconds int) error {
 
 	return fmt.Errorf("was not mounted in time")
 }
-
 func (f *flistModule) isMountpoint(path string) error {
 	log.Debug().Str("mnt", path).Msg("testing mountpoint")
 	return f.commander.Command("mountpoint", path).Run()
 }
 
-func (f *flistModule) getPid(pidPath string) (int64, error) {
-	pid, err := ioutil.ReadFile(pidPath)
-	if err != nil {
-		return 0, err
-	}
-
-	value, err := strconv.ParseInt(string(pid), 10, 64)
-	if err != nil {
-		return 0, errors.Wrap(err, "invalid pid value in file, expected int")
-	}
-
-	return value, nil
-}
-
-func (f *flistModule) getMountOptions(pidPath string) (options, error) {
-	pid, err := f.getPid(pidPath)
-	if err != nil {
-		return nil, errors.Wrapf(err, "failed to get pid from file: %s", pidPath)
-	}
-
-	return f.getMountOptionsForPID(pid)
-}
-
 func (f *flistModule) getMountOptionsForPID(pid int64) (options, error) {
 	cmdline, err := ioutil.ReadFile(path.Join("/proc", fmt.Sprint(pid), "cmdline"))
-	if err != nil {
+	if os.IsNotExist(err) {
+		return nil, ErrZFSProcessNotFound
+	} else if err != nil {
 		return nil, errors.Wrapf(err, "failed to read mount (%d) cmdline", pid)
 	}
 
@@ -506,10 +452,18 @@ func (f *flistModule) getMountOptionsForPID(pid int64) (options, error) {
 }
 
 func (f *flistModule) HashFromRootPath(name string) (string, error) {
-	base := filepath.Base(name)
-	pidPath := filepath.Join(f.pid, base) + ".pid"
+	path, err := f.mountpath(filepath.Base(name))
+	if err != nil {
+		return "", err
+	}
+
+	info, err := f.resolve(path)
+	if err != nil {
+		return "", err
+	}
 
-	opts, err := f.getMountOptions(pidPath)
+	// this can either be an overlay mount.
+	opts, err := f.getMountOptionsForPID(info.Pid)
 	if err != nil {
 		return "", err
 	}
@@ -665,108 +619,4 @@ func md5Compare(hash string, r io.Reader) (bool, error) {
 	return strings.Compare(fmt.Sprintf("%x", h.Sum(nil)), hash) == 0, nil
 }
 
-// waitPidFile wait for a file pointed by path to be created or deleted
-// for at most timeout duration
-// is exists is true, it waits for the file to exists
-// else it waits for the file to be deleted
-func waitPidFile(timeout time.Duration, path string, exists bool) error {
-	const delay = time.Millisecond * 100
-
-	ctx, cancel := context.WithTimeout(context.Background(), timeout)
-	defer cancel()
-
-	for {
-		select {
-		case <-ctx.Done():
-			return ctx.Err()
-		default:
-			_, err := os.Stat(path)
-			// check exist but the file is not there yet,
-			// or check NOT exist but it is still there
-			// we try again
-			if (exists && os.IsNotExist(err)) || (!exists && err == nil) {
-				time.Sleep(delay)
-			} else if err != nil && !os.IsNotExist(err) {
-				//another error that is NOT IsNotExist.
-				return err
-			} else {
-				return nil
-			}
-		}
-	}
-}
-
-func waitMountedLog(timeout time.Duration, logfile string) error {
-	const target = "mount ready"
-	const delay = time.Millisecond * 500
-
-	f, err := os.Open(logfile)
-	if err != nil {
-		return err
-	}
-	defer f.Close()
-	br := bufio.NewReader(f)
-
-	ctx, cancel := context.WithTimeout(context.Background(), timeout)
-	defer cancel()
-
-	// this goroutine looks for "mount ready"
-	// in the logs of the 0-fs
-	cErr := make(chan error)
-	go func(ctx context.Context, r io.Reader, cErr chan<- error) {
-		for {
-			select {
-			case <-ctx.Done():
-				// ensure we don't leak the goroutine
-				cErr <- ctx.Err()
-			default:
-				line, err := br.ReadString('\n')
-				if err != nil {
-					time.Sleep(delay)
-					continue
-				}
-
-				if !strings.Contains(line, target) {
-					time.Sleep(delay)
-					continue
-				}
-				// found
-				cErr <- nil
-				return
-			}
-		}
-	}(ctx, br, cErr)
-
-	return <-cErr
-}
-
-func forceStop(pid int) error {
-	slog := log.With().Int("pid", pid).Logger()
-	slog.Info().Msg("trying to force stop by killing the process")
-
-	p, err := os.FindProcess(int(pid))
-	if err != nil {
-		return err
-	}
-
-	if err := p.Signal(syscall.SIGTERM); err != nil {
-		slog.Error().Err(err).Msg("failed to send SIGTERM to process")
-	}
-
-	time.Sleep(time.Second)
-	if pidExist(p) {
-		slog.Info().Msgf("process didn't stop gracefully, lets kill it")
-		if err := p.Signal(syscall.SIGKILL); err != nil {
-			slog.Error().Err(err).Msg("failed to send SIGKILL to process")
-		}
-	}
-
-	return nil
-}
-
-func pidExist(p *os.Process) bool {
-	// https://github.com/golang/go/issues/14146#issuecomment-176888204
-	return p.Signal(syscall.Signal(0)) == nil
-}
-
 var _ pkg.Flister = (*flistModule)(nil)
diff --git a/pkg/flist/mounts.go b/pkg/flist/mounts.go
new file mode 100644
index 00000000..e326bf8a
--- /dev/null
+++ b/pkg/flist/mounts.go
@@ -0,0 +1,145 @@
+package flist
+
+import (
+	"encoding/json"
+	"fmt"
+	"os/exec"
+	"strconv"
+	"strings"
+
+	"github.com/pkg/errors"
+)
+
+const (
+	fsTypeG8ufs   = "fuse.g8ufs"
+	fsTypeOverlay = "overlay"
+)
+
+// //nolint
+// type filter func(i *mountInfo) bool
+
+// //nolint
+// func withParentDir(path string) filter {
+// 	path = filepath.Clean(path)
+// 	return func(mnt *mountInfo) bool {
+// 		base := filepath.Base(mnt.Target)
+// 		return path == base
+// 	}
+// }
+
+type mountInfo struct {
+	Target  string `json:"target"`
+	Source  string `json:"source"`
+	FSType  string `json:"fstype"`
+	Options string `json:"options"`
+}
+
+type overlayInfo struct {
+	LowerDir string
+	UpperDir string
+	WorkDir  string
+}
+
+type g8ufsInfo struct {
+	Pid int64
+}
+
+func (m *mountInfo) AsOverlay() overlayInfo {
+	var info overlayInfo
+	for _, part := range strings.Split(m.Options, ",") {
+		kv := strings.SplitN(part, "=", 2)
+		if len(kv) != 2 {
+			continue
+		}
+		switch kv[0] {
+		case "lowerdir":
+			info.LowerDir = kv[1]
+		case "upperdir":
+			info.UpperDir = kv[1]
+		case "workdir":
+			info.WorkDir = kv[1]
+		}
+	}
+
+	return info
+}
+
+func (m *mountInfo) AsG8ufs() g8ufsInfo {
+	var info g8ufsInfo
+	if pid, err := strconv.ParseInt(m.Source, 10, 64); err == nil {
+		info.Pid = pid
+	}
+	return info
+}
+
+func (f *flistModule) getMount(path string) (info mountInfo, err error) {
+	output, err := f.commander.Command("findmnt", "-J", path).Output()
+	if err, ok := err.(*exec.ExitError); ok && err != nil {
+		if err.ExitCode() == 1 {
+			return info, ErrNotMountPoint
+		}
+	}
+
+	var result struct {
+		Filesystems []mountInfo `json:"filesystems"`
+	}
+
+	if err := json.Unmarshal(output, &result); err != nil {
+		return info, errors.Wrap(err, "failed to parse findmnt output")
+	}
+
+	if len(result.Filesystems) != 1 {
+		return info, fmt.Errorf("invalid number of mounts in output")
+	}
+
+	return result.Filesystems[0], nil
+}
+
+func (f *flistModule) resolve(path string) (g8ufsInfo, error) {
+	info, err := f.getMount(path)
+	if err != nil {
+		return g8ufsInfo{}, err
+	}
+
+	if info.FSType == fsTypeG8ufs {
+		return info.AsG8ufs(), nil
+	} else if info.FSType == fsTypeOverlay {
+		overlay := info.AsOverlay()
+		return f.resolve(overlay.LowerDir)
+	} else {
+		return g8ufsInfo{}, fmt.Errorf("invalid mount fs type: '%s'", info.FSType)
+	}
+}
+
+// //nolint
+// func (f *flistModule) mounts(filter ...filter) ([]mountInfo, error) {
+// 	output, err := f.commander.Command("findmnt", "-J", "-l").Output()
+// 	if err != nil {
+// 		return nil, errors.Wrap(err, "failed to list system mounts")
+// 	}
+
+// 	var result struct {
+// 		Filesystems []mountInfo `json:"filesystems"`
+// 	}
+
+// 	if err := json.Unmarshal(output, &result); err != nil {
+// 		return nil, errors.Wrap(err, "failed to parse findmnt output")
+// 	}
+
+// 	if len(filter) == 0 {
+// 		return result.Filesystems, nil
+// 	}
+
+// 	mounts := result.Filesystems[:0]
+// next:
+// 	for _, mnt := range result.Filesystems {
+// 		for _, f := range filter {
+// 			if !f(&mnt) {
+// 				continue next
+// 			}
+// 		}
+// 		mounts = append(mounts, mnt)
+// 	}
+
+// 	return mounts, nil
+// }
diff --git a/qemu/overlay/etc/zinit/identityd.yaml b/qemu/overlay/etc/zinit/identityd.yaml
index cca32db2..f4c6afab 100644
--- a/qemu/overlay/etc/zinit/identityd.yaml
+++ b/qemu/overlay/etc/zinit/identityd.yaml
@@ -1,4 +1,4 @@
 exec: identityd -d
+test: zbusdebug --module identityd
 after:
   - boot
-  - flistd


Test patch:
diff --git a/pkg/flist/flist_test.go b/pkg/flist/flist_test.go
index 713f65b1..09a704ea 100644
--- a/pkg/flist/flist_test.go
+++ b/pkg/flist/flist_test.go
@@ -11,7 +11,6 @@ import (
 	"syscall"
 	"testing"
 	"text/template"
-	"time"
 
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/mock"
@@ -22,7 +21,6 @@ import (
 
 var (
 	templ = template.Must(template.New("script").Parse(`
-echo $PPID > {{.pid}}
 echo "mount ready" > {{.log}}
 `))
 )
@@ -96,7 +94,6 @@ func (t *testCommander) Command(name string, args ...string) *exec.Cmd {
 	m, _ := t.args(args...)
 	var script bytes.Buffer
 	err := templ.Execute(&script, map[string]string{
-		"pid": m["pid"],
 		"log": m["log"],
 	})
 
@@ -122,11 +119,10 @@ func (t *testSystem) Unmount(target string, flags int) error {
 func TestCommander(t *testing.T) {
 	cmder := testCommander{T: t}
 
-	m, r := cmder.args("-pid", "pid-file", "-log", "log-file", "remaining")
+	m, r := cmder.args("-log", "log-file", "remaining")
 
 	require.Equal(t, []string{"remaining"}, r)
 	require.Equal(t, map[string]string{
-		"pid": "pid-file",
 		"log": "log-file",
 	}, m)
 }
@@ -262,10 +258,8 @@ func TestDownloadFlist(t *testing.T) {
 
 	sys := &testSystem{}
 
-	x := newFlister(root, strg, cmder, sys)
+	f := newFlister(root, strg, cmder, sys)
 
-	f, ok := x.(*flistModule)
-	require.True(ok)
 	path1, err := f.downloadFlist("https://hub.grid.tf/thabet/redis.flist")
 	require.NoError(err)
 
@@ -294,70 +288,3 @@ func TestDownloadFlist(t *testing.T) {
 	require.NoError(err)
 	assert.NotEqual(info2.ModTime(), info3.ModTime())
 }
-
-func TestWaitPIDFileExists(t *testing.T) {
-	require := require.New(t)
-	const testFile = "/tmp/wait.exists.test"
-	os.Remove(testFile)
-
-	out := make(chan error)
-	go func(out chan<- error) {
-		out <- waitPidFile(2*time.Second, testFile, true)
-	}(out)
-
-	f, err := os.Create(testFile)
-	require.NoError(err)
-	defer f.Close()
-	err = <-out
-	require.NoError(err)
-}
-
-func TestWaitPIDFileDeleted(t *testing.T) {
-	require := require.New(t)
-	const testFile = "/tmp/wait.deleted.test"
-	f, err := os.Create(testFile)
-	require.NoError(err)
-	defer f.Close()
-
-	out := make(chan error)
-	go func(out chan<- error) {
-		out <- waitPidFile(2*time.Second, testFile, false)
-	}(out)
-
-	err = os.Remove(testFile)
-	require.NoError(err)
-	err = <-out
-	require.NoError(err)
-}
-
-func TestWaitPIDFileTimeout(t *testing.T) {
-	require := require.New(t)
-	const testFile = "/tmp/wait.deleted.test"
-	f, err := os.Create(testFile)
-	require.NoError(err)
-	defer f.Close()
-
-	out := make(chan error)
-	go func(out chan<- error) {
-		out <- waitPidFile(1*time.Second, testFile, false)
-	}(out)
-
-	err = <-out
-	require.Equal(context.DeadlineExceeded, err)
-}
-
-func Test_forceStop(t *testing.T) {
-	ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
-	defer cancel()
-
-	cmd := exec.CommandContext(ctx, "sleep", "50")
-	err := cmd.Start()
-	require.NoError(t, err)
-
-	go func() {
-		_ = cmd.Wait()
-	}()
-
-	err = forceStop(cmd.Process.Pid)
-	assert.NoError(t, err)
-}