-
Notifications
You must be signed in to change notification settings - Fork 251
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Initial implementation of consistency checks
Add initial Check() and Repair() methods to Stores. Check() checks for inconsistencies between the layers which the lower-level storage driver claims to know about and the ones which we know we're managing. It checks that layers referenced by layers, images, and containers are known to us and that images referenced by containers are known to us. It checks that data which we store alongside layers, images, and containers is still present, and to the extent which we store other information about that data (frequenly just the size of the data), verifies that it matches recorded expectations. Lastly, it checks that layers which are part of images (and which we therefore know what they should have in them) have the expected content, and nothing else. Repair() removes any containers, images, and layers which have any errors associated with them. This is destructive, so its use should be considered and deliberate. Signed-off-by: Nalin Dahyabhai <[email protected]>
- Loading branch information
Showing
11 changed files
with
2,528 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
package storage | ||
|
||
import ( | ||
"archive/tar" | ||
"sort" | ||
"testing" | ||
|
||
"github.com/containers/storage/pkg/archive" | ||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestCheckDirectory(t *testing.T) { | ||
vectors := []struct { | ||
description string | ||
headers []tar.Header | ||
expected []string | ||
}{ | ||
{ | ||
description: "basic", | ||
headers: []tar.Header{ | ||
{Name: "a", Typeflag: tar.TypeDir}, | ||
}, | ||
expected: []string{ | ||
"a/", | ||
}, | ||
}, | ||
{ | ||
description: "whiteout", | ||
headers: []tar.Header{ | ||
{Name: "a", Typeflag: tar.TypeDir}, | ||
{Name: "a/b", Typeflag: tar.TypeDir}, | ||
{Name: "a/b/c", Typeflag: tar.TypeReg}, | ||
{Name: "a/b/d", Typeflag: tar.TypeReg}, | ||
{Name: "a/b/" + archive.WhiteoutPrefix + "c", Typeflag: tar.TypeReg}, | ||
}, | ||
expected: []string{ | ||
"a/", | ||
"a/b/", | ||
"a/b/d", | ||
}, | ||
}, | ||
{ | ||
description: "opaque", | ||
headers: []tar.Header{ | ||
{Name: "a", Typeflag: tar.TypeDir}, | ||
{Name: "a/b", Typeflag: tar.TypeDir}, | ||
{Name: "a/b/c", Typeflag: tar.TypeReg}, | ||
{Name: "a/b/d", Typeflag: tar.TypeReg}, | ||
{Name: "a/b/" + archive.WhiteoutOpaqueDir, Typeflag: tar.TypeReg}, | ||
}, | ||
expected: []string{ | ||
"a/", | ||
"a/b/", | ||
}, | ||
}, | ||
} | ||
for i := range vectors { | ||
t.Run(vectors[i].description, func(t *testing.T) { | ||
cd := newCheckDirectoryDefaults() | ||
for _, hdr := range vectors[i].headers { | ||
cd.header(&hdr) | ||
} | ||
actual := cd.names() | ||
sort.Strings(actual) | ||
expected := append([]string{}, vectors[i].expected...) | ||
sort.Strings(expected) | ||
assert.Equal(t, expected, actual) | ||
}) | ||
} | ||
} | ||
|
||
func TestCheckDetectWriteable(t *testing.T) { | ||
var sawRWlayers, sawRWimages bool | ||
stoar, err := GetStore(StoreOptions{ | ||
RunRoot: t.TempDir(), | ||
GraphRoot: t.TempDir(), | ||
GraphDriverName: "vfs", | ||
}) | ||
require.NoError(t, err, "unexpected error initializing test store") | ||
s, ok := stoar.(*store) | ||
require.True(t, ok, "unexpected error making type assertion") | ||
done, err := s.readAllLayerStores(func(store roLayerStore) (bool, error) { | ||
if roLayerStoreIsReallyReadWrite(store) { // implicitly checking that the type assertion in this function doesn't panic | ||
sawRWlayers = true | ||
} | ||
return false, nil | ||
}) | ||
assert.False(t, done, "unexpected error from readAllLayerStores") | ||
assert.NoError(t, err, "unexpected error from readAllLayerStores") | ||
assert.True(t, sawRWlayers, "unexpected error detecting which layer store is writeable") | ||
done, err = s.readAllImageStores(func(store roImageStore) (bool, error) { | ||
if roImageStoreIsReallyReadWrite(store) { // implicitly checking that the type assertion in this function doesn't panic | ||
sawRWimages = true | ||
} | ||
return false, nil | ||
}) | ||
assert.False(t, done, "unexpected error from readAllImageStores") | ||
assert.NoError(t, err, "unexpected error from readAllImageStores") | ||
assert.True(t, sawRWimages, "unexpected error detecting which image store is writeable") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
package main | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"os" | ||
"time" | ||
|
||
"github.com/containers/storage" | ||
"github.com/containers/storage/pkg/mflag" | ||
) | ||
|
||
var ( | ||
quickCheck, repair, forceRepair bool | ||
maximumUnreferencedLayerAge string | ||
) | ||
|
||
func check(flags *mflag.FlagSet, action string, m storage.Store, args []string) (int, error) { | ||
if forceRepair { | ||
repair = true | ||
} | ||
defer func() { | ||
if _, err := m.Shutdown(true); err != nil { | ||
fmt.Fprintf(os.Stderr, "shutdown: %v\n", err) | ||
} | ||
}() | ||
checkOptions := storage.CheckEverything() | ||
if quickCheck { | ||
checkOptions = storage.CheckMost() | ||
} | ||
if maximumUnreferencedLayerAge != "" { | ||
age, err := time.ParseDuration(maximumUnreferencedLayerAge) | ||
if err != nil { | ||
return 1, err | ||
} | ||
checkOptions.LayerUnreferencedMaximumAge = &age | ||
} | ||
report, err := m.Check(checkOptions) | ||
if err != nil { | ||
return 1, err | ||
} | ||
outputNonJSON := func(report storage.CheckReport) { | ||
for id, errs := range report.Layers { | ||
if len(errs) > 0 { | ||
fmt.Fprintf(os.Stdout, "layer %s:\n", id) | ||
} | ||
for _, err := range errs { | ||
fmt.Fprintf(os.Stdout, " %v\n", err) | ||
} | ||
} | ||
for id, errs := range report.ROLayers { | ||
if len(errs) > 0 { | ||
fmt.Fprintf(os.Stdout, "read-only layer %s:\n", id) | ||
} | ||
for _, err := range errs { | ||
fmt.Fprintf(os.Stdout, " %v\n", err) | ||
} | ||
} | ||
for id, errs := range report.Images { | ||
if len(errs) > 0 { | ||
fmt.Fprintf(os.Stdout, "image %s:\n", id) | ||
} | ||
for _, err := range errs { | ||
fmt.Fprintf(os.Stdout, " %v\n", err) | ||
} | ||
} | ||
for id, errs := range report.ROImages { | ||
if len(errs) > 0 { | ||
fmt.Fprintf(os.Stdout, "read-only image %s:\n", id) | ||
} | ||
for _, err := range errs { | ||
fmt.Fprintf(os.Stdout, " %v\n", err) | ||
} | ||
} | ||
for id, errs := range report.Containers { | ||
if len(errs) > 0 { | ||
fmt.Fprintf(os.Stdout, "container %s:\n", id) | ||
} | ||
for _, err := range errs { | ||
fmt.Fprintf(os.Stdout, " %v\n", err) | ||
} | ||
} | ||
} | ||
|
||
if jsonOutput { | ||
if err := json.NewEncoder(os.Stdout).Encode(report); err != nil { | ||
return 1, err | ||
} | ||
} else { | ||
outputNonJSON(report) | ||
} | ||
|
||
if !repair { | ||
if len(report.Layers) > 0 || len(report.ROLayers) > 0 || len(report.Images) > 0 || len(report.ROImages) > 0 || len(report.Containers) > 0 { | ||
return 1, fmt.Errorf("%d layer errors, %d read-only layer errors, %d image errors, %d read-only image errors, %d container errors", len(report.Layers), len(report.ROLayers), len(report.Images), len(report.ROImages), len(report.Containers)) | ||
} | ||
} else { | ||
options := storage.RepairOptions{ | ||
RemoveContainers: forceRepair, | ||
} | ||
if errs := m.Repair(report, &options); len(errs) != 0 { | ||
if jsonOutput { | ||
if err := json.NewEncoder(os.Stdout).Encode(errs); err != nil { | ||
return 1, err | ||
} | ||
} else { | ||
for _, err := range errs { | ||
fmt.Fprintf(os.Stderr, "%v\n", err) | ||
} | ||
} | ||
return 1, errs[0] | ||
} | ||
if len(report.ROLayers) > 0 || len(report.ROImages) > 0 || (!options.RemoveContainers && len(report.Containers) > 0) { | ||
var err error | ||
if options.RemoveContainers { | ||
err = fmt.Errorf("%d read-only layer errors, %d read-only image errors", len(report.ROLayers), len(report.ROImages)) | ||
} else { | ||
err = fmt.Errorf("%d read-only layer errors, %d read-only image errors, %d container errors", len(report.ROLayers), len(report.ROImages), len(report.Containers)) | ||
} | ||
return 1, err | ||
} | ||
} | ||
return 0, nil | ||
} | ||
|
||
func init() { | ||
commands = append(commands, command{ | ||
names: []string{"check"}, | ||
usage: "Check storage consistency", | ||
minArgs: 0, | ||
maxArgs: 0, | ||
action: check, | ||
addFlags: func(flags *mflag.FlagSet, cmd *command) { | ||
flags.BoolVar(&jsonOutput, []string{"-json", "j"}, jsonOutput, "Prefer JSON output") | ||
flags.StringVar(&maximumUnreferencedLayerAge, []string{"-max", "m"}, "24h", "Maximum allowed age for unreferenced layers") | ||
flags.BoolVar(&repair, []string{"-repair", "r"}, repair, "Remove damaged images and layers") | ||
flags.BoolVar(&forceRepair, []string{"-force", "f"}, forceRepair, "Remove damaged containers") | ||
flags.BoolVar(&quickCheck, []string{"-quick", "q"}, quickCheck, "Perform only quick checks") | ||
}, | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
## containers-storage-check 1 "September 2022" | ||
|
||
## NAME | ||
containers-storage check - Check for and remove damaged layers/images/containers | ||
|
||
## SYNOPSIS | ||
**containers-storage** **check** [-q] [-r [-f]] | ||
|
||
## DESCRIPTION | ||
Checks layers, images, and containers for identifiable damage. | ||
|
||
## OPTIONS | ||
|
||
**-f** | ||
|
||
When repairing damage, also remove damaged containers. No effect unless *-r* | ||
is used. | ||
|
||
**-r** | ||
|
||
Attempt to repair damage by removing damaged images and layers. If not | ||
specified, damage is reported but not acted upon. | ||
|
||
**-q** | ||
|
||
Perform only checks which are not expected to be time-consuming. This | ||
currently skips verifying that a layer which was initialized using a diff can | ||
reproduce that diff if asked to. | ||
|
||
## EXAMPLE | ||
**containers-storage check -r -f | ||
|
||
## SEE ALSO | ||
containers-storage(1) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.