diff options
author | John Cai <jcai@gitlab.com> | 2022-07-26 22:02:59 +0300 |
---|---|---|
committer | John Cai <jcai@gitlab.com> | 2022-08-22 15:56:27 +0300 |
commit | cdb5961d14bd60a5d57ba43dedb940b99b51d1d1 (patch) | |
tree | d29aaa655c75ec78259eccc717e1c8c06235ed6b | |
parent | b9e607f9bf82defc2b57dacdbc31a1faa2257270 (diff) |
config: Generisize functions that create and prune temp directories
Pruning directories is a common operation. Currently it's used for
pruning Gitaly runtime directories. However, Cgroup directories are made
according to the same convention as Gitaly runtime directories in the
form of gitaly-<pid>. Instead of reproducing the logic, simply rename
the function so it can be reused.
To do this, we also need to encapsulate the logic that constructs the
name of a process directory and the logic that parses out the pid from
such a directory into functions.
-rw-r--r-- | cmd/gitaly/main.go | 2 | ||||
-rw-r--r-- | internal/gitaly/config/config.go | 69 | ||||
-rw-r--r-- | internal/gitaly/config/config_test.go | 19 | ||||
-rw-r--r-- | internal/gitaly/config/temp_dir.go | 88 |
4 files changed, 101 insertions, 77 deletions
diff --git a/cmd/gitaly/main.go b/cmd/gitaly/main.go index 765d729ca..636476901 100644 --- a/cmd/gitaly/main.go +++ b/cmd/gitaly/main.go @@ -149,7 +149,7 @@ func run(cfg config.Cfg) error { defer cancel() if cfg.RuntimeDir != "" { - if err := config.PruneRuntimeDirectories(log.StandardLogger(), cfg.RuntimeDir); err != nil { + if err := config.PruneOldGitalyProcessDirectories(log.StandardLogger(), cfg.RuntimeDir); err != nil { return fmt.Errorf("prune runtime directories: %w", err) } } diff --git a/internal/gitaly/config/config.go b/internal/gitaly/config/config.go index 43fae6f40..4b816b752 100644 --- a/internal/gitaly/config/config.go +++ b/internal/gitaly/config/config.go @@ -8,7 +8,6 @@ import ( "os" "path/filepath" "reflect" - "strconv" "strings" "syscall" "time" @@ -543,72 +542,6 @@ func (cfg *Cfg) configurePackObjectsCache() error { return nil } -// PruneRuntimeDirectories removes leftover runtime directories that belonged to processes that -// no longer exist. The removals are logged prior to being executed. Unexpected directory entries -// are logged but not removed -func PruneRuntimeDirectories(log log.FieldLogger, runtimeDir string) error { - entries, err := os.ReadDir(runtimeDir) - if err != nil { - return fmt.Errorf("list runtime directory: %w", err) - } - - for _, entry := range entries { - if err := func() error { - log := log.WithField("path", filepath.Join(runtimeDir, entry.Name())) - if !entry.IsDir() { - // There should be no files, only the runtime directories. - log.Error("runtime directory contains an unexpected file") - return nil - } - - components := strings.Split(entry.Name(), "-") - if len(components) != 2 || components[0] != "gitaly" { - // This directory does not match the runtime directory naming format - // of `gitaly-<process id>. - log.Error("runtime directory contains an unexpected directory") - return nil - } - - processID, err := strconv.ParseInt(components[1], 10, 64) - if err != nil { - // This is not a runtime directory as the section after the hyphen is not a process id. - log.Error("runtime directory contains an unexpected directory") - return nil - } - - process, err := os.FindProcess(int(processID)) - if err != nil { - return fmt.Errorf("find process: %w", err) - } - defer func() { - if err := process.Release(); err != nil { - log.WithError(err).Error("failed releasing process") - } - }() - - if err := process.Signal(syscall.Signal(0)); err != nil { - // Either the process does not exist, or the pid has been re-used by for a - // process owned by another user and is not a Gitaly process. - if !errors.Is(err, os.ErrProcessDone) && !errors.Is(err, syscall.EPERM) { - return fmt.Errorf("signal: %w", err) - } - - log.Info("removing leftover runtime directory") - - if err := os.RemoveAll(filepath.Join(runtimeDir, entry.Name())); err != nil { - return fmt.Errorf("remove leftover runtime directory: %w", err) - } - } - - return nil - }(); err != nil { - return err - } - } - - return nil -} - // SetupRuntimeDirectory creates a new runtime directory. Runtime directory contains internal // runtime data generated by Gitaly such as the internal sockets. If cfg.RuntimeDir is set, // it's used as the parent directory for the runtime directory. Runtime directory owner process @@ -638,7 +571,7 @@ func SetupRuntimeDirectory(cfg Cfg, processID int) (string, error) { // PID exists. Furthermore, it allows easier debugging in case one wants to inspect // the runtime directory of a running Gitaly node. - runtimeDir = filepath.Join(cfg.RuntimeDir, fmt.Sprintf("gitaly-%d", processID)) + runtimeDir = GetGitalyProcessTempDir(cfg.RuntimeDir, processID) if _, err := os.Stat(runtimeDir); err != nil && !os.IsNotExist(err) { return "", fmt.Errorf("statting runtime directory: %w", err) diff --git a/internal/gitaly/config/config_test.go b/internal/gitaly/config/config_test.go index fb93676f8..cc46193f5 100644 --- a/internal/gitaly/config/config_test.go +++ b/internal/gitaly/config/config_test.go @@ -1304,15 +1304,18 @@ func TestSetupRuntimeDirectory(t *testing.T) { func TestPruneRuntimeDirectories(t *testing.T) { t.Run("no runtime directories", func(t *testing.T) { - require.NoError(t, PruneRuntimeDirectories(testhelper.NewDiscardingLogEntry(t), testhelper.TempDir(t))) + require.NoError(t, PruneOldGitalyProcessDirectories(testhelper.NewDiscardingLogEntry(t), testhelper.TempDir(t))) }) t.Run("unset runtime directory", func(t *testing.T) { - require.EqualError(t, PruneRuntimeDirectories(testhelper.NewDiscardingLogEntry(t), ""), "list runtime directory: open : no such file or directory") + require.EqualError(t, + PruneOldGitalyProcessDirectories(testhelper.NewDiscardingLogEntry(t), ""), "list gitaly process directory: open : no such file or directory") }) t.Run("non-existent runtime directory", func(t *testing.T) { - require.EqualError(t, PruneRuntimeDirectories(testhelper.NewDiscardingLogEntry(t), "/path/does/not/exist"), "list runtime directory: open /path/does/not/exist: no such file or directory") + require.EqualError(t, + PruneOldGitalyProcessDirectories(testhelper.NewDiscardingLogEntry(t), + "/path/does/not/exist"), "list gitaly process directory: open /path/does/not/exist: no such file or directory") }) t.Run("invalid, stale and active runtime directories", func(t *testing.T) { @@ -1336,19 +1339,19 @@ func TestPruneRuntimeDirectories(t *testing.T) { require.NoError(t, err) prunableDirs = append(prunableDirs, staleRuntimeDir) - expectedLogs[staleRuntimeDir] = "removing leftover runtime directory" + expectedLogs[staleRuntimeDir] = "removing leftover gitaly process directory" } // Setup runtime directory with pid of process not owned by git user rootRuntimeDir, err := SetupRuntimeDirectory(cfg, 1) require.NoError(t, err) - expectedLogs[rootRuntimeDir] = "removing leftover runtime directory" + expectedLogs[rootRuntimeDir] = "removing leftover gitaly process directory" prunableDirs = append(prunableDirs, rootRuntimeDir) // Create an unexpected file in the runtime directory unexpectedFilePath := filepath.Join(baseDir, "unexpected-file") require.NoError(t, os.WriteFile(unexpectedFilePath, []byte(""), os.ModePerm)) - expectedLogs[unexpectedFilePath] = "runtime directory contains an unexpected file" + expectedLogs[unexpectedFilePath] = "gitaly process directory contains an unexpected file" nonPrunableDirs := []string{ownRuntimeDir} @@ -1361,12 +1364,12 @@ func TestPruneRuntimeDirectories(t *testing.T) { } { dirPath := filepath.Join(baseDir, dirName) require.NoError(t, os.Mkdir(dirPath, os.ModePerm)) - expectedLogs[dirPath] = "runtime directory contains an unexpected directory" + expectedLogs[dirPath] = "gitaly process directory contains an unexpected directory" nonPrunableDirs = append(nonPrunableDirs, dirPath) } logger, hook := test.NewNullLogger() - require.NoError(t, PruneRuntimeDirectories(logger, cfg.RuntimeDir)) + require.NoError(t, PruneOldGitalyProcessDirectories(logger, cfg.RuntimeDir)) actualLogs := map[string]string{} for _, entry := range hook.Entries { diff --git a/internal/gitaly/config/temp_dir.go b/internal/gitaly/config/temp_dir.go new file mode 100644 index 000000000..52ba93dd1 --- /dev/null +++ b/internal/gitaly/config/temp_dir.go @@ -0,0 +1,88 @@ +package config + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "strconv" + "strings" + "syscall" + + log "github.com/sirupsen/logrus" +) + +// PruneOldGitalyProcessDirectories removes leftover temporary directories that belonged to processes that +// no longer exist. Directories are expected to be in the form gitaly-<pid>. +// The removals are logged prior to being executed. Unexpected directory entries are logged +// but not removed. +func PruneOldGitalyProcessDirectories(log log.FieldLogger, directory string) error { + entries, err := os.ReadDir(directory) + if err != nil { + return fmt.Errorf("list gitaly process directory: %w", err) + } + + for _, entry := range entries { + if err := func() error { + log := log.WithField("path", filepath.Join(directory, entry.Name())) + if !entry.IsDir() { + // There should be no files, only the gitaly process directories. + log.Error("gitaly process directory contains an unexpected file") + return nil + } + + components := strings.Split(entry.Name(), "-") + if len(components) != 2 || components[0] != "gitaly" { + // This directory does not match the gitaly process directory naming format + // of `gitaly-<process id>. + log.Error("gitaly process directory contains an unexpected directory") + return nil + } + + processID, err := strconv.ParseInt(components[1], 10, 64) + if err != nil { + // This is not a temporary gitaly process directory as the section + // after the hyphen is not a process id. + log.Error("gitaly process directory contains an unexpected directory") + return nil + } + + process, err := os.FindProcess(int(processID)) + if err != nil { + return fmt.Errorf("find process: %w", err) + } + defer func() { + if err := process.Release(); err != nil { + log.WithError(err).Error("failed releasing process") + } + }() + + if err := process.Signal(syscall.Signal(0)); err != nil { + // Either the process does not exist, or the pid has been re-used by for a + // process owned by another user and is not a Gitaly process. + if !errors.Is(err, os.ErrProcessDone) && !errors.Is(err, syscall.EPERM) { + return fmt.Errorf("signal: %w", err) + } + + log.Info("removing leftover gitaly process directory") + + if err := os.RemoveAll(filepath.Join(directory, entry.Name())); err != nil { + return fmt.Errorf("remove leftover gitaly process directory: %w", err) + } + } + + return nil + }(); err != nil { + return err + } + } + + return nil +} + +// GetGitalyProcessTempDir constructs a temporary directory name for the current gitaly +// process. This way, we can clean up old temporary directories by inspecting the pid attached +// to the folder. +func GetGitalyProcessTempDir(parentDir string, processID int) string { + return filepath.Join(parentDir, fmt.Sprintf("gitaly-%d", processID)) +} |