Skip to content

Commit

Permalink
Add Store.GarbageCollect() method
Browse files Browse the repository at this point in the history
This looks in the container store for existing data dirs with ids not in
the container files and removes them. It also adds an (optional) driver
method to list available layers, then uses this and compares it to the
layers json file and removes layers that are not references.

Losing track of containers and layers can potentially happen in the
case of some kind of unclean shutdown, but mainly it happens at reboot
when using transient storage mode. Such users are recommended to run
a garbage collect at boot.

Signed-off-by: Alexander Larsson <[email protected]>
  • Loading branch information
alexlarsson committed Nov 11, 2022
1 parent 4655892 commit f16e34d
Show file tree
Hide file tree
Showing 12 changed files with 199 additions and 0 deletions.
36 changes: 36 additions & 0 deletions containers.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,9 @@ type rwContainerStore interface {

// Containers returns a slice enumerating the known containers.
Containers() ([]Container, error)

// Clean up unreferenced datadirs
GarbageCollect() error
}

type containerStore struct {
Expand Down Expand Up @@ -317,6 +320,39 @@ func (r *containerStore) Containers() ([]Container, error) {
return containers, nil
}

// This looks for datadirs in the store directory that are not referenced
// by the json file and removes it. These can happen in the case of unclean
// shutdowns or regular restarts in transient store mode.
func (r *containerStore) GarbageCollect() error {
entries, err := os.ReadDir(r.dir)
if err != nil {
// Unexpected, don't try any GC
return err
}

for _, entry := range entries {
id := entry.Name()
// Does it look like a datadir directory?
if !entry.IsDir() || !nameLooksLikeID(id) {
continue
}

// Should the id be there?
if r.byid[id] != nil {
continue
}

// Otherwise remove datadir
moreErr := os.RemoveAll(filepath.Join(r.dir, id))
// Propagate first error
if moreErr != nil && err == nil {
err = moreErr
}
}

return err
}

func (r *containerStore) datadir(id string) string {
return filepath.Join(r.dir, id)
}
Expand Down
5 changes: 5 additions & 0 deletions drivers/aufs/aufs.go
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,11 @@ func (a *Driver) Exists(id string) bool {
return true
}

// List layers (not including additional image stores)
func (a *Driver) ListLayers() ([]string, error) {
return nil, graphdriver.ErrNotSupported
}

// AdditionalImageStores returns additional image stores supported by the driver
func (a *Driver) AdditionalImageStores() []string {
return nil
Expand Down
5 changes: 5 additions & 0 deletions drivers/btrfs/btrfs.go
Original file line number Diff line number Diff line change
Expand Up @@ -676,6 +676,11 @@ func (d *Driver) Exists(id string) bool {
return err == nil
}

// List layers (not including additional image stores)
func (d *Driver) ListLayers() ([]string, error) {
return nil, graphdriver.ErrNotSupported
}

// AdditionalImageStores returns additional image stores supported by the driver
func (d *Driver) AdditionalImageStores() []string {
return nil
Expand Down
5 changes: 5 additions & 0 deletions drivers/devmapper/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,11 @@ func (d *Driver) Exists(id string) bool {
return d.DeviceSet.HasDevice(id)
}

// List layers (not including additional image stores)
func (d *Driver) ListLayers() ([]string, error) {
return nil, graphdriver.ErrNotSupported
}

// AdditionalImageStores returns additional image stores supported by the driver
func (d *Driver) AdditionalImageStores() []string {
return nil
Expand Down
3 changes: 3 additions & 0 deletions drivers/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,9 @@ type ProtoDriver interface {
// Exists returns whether a filesystem layer with the specified
// ID exists on this driver.
Exists(id string) bool
// Returns a list of layer ids that exist on this driver (does not include
// additional storage layers). Not supported by all backends.
ListLayers() ([]string, error)
// Status returns a set of key-value pairs which give low
// level diagnostic status about this driver.
Status() [][2]string
Expand Down
35 changes: 35 additions & 0 deletions drivers/overlay/overlay.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"strings"
"sync"
"syscall"
"unicode"

graphdriver "github.com/containers/storage/drivers"
"github.com/containers/storage/drivers/overlayutils"
Expand Down Expand Up @@ -1697,6 +1698,40 @@ func (d *Driver) Exists(id string) bool {
return err == nil
}

func nameLooksLikeID(name string) bool {
if len(name) != 64 {
return false
}
for _, c := range name {
if !unicode.Is(unicode.ASCII_Hex_Digit, c) {
return false
}
}
return true
}

// List layers (not including additional image stores)
func (d *Driver) ListLayers() ([]string, error) {
entries, err := os.ReadDir(d.home)
if err != nil {
return nil, err
}

layers := make([]string, 0)

for _, entry := range entries {
id := entry.Name()
// Does it look like a datadir directory?
if !entry.IsDir() || !nameLooksLikeID(id) {
continue
}

layers = append(layers, id)
}

return layers, err
}

// isParent returns if the passed in parent is the direct parent of the passed in layer
func (d *Driver) isParent(id, parent string) bool {
lowers, err := d.getLowerDirs(id)
Expand Down
35 changes: 35 additions & 0 deletions drivers/vfs/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"runtime"
"strconv"
"strings"
"unicode"

graphdriver "github.com/containers/storage/drivers"
"github.com/containers/storage/pkg/archive"
Expand Down Expand Up @@ -265,6 +266,40 @@ func (d *Driver) Exists(id string) bool {
return err == nil
}

func nameLooksLikeID(name string) bool {
if len(name) != 64 {
return false
}
for _, c := range name {
if !unicode.Is(unicode.ASCII_Hex_Digit, c) {
return false
}
}
return true
}

// List layers (not including additional image stores)
func (d *Driver) ListLayers() ([]string, error) {
entries, err := os.ReadDir(d.homes[0])
if err != nil {
return nil, err
}

layers := make([]string, 0)

for _, entry := range entries {
id := entry.Name()
// Does it look like a datadir directory?
if !entry.IsDir() || !nameLooksLikeID(id) {
continue
}

layers = append(layers, id)
}

return layers, err
}

// AdditionalImageStores returns additional image stores supported by the driver
func (d *Driver) AdditionalImageStores() []string {
if len(d.homes) > 1 {
Expand Down
5 changes: 5 additions & 0 deletions drivers/windows/windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,11 @@ func (d *Driver) Exists(id string) bool {
return result
}

// List layers (not including additional image stores)
func (d *Driver) ListLayers() ([]string, error) {
return nil, graphdriver.ErrNotSupported
}

// CreateFromTemplate creates a layer with the same contents and parent as another layer.
func (d *Driver) CreateFromTemplate(id, template string, templateIDMappings *idtools.IDMappings, parent string, parentIDMappings *idtools.IDMappings, opts *graphdriver.CreateOpts, readWrite bool) error {
return graphdriver.NaiveCreateFromTemplate(d, id, template, templateIDMappings, parent, parentIDMappings, opts, readWrite)
Expand Down
5 changes: 5 additions & 0 deletions drivers/zfs/zfs.go
Original file line number Diff line number Diff line change
Expand Up @@ -506,6 +506,11 @@ func (d *Driver) Exists(id string) bool {
return d.filesystemsCache[d.zfsPath(id)]
}

// List layers (not including additional image stores)
func (d *Driver) ListLayers() ([]string, error) {
return nil, graphdriver.ErrNotSupported
}

// AdditionalImageStores returns additional image stores supported by the driver
func (d *Driver) AdditionalImageStores() []string {
return nil
Expand Down
30 changes: 30 additions & 0 deletions layers.go
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,9 @@ type rwLayerStore interface {
// store.
// This API is experimental and can be changed without bumping the major version number.
PutAdditionalLayer(id string, parentLayer *Layer, names []string, aLayer drivers.AdditionalLayer) (layer *Layer, err error)

// Clean up unreferenced layers
GarbageCollect() error
}

type layerStore struct {
Expand Down Expand Up @@ -512,6 +515,33 @@ func (r *layerStore) Layers() ([]Layer, error) {
return layers, nil
}

func (r *layerStore) GarbageCollect() error {
layers, err := r.driver.ListLayers()

if err != nil {
if errors.Is(err, drivers.ErrNotSupported) {
return nil
}
return err
}

for _, id := range layers {
// Is the id still referenced
if r.byid[id] != nil {
continue
}

// Remove layer and any related data of unreferenced id
if err := r.driver.Remove(id); err != nil {
return err
}

os.Remove(r.tspath(id))
os.RemoveAll(r.datadir(id))
}
return nil
}

func (r *layerStore) mountspath() string {
return filepath.Join(r.rundir, "mountpoints.json")
}
Expand Down
22 changes: 22 additions & 0 deletions store.go
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,11 @@ type Store interface {
// Releasing AdditionalLayer handler is caller's responsibility.
// This API is experimental and can be changed without bumping the major version number.
LookupAdditionalLayer(d digest.Digest, imageref string) (AdditionalLayer, error)

// Tries to clean up remainders of previous containers or layers that are not
// references in the json files. These can happen in the case of unclean
// shutdowns or regular restarts in transient store mode.
GarbageCollect() error
}

// AdditionalLayer reprents a layer that is contained in the additional layer store
Expand Down Expand Up @@ -3385,3 +3390,20 @@ func (s *store) Free() {
}
}
}

// Tries to clean up old unreferenced container leftovers. returns the first error
// but continues as far as it can
func (s *store) GarbageCollect() error {
firstErr := s.writeToContainerStore(func(rcstore rwContainerStore) error {
return rcstore.GarbageCollect()
})

moreErr := s.writeToLayerStore(func(rlstore rwLayerStore) error {
return rlstore.GarbageCollect()
})
if firstErr == nil {
firstErr = moreErr
}

return firstErr
}
13 changes: 13 additions & 0 deletions utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package storage

import (
"fmt"
"unicode"

"github.com/containers/storage/types"
)
Expand Down Expand Up @@ -72,3 +73,15 @@ func applyNameOperation(oldNames []string, opParameters []string, op updateNameO
}
return dedupeNames(result), nil
}

func nameLooksLikeID(name string) bool {
if len(name) != 64 {
return false
}
for _, c := range name {
if !unicode.Is(unicode.ASCII_Hex_Digit, c) {
return false
}
}
return true
}

0 comments on commit f16e34d

Please sign in to comment.