Move Cleanup to its own package

PiperOrigin-RevId: 313663382
This commit is contained in:
Fabricio Voznika 2020-05-28 14:45:52 -07:00 committed by gVisor bot
parent 7b79370c10
commit f7418e2159
16 changed files with 186 additions and 100 deletions

17
pkg/cleanup/BUILD Normal file
View File

@ -0,0 +1,17 @@
load("//tools:defs.bzl", "go_library", "go_test")
package(licenses = ["notice"])
go_library(
name = "cleanup",
srcs = ["cleanup.go"],
visibility = ["//:sandbox"],
deps = [
],
)
go_test(
name = "cleanup_test",
srcs = ["cleanup_test.go"],
library = ":cleanup",
)

60
pkg/cleanup/cleanup.go Normal file
View File

@ -0,0 +1,60 @@
// Copyright 2020 The gVisor 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 cleanup provides utilities to clean "stuff" on defers.
package cleanup
// Cleanup allows defers to be aborted when cleanup needs to happen
// conditionally. Usage:
// cu := cleanup.Make(func() { f.Close() })
// defer cu.Clean() // failure before release is called will close the file.
// ...
// cu.Add(func() { f2.Close() }) // Adds another cleanup function
// ...
// cu.Release() // on success, aborts closing the file.
// return f
type Cleanup struct {
cleaners []func()
}
// Make creates a new Cleanup object.
func Make(f func()) Cleanup {
return Cleanup{cleaners: []func(){f}}
}
// Add adds a new function to be called on Clean().
func (c *Cleanup) Add(f func()) {
c.cleaners = append(c.cleaners, f)
}
// Clean calls all cleanup functions in reverse order.
func (c *Cleanup) Clean() {
clean(c.cleaners)
c.cleaners = nil
}
// Release releases the cleanup from its duties, i.e. cleanup functions are not
// called after this point. Returns a function that calls all registered
// functions in case the caller has use for them.
func (c *Cleanup) Release() func() {
old := c.cleaners
c.cleaners = nil
return func() { clean(old) }
}
func clean(cleaners []func()) {
for i := len(cleaners) - 1; i >= 0; i-- {
cleaners[i]()
}
}

View File

@ -0,0 +1,66 @@
// Copyright 2020 The gVisor 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 cleanup
import "testing"
func testCleanupHelper(clean, cleanAdd *bool, release bool) func() {
cu := Make(func() {
*clean = true
})
cu.Add(func() {
*cleanAdd = true
})
defer cu.Clean()
if release {
return cu.Release()
}
return nil
}
func TestCleanup(t *testing.T) {
clean := false
cleanAdd := false
testCleanupHelper(&clean, &cleanAdd, false)
if !clean {
t.Fatalf("cleanup function was not called.")
}
if !cleanAdd {
t.Fatalf("added cleanup function was not called.")
}
}
func TestRelease(t *testing.T) {
clean := false
cleanAdd := false
cleaner := testCleanupHelper(&clean, &cleanAdd, true)
// Check that clean was not called after release.
if clean {
t.Fatalf("cleanup function was called.")
}
if cleanAdd {
t.Fatalf("added cleanup function was called.")
}
// Call the cleaner function and check that both cleanup functions are called.
cleaner()
if !clean {
t.Fatalf("cleanup function was not called.")
}
if !cleanAdd {
t.Fatalf("added cleanup function was not called.")
}
}

View File

@ -7,8 +7,8 @@ go_library(
srcs = ["cgroup.go"],
visibility = ["//:sandbox"],
deps = [
"//pkg/cleanup",
"//pkg/log",
"//runsc/specutils",
"@com_github_cenkalti_backoff//:go_default_library",
"@com_github_opencontainers_runtime-spec//specs-go:go_default_library",
],

View File

@ -31,8 +31,8 @@ import (
"github.com/cenkalti/backoff"
specs "github.com/opencontainers/runtime-spec/specs-go"
"gvisor.dev/gvisor/pkg/cleanup"
"gvisor.dev/gvisor/pkg/log"
"gvisor.dev/gvisor/runsc/specutils"
)
const (
@ -246,7 +246,7 @@ func (c *Cgroup) Install(res *specs.LinuxResources) error {
// The Cleanup object cleans up partially created cgroups when an error occurs.
// Errors occuring during cleanup itself are ignored.
clean := specutils.MakeCleanup(func() { _ = c.Uninstall() })
clean := cleanup.Make(func() { _ = c.Uninstall() })
defer clean.Clean()
for key, cfg := range controllers {

View File

@ -16,6 +16,7 @@ go_library(
],
deps = [
"//pkg/abi/linux",
"//pkg/cleanup",
"//pkg/log",
"//pkg/sentry/control",
"//pkg/sentry/sighandling",
@ -53,6 +54,7 @@ go_test(
deps = [
"//pkg/abi/linux",
"//pkg/bits",
"//pkg/cleanup",
"//pkg/log",
"//pkg/sentry/control",
"//pkg/sentry/kernel",

View File

@ -31,6 +31,7 @@ import (
"github.com/cenkalti/backoff"
specs "github.com/opencontainers/runtime-spec/specs-go"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/cleanup"
"gvisor.dev/gvisor/pkg/log"
"gvisor.dev/gvisor/pkg/sentry/control"
"gvisor.dev/gvisor/pkg/sentry/sighandling"
@ -293,7 +294,7 @@ func New(conf *boot.Config, args Args) (*Container, error) {
}
// The Cleanup object cleans up partially created containers when an error
// occurs. Any errors occurring during cleanup itself are ignored.
cu := specutils.MakeCleanup(func() { _ = c.Destroy() })
cu := cleanup.Make(func() { _ = c.Destroy() })
defer cu.Clean()
// Lock the container metadata file to prevent concurrent creations of
@ -402,7 +403,7 @@ func (c *Container) Start(conf *boot.Config) error {
if err := c.Saver.lock(); err != nil {
return err
}
unlock := specutils.MakeCleanup(func() { c.Saver.unlock() })
unlock := cleanup.Make(func() { c.Saver.unlock() })
defer unlock.Clean()
if err := c.requireStatus("start", Created); err != nil {
@ -506,7 +507,7 @@ func Run(conf *boot.Config, args Args) (syscall.WaitStatus, error) {
}
// Clean up partially created container if an error occurs.
// Any errors returned by Destroy() itself are ignored.
cu := specutils.MakeCleanup(func() {
cu := cleanup.Make(func() {
c.Destroy()
})
defer cu.Clean()

View File

@ -27,6 +27,7 @@ import (
"time"
specs "github.com/opencontainers/runtime-spec/specs-go"
"gvisor.dev/gvisor/pkg/cleanup"
"gvisor.dev/gvisor/pkg/sentry/control"
"gvisor.dev/gvisor/pkg/sentry/kernel"
"gvisor.dev/gvisor/pkg/sync"
@ -64,29 +65,16 @@ func startContainers(conf *boot.Config, specs []*specs.Spec, ids []string) ([]*C
panic("conf.RootDir not set. Call testutil.SetupRootDir() to set.")
}
var (
containers []*Container
cleanups []func()
)
cleanups = append(cleanups, func() {
for _, c := range containers {
c.Destroy()
}
})
cleanupAll := func() {
for _, c := range cleanups {
c()
}
}
localClean := specutils.MakeCleanup(cleanupAll)
defer localClean.Clean()
cu := cleanup.Cleanup{}
defer cu.Clean()
var containers []*Container
for i, spec := range specs {
bundleDir, cleanup, err := testutil.SetupBundleDir(spec)
if err != nil {
return nil, nil, fmt.Errorf("error setting up container: %v", err)
}
cleanups = append(cleanups, cleanup)
cu.Add(cleanup)
args := Args{
ID: ids[i],
@ -97,6 +85,7 @@ func startContainers(conf *boot.Config, specs []*specs.Spec, ids []string) ([]*C
if err != nil {
return nil, nil, fmt.Errorf("error creating container: %v", err)
}
cu.Add(func() { cont.Destroy() })
containers = append(containers, cont)
if err := cont.Start(conf); err != nil {
@ -104,8 +93,7 @@ func startContainers(conf *boot.Config, specs []*specs.Spec, ids []string) ([]*C
}
}
localClean.Release()
return containers, cleanupAll, nil
return containers, cu.Release(), nil
}
type execDesc struct {

View File

@ -13,12 +13,12 @@ go_library(
visibility = ["//runsc:__subpackages__"],
deps = [
"//pkg/abi/linux",
"//pkg/cleanup",
"//pkg/fd",
"//pkg/log",
"//pkg/p9",
"//pkg/sync",
"//pkg/syserr",
"//runsc/specutils",
"@org_golang_x_sys//unix:go_default_library",
],
)

View File

@ -33,11 +33,11 @@ import (
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/cleanup"
"gvisor.dev/gvisor/pkg/fd"
"gvisor.dev/gvisor/pkg/log"
"gvisor.dev/gvisor/pkg/p9"
"gvisor.dev/gvisor/pkg/sync"
"gvisor.dev/gvisor/runsc/specutils"
)
const (
@ -439,7 +439,7 @@ func (l *localFile) Create(name string, mode p9.OpenFlags, perm p9.FileMode, uid
if err != nil {
return nil, nil, p9.QID{}, 0, extractErrno(err)
}
cu := specutils.MakeCleanup(func() {
cu := cleanup.Make(func() {
child.Close()
// Best effort attempt to remove the file in case of failure.
if err := syscall.Unlinkat(l.file.FD(), name); err != nil {
@ -480,7 +480,7 @@ func (l *localFile) Mkdir(name string, perm p9.FileMode, uid p9.UID, gid p9.GID)
if err := syscall.Mkdirat(l.file.FD(), name, uint32(perm.Permissions())); err != nil {
return p9.QID{}, extractErrno(err)
}
cu := specutils.MakeCleanup(func() {
cu := cleanup.Make(func() {
// Best effort attempt to remove the dir in case of failure.
if err := unix.Unlinkat(l.file.FD(), name, unix.AT_REMOVEDIR); err != nil {
log.Warningf("error unlinking dir %q after failure: %v", path.Join(l.hostPath, name), err)
@ -864,7 +864,7 @@ func (l *localFile) Symlink(target, newName string, uid p9.UID, gid p9.GID) (p9.
if err := unix.Symlinkat(target, l.file.FD(), newName); err != nil {
return p9.QID{}, extractErrno(err)
}
cu := specutils.MakeCleanup(func() {
cu := cleanup.Make(func() {
// Best effort attempt to remove the symlink in case of failure.
if err := syscall.Unlinkat(l.file.FD(), newName); err != nil {
log.Warningf("error unlinking file %q after failure: %v", path.Join(l.hostPath, newName), err)

View File

@ -13,6 +13,7 @@ go_library(
"//runsc:__subpackages__",
],
deps = [
"//pkg/cleanup",
"//pkg/control/client",
"//pkg/control/server",
"//pkg/log",

View File

@ -30,6 +30,7 @@ import (
"github.com/cenkalti/backoff"
specs "github.com/opencontainers/runtime-spec/specs-go"
"github.com/syndtr/gocapability/capability"
"gvisor.dev/gvisor/pkg/cleanup"
"gvisor.dev/gvisor/pkg/control/client"
"gvisor.dev/gvisor/pkg/control/server"
"gvisor.dev/gvisor/pkg/log"
@ -119,7 +120,7 @@ func New(conf *boot.Config, args *Args) (*Sandbox, error) {
s := &Sandbox{ID: args.ID, Cgroup: args.Cgroup}
// The Cleanup object cleans up partially created sandboxes when an error
// occurs. Any errors occurring during cleanup itself are ignored.
c := specutils.MakeCleanup(func() {
c := cleanup.Make(func() {
err := s.destroy()
log.Warningf("error destroying sandbox: %v", err)
})

View File

@ -444,36 +444,6 @@ func ContainsStr(strs []string, str string) bool {
return false
}
// Cleanup allows defers to be aborted when cleanup needs to happen
// conditionally. Usage:
// c := MakeCleanup(func() { f.Close() })
// defer c.Clean() // any failure before release is called will close the file.
// ...
// c.Release() // on success, aborts closing the file and return it.
// return f
type Cleanup struct {
clean func()
}
// MakeCleanup creates a new Cleanup object.
func MakeCleanup(f func()) Cleanup {
return Cleanup{clean: f}
}
// Clean calls the cleanup function.
func (c *Cleanup) Clean() {
if c.clean != nil {
c.clean()
c.clean = nil
}
}
// Release releases the cleanup from its duties, i.e. cleanup function is not
// called after this point.
func (c *Cleanup) Release() {
c.clean = nil
}
// RetryEintr retries the function until an error different than EINTR is
// returned.
func RetryEintr(f func() (uintptr, uintptr, error)) (uintptr, uintptr, error) {

View File

@ -33,6 +33,7 @@ go_test(
],
visibility = ["//:sandbox"],
deps = [
"//pkg/cleanup",
"//pkg/test/criutil",
"//pkg/test/dockerutil",
"//pkg/test/testutil",

View File

@ -30,10 +30,10 @@ import (
"testing"
"time"
"gvisor.dev/gvisor/pkg/cleanup"
"gvisor.dev/gvisor/pkg/test/criutil"
"gvisor.dev/gvisor/pkg/test/dockerutil"
"gvisor.dev/gvisor/pkg/test/testutil"
"gvisor.dev/gvisor/runsc/specutils"
)
// Tests for crictl have to be run as root (rather than in a user namespace)
@ -272,27 +272,20 @@ disabled_plugins = ["restart"]
// * Runs containerd and waits for it to reach a "ready" state for testing.
// * Returns a cleanup function that should be called at the end of the test.
func setup(t *testing.T) (*criutil.Crictl, func(), error) {
var cleanups []func()
cleanupFunc := func() {
for i := len(cleanups) - 1; i >= 0; i-- {
cleanups[i]()
}
}
cleanup := specutils.MakeCleanup(cleanupFunc)
defer cleanup.Clean()
// Create temporary containerd root and state directories, and a socket
// via which crictl and containerd communicate.
containerdRoot, err := ioutil.TempDir(testutil.TmpDir(), "containerd-root")
if err != nil {
t.Fatalf("failed to create containerd root: %v", err)
}
cleanups = append(cleanups, func() { os.RemoveAll(containerdRoot) })
cu := cleanup.Make(func() { os.RemoveAll(containerdRoot) })
defer cu.Clean()
containerdState, err := ioutil.TempDir(testutil.TmpDir(), "containerd-state")
if err != nil {
t.Fatalf("failed to create containerd state: %v", err)
}
cleanups = append(cleanups, func() { os.RemoveAll(containerdState) })
cu.Add(func() { os.RemoveAll(containerdState) })
sockAddr := filepath.Join(testutil.TmpDir(), "containerd-test.sock")
// We rewrite a configuration. This is based on the current docker
@ -305,7 +298,7 @@ func setup(t *testing.T) (*criutil.Crictl, func(), error) {
if err != nil {
t.Fatalf("failed to write containerd config")
}
cleanups = append(cleanups, configCleanup)
cu.Add(configCleanup)
// Start containerd.
cmd := exec.Command(getContainerd(),
@ -321,7 +314,8 @@ func setup(t *testing.T) (*criutil.Crictl, func(), error) {
stdout := &bytes.Buffer{}
cmd.Stderr = io.MultiWriter(startupW, stderr)
cmd.Stdout = io.MultiWriter(startupW, stdout)
cleanups = append(cleanups, func() {
cu.Add(func() {
// Log output in case of failure.
t.Logf("containerd stdout: %s", stdout.String())
t.Logf("containerd stderr: %s", stderr.String())
})
@ -338,15 +332,14 @@ func setup(t *testing.T) (*criutil.Crictl, func(), error) {
// Kill must be the last cleanup (as it will be executed first).
cc := criutil.NewCrictl(t, sockAddr)
cleanups = append(cleanups, func() {
cu.Add(func() {
cc.CleanUp() // Remove tmp files, etc.
if err := testutil.KillCommand(cmd); err != nil {
log.Printf("error killing containerd: %v", err)
}
})
cleanup.Release()
return cc, cleanupFunc, nil
return cc, cu.Release(), nil
}
// httpGet GETs the contents of a file served from a pod on port 80.

View File

@ -20,6 +20,7 @@ import (
"testing"
specs "github.com/opencontainers/runtime-spec/specs-go"
"gvisor.dev/gvisor/pkg/cleanup"
"gvisor.dev/gvisor/pkg/test/testutil"
"gvisor.dev/gvisor/runsc/container"
"gvisor.dev/gvisor/runsc/specutils"
@ -324,40 +325,26 @@ func createSpecs(cmds ...[]string) ([]*specs.Spec, []string) {
}
func startContainers(t *testing.T, specs []*specs.Spec, ids []string) ([]*container.Container, func(), error) {
var (
containers []*container.Container
cleanups []func()
)
cleanups = append(cleanups, func() {
for _, c := range containers {
c.Destroy()
}
})
cleanupAll := func() {
for _, c := range cleanups {
c()
}
}
localClean := specutils.MakeCleanup(cleanupAll)
defer localClean.Clean()
var containers []*container.Container
// All containers must share the same root.
rootDir, cleanup, err := testutil.SetupRootDir()
rootDir, clean, err := testutil.SetupRootDir()
if err != nil {
t.Fatalf("error creating root dir: %v", err)
}
cleanups = append(cleanups, cleanup)
cu := cleanup.Make(clean)
defer cu.Clean()
// Point this to from the configuration.
conf := testutil.TestConfig(t)
conf.RootDir = rootDir
for i, spec := range specs {
bundleDir, cleanup, err := testutil.SetupBundleDir(spec)
bundleDir, clean, err := testutil.SetupBundleDir(spec)
if err != nil {
return nil, nil, fmt.Errorf("error setting up bundle: %v", err)
}
cleanups = append(cleanups, cleanup)
cu.Add(clean)
args := container.Args{
ID: ids[i],
@ -375,6 +362,5 @@ func startContainers(t *testing.T, specs []*specs.Spec, ids []string) ([]*contai
}
}
localClean.Release()
return containers, cleanupAll, nil
return containers, cu.Release(), nil
}