Ensure bundle removal is atomic

This makes bundle removal atomic by first renaming the bundle and
working directories to a hidden path before removing the underlying
directories.

Closes #2567
Closes #2327

Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
This commit is contained in:
Michael Crosby 2018-12-20 13:45:18 -05:00
parent 9b366b2329
commit 36e4dc603e
4 changed files with 36 additions and 6 deletions

View File

@ -20,6 +20,7 @@ package linux
import ( import (
"context" "context"
"fmt"
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
@ -114,12 +115,12 @@ func (b *bundle) NewShimClient(ctx context.Context, namespace string, getClientO
// Delete deletes the bundle from disk // Delete deletes the bundle from disk
func (b *bundle) Delete() error { func (b *bundle) Delete() error {
err := os.RemoveAll(b.path) err := atomicDelete(b.path)
if err == nil { if err == nil {
return os.RemoveAll(b.workDir) return atomicDelete(b.workDir)
} }
// error removing the bundle path; still attempt removing work dir // error removing the bundle path; still attempt removing work dir
err2 := os.RemoveAll(b.workDir) err2 := atomicDelete(b.workDir)
if err2 == nil { if err2 == nil {
return err return err
} }
@ -152,3 +153,13 @@ func (b *bundle) shimConfig(namespace string, c *Config, runcOptions *runctypes.
SystemdCgroup: systemdCgroup, SystemdCgroup: systemdCgroup,
} }
} }
// atomicDelete renames the path to a hidden file before removal
func atomicDelete(path string) error {
// create a hidden dir for an atomic removal
atomicPath := filepath.Join(filepath.Dir(path), fmt.Sprintf(".%s", filepath.Base(path)))
if err := os.Rename(path, atomicPath); err != nil {
return err
}
return os.RemoveAll(atomicPath)
}

View File

@ -290,6 +290,10 @@ func (r *Runtime) restoreTasks(ctx context.Context) ([]*Task, error) {
continue continue
} }
name := namespace.Name() name := namespace.Name()
// skip hidden directories
if len(name) > 0 && name[0] == '.' {
continue
}
log.G(ctx).WithField("namespace", name).Debug("loading tasks in namespace") log.G(ctx).WithField("namespace", name).Debug("loading tasks in namespace")
tasks, err := r.loadTasks(ctx, name) tasks, err := r.loadTasks(ctx, name)
if err != nil { if err != nil {

View File

@ -18,6 +18,7 @@ package v2
import ( import (
"context" "context"
"fmt"
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
@ -114,20 +115,30 @@ type Bundle struct {
// Delete a bundle atomically // Delete a bundle atomically
func (b *Bundle) Delete() error { func (b *Bundle) Delete() error {
work, werr := os.Readlink(filepath.Join(b.Path, "work")) work, werr := os.Readlink(filepath.Join(b.Path, "work"))
err := os.RemoveAll(b.Path) err := atomicDelete(b.Path)
if err == nil { if err == nil {
if werr == nil { if werr == nil {
return os.RemoveAll(work) return atomicDelete(work)
} }
return nil return nil
} }
// error removing the bundle path; still attempt removing work dir // error removing the bundle path; still attempt removing work dir
var err2 error var err2 error
if werr == nil { if werr == nil {
err2 = os.RemoveAll(work) err2 = atomicDelete(work)
if err2 == nil { if err2 == nil {
return err return err
} }
} }
return errors.Wrapf(err, "failed to remove both bundle and workdir locations: %v", err2) return errors.Wrapf(err, "failed to remove both bundle and workdir locations: %v", err2)
} }
// atomicDelete renames the path to a hidden file before removal
func atomicDelete(path string) error {
// create a hidden dir for an atomic removal
atomicPath := filepath.Join(filepath.Dir(path), fmt.Sprintf(".%s", filepath.Base(path)))
if err := os.Rename(path, atomicPath); err != nil {
return err
}
return os.RemoveAll(atomicPath)
}

View File

@ -145,6 +145,10 @@ func (m *TaskManager) loadExistingTasks(ctx context.Context) error {
continue continue
} }
ns := nsd.Name() ns := nsd.Name()
// skip hidden directories
if len(ns) > 0 && ns[0] == '.' {
continue
}
log.G(ctx).WithField("namespace", ns).Debug("loading tasks in namespace") log.G(ctx).WithField("namespace", ns).Debug("loading tasks in namespace")
if err := m.loadTasks(namespaces.WithNamespace(ctx, ns)); err != nil { if err := m.loadTasks(namespaces.WithNamespace(ctx, ns)); err != nil {
log.G(ctx).WithField("namespace", ns).WithError(err).Error("loading tasks in namespace") log.G(ctx).WithField("namespace", ns).WithError(err).Error("loading tasks in namespace")