github.com/opencontainers/umoci@v0.4.8-0.20240508124516-656e4836fb0d/oci/casext/gc_test.go (about)

     1  package casext
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"encoding/json"
     7  	"fmt"
     8  	"io/ioutil"
     9  	"os"
    10  	"path/filepath"
    11  	"strings"
    12  	"testing"
    13  
    14  	"github.com/opencontainers/go-digest"
    15  	imeta "github.com/opencontainers/image-spec/specs-go"
    16  	ispec "github.com/opencontainers/image-spec/specs-go/v1"
    17  	"github.com/opencontainers/umoci/oci/cas/dir"
    18  )
    19  
    20  func TestGCWithEmptyIndex(t *testing.T) {
    21  	ctx := context.Background()
    22  
    23  	root, err := ioutil.TempDir("", "umoci-TestEngineReference")
    24  	if err != nil {
    25  		t.Fatal(err)
    26  	}
    27  	defer os.RemoveAll(root)
    28  
    29  	image := filepath.Join(root, "image")
    30  	if err := dir.Create(image); err != nil {
    31  		t.Fatalf("unexpected error creating image: %+v", err)
    32  	}
    33  
    34  	engine, err := dir.Open(image)
    35  	if err != nil {
    36  		t.Fatalf("unexpected error opening image: %+v", err)
    37  	}
    38  	engineExt := NewEngine(engine)
    39  	defer engine.Close()
    40  
    41  	// creates an empty index.json and several orphan blobs which should be pruned
    42  	descMap, err := fakeSetupEngine(t, engineExt)
    43  	if err != nil {
    44  		t.Fatalf("unexpected error doing fakeSetupEngine: %+v", err)
    45  	}
    46  	if descMap == nil {
    47  		t.Fatalf("empty descMap")
    48  	}
    49  
    50  	b, err := engine.ListBlobs(ctx)
    51  	if err != nil {
    52  		t.Fatalf("unable to list blobs: %+v", err)
    53  	}
    54  	if len(b) == 0 {
    55  		t.Fatalf("expected non-empty blob list before GC")
    56  	}
    57  
    58  	err = engineExt.GC(ctx)
    59  	if err != nil {
    60  		t.Fatalf("GC failed: %+v", err)
    61  	}
    62  
    63  	b, err = engine.ListBlobs(ctx)
    64  	if err != nil {
    65  		t.Fatalf("unable to list blobs: %+v", err)
    66  	}
    67  	if len(b) != 0 {
    68  		t.Fatalf("expected empty blob list after GC: %#v", b)
    69  	}
    70  }
    71  
    72  func TestGCWithNonEmptyIndex(t *testing.T) {
    73  	ctx := context.Background()
    74  
    75  	root, err := ioutil.TempDir("", "umoci-TestEngineReference")
    76  	if err != nil {
    77  		t.Fatal(err)
    78  	}
    79  	defer os.RemoveAll(root)
    80  
    81  	image := filepath.Join(root, "image")
    82  	if err := dir.Create(image); err != nil {
    83  		t.Fatalf("unexpected error creating image: %+v", err)
    84  	}
    85  
    86  	engine, err := dir.Open(image)
    87  	if err != nil {
    88  		t.Fatalf("unexpected error opening image: %+v", err)
    89  	}
    90  	engineExt := NewEngine(engine)
    91  	defer engine.Close()
    92  
    93  	// creates an empty index.json and several orphan blobs which should be pruned
    94  	descMap, err := fakeSetupEngine(t, engineExt)
    95  	if err != nil {
    96  		t.Fatalf("unexpected error doing fakeSetupEngine: %+v", err)
    97  	}
    98  	if descMap == nil {
    99  		t.Fatalf("empty descMap")
   100  	}
   101  
   102  	b, err := engine.ListBlobs(ctx)
   103  	if err != nil {
   104  		t.Fatalf("unable to list blobs: %+v", err)
   105  	}
   106  	if len(b) == 0 {
   107  		t.Fatalf("expected non-empty blob list before GC")
   108  	}
   109  
   110  	// build a blob, manifest, index that will survive GC
   111  	content := "this is a test blob"
   112  	br := strings.NewReader(content)
   113  	digest, size, err := engine.PutBlob(ctx, br)
   114  	if err != nil {
   115  		t.Fatalf("error writing blob: %+v", err)
   116  	}
   117  	if size != int64(len(content)) {
   118  		t.Fatalf("partially written blob")
   119  	}
   120  
   121  	m := ispec.Manifest{
   122  		Versioned: imeta.Versioned{
   123  			SchemaVersion: 2,
   124  		},
   125  		MediaType: ispec.MediaTypeImageManifest,
   126  		Config: ispec.Descriptor{
   127  			MediaType: ispec.MediaTypeImageLayer,
   128  			Digest:    digest,
   129  			Size:      size,
   130  		},
   131  		Layers: []ispec.Descriptor{
   132  			{
   133  				MediaType: ispec.MediaTypeImageLayer,
   134  				Digest:    digest,
   135  				Size:      size,
   136  			},
   137  		},
   138  	}
   139  	data, err := json.Marshal(&m)
   140  	if err != nil {
   141  		t.Fatalf("error marshaling json: %+v", err)
   142  	}
   143  	mr := bytes.NewReader(data)
   144  	digest, size, err = engine.PutBlob(ctx, mr)
   145  	if err != nil {
   146  		t.Fatalf("error writing blob: %+v", err)
   147  	}
   148  	if size != int64(len(data)) {
   149  		t.Fatalf("partially written blob")
   150  	}
   151  
   152  	idx := ispec.Index{
   153  		Versioned: imeta.Versioned{
   154  			SchemaVersion: 2,
   155  		},
   156  		MediaType: ispec.MediaTypeImageIndex,
   157  		Manifests: []ispec.Descriptor{
   158  			{
   159  				MediaType: ispec.MediaTypeImageManifest,
   160  				Digest:    digest,
   161  				Size:      size,
   162  			},
   163  		},
   164  	}
   165  	if err := engine.PutIndex(ctx, idx); err != nil {
   166  		t.Fatalf("error writing index: %+v", err)
   167  	}
   168  
   169  	b, err = engine.ListBlobs(ctx)
   170  	if err != nil {
   171  		t.Fatalf("unable to list blobs: %+v", err)
   172  	}
   173  	if len(b) <= 2 {
   174  		t.Fatalf("expected >2 blob list before GC: %#v", b)
   175  	}
   176  
   177  	err = engineExt.GC(ctx)
   178  	if err != nil {
   179  		t.Fatalf("GC failed: %+v", err)
   180  	}
   181  
   182  	b, err = engine.ListBlobs(ctx)
   183  	if err != nil {
   184  		t.Fatalf("unable to list blobs: %+v", err)
   185  	}
   186  	if len(b) != 2 {
   187  		t.Fatalf("expected two-entry blob list after GC: %#v", b)
   188  	}
   189  }
   190  
   191  func gcOkFunc(t *testing.T, expectedDigest digest.Digest, unexpectedDigest digest.Digest) GCPolicy {
   192  	return func(ctx context.Context, digest digest.Digest) (bool, error) {
   193  		if digest == "" || digest == unexpectedDigest {
   194  			t.Errorf("got incorrect digest to gc policy callback: unexpected %v", digest)
   195  		}
   196  		if digest != expectedDigest {
   197  			t.Errorf("got incorrect digest to gc policy callback: expected %v, got %v", expectedDigest, digest)
   198  		}
   199  		return true, nil
   200  	}
   201  }
   202  
   203  func gcSkipFunc(t *testing.T, expectedDigest digest.Digest) GCPolicy {
   204  	return func(ctx context.Context, digest digest.Digest) (bool, error) {
   205  		if digest != expectedDigest {
   206  			t.Errorf("got incorrect digest to gc policy callback: expected %v, got %v", expectedDigest, digest)
   207  		}
   208  		return false, nil
   209  	}
   210  }
   211  
   212  func errFunc(ctx context.Context, digest digest.Digest) (bool, error) {
   213  	return false, fmt.Errorf("err policy")
   214  }
   215  
   216  func TestGCWithPolicy(t *testing.T) {
   217  	ctx := context.Background()
   218  
   219  	root, err := ioutil.TempDir("", "umoci-TestEngineReference")
   220  	if err != nil {
   221  		t.Fatal(err)
   222  	}
   223  	defer os.RemoveAll(root)
   224  
   225  	image := filepath.Join(root, "image")
   226  	if err := dir.Create(image); err != nil {
   227  		t.Fatalf("unexpected error creating image: %+v", err)
   228  	}
   229  
   230  	engine, err := dir.Open(image)
   231  	if err != nil {
   232  		t.Fatalf("unexpected error opening image: %+v", err)
   233  	}
   234  	engineExt := NewEngine(engine)
   235  	defer engine.Close()
   236  
   237  	// build a orphan blob that should be GC'ed
   238  	content := "this is a orphan blob"
   239  	br := strings.NewReader(content)
   240  	odigest, size, err := engine.PutBlob(ctx, br)
   241  	if err != nil {
   242  		t.Fatalf("error writing blob: %+v", err)
   243  	}
   244  	if size != int64(len(content)) {
   245  		t.Fatalf("partially written blob")
   246  	}
   247  
   248  	// build a blob, manifest, index that will survive GC
   249  	content = "this is a test blob"
   250  	br = strings.NewReader(content)
   251  	digest, size, err := engine.PutBlob(ctx, br)
   252  	if err != nil {
   253  		t.Fatalf("error writing blob: %+v", err)
   254  	}
   255  	if size != int64(len(content)) {
   256  		t.Fatalf("partially written blob")
   257  	}
   258  
   259  	digest, size, err = engineExt.PutBlobJSON(ctx,
   260  		ispec.Manifest{
   261  			Versioned: imeta.Versioned{
   262  				SchemaVersion: 2,
   263  			},
   264  			MediaType: ispec.MediaTypeImageManifest,
   265  			Config: ispec.Descriptor{
   266  				MediaType: ispec.MediaTypeImageLayer,
   267  				Digest:    digest,
   268  				Size:      size,
   269  			},
   270  			Layers: []ispec.Descriptor{
   271  				{
   272  					MediaType: ispec.MediaTypeImageLayer,
   273  					Digest:    digest,
   274  					Size:      size,
   275  				},
   276  			},
   277  		})
   278  	if err != nil {
   279  		t.Fatalf("error writing blob: %+v", err)
   280  	}
   281  
   282  	idx := ispec.Index{
   283  		Versioned: imeta.Versioned{
   284  			SchemaVersion: 2,
   285  		},
   286  		MediaType: ispec.MediaTypeImageIndex,
   287  		Manifests: []ispec.Descriptor{
   288  			{
   289  				MediaType: ispec.MediaTypeImageManifest,
   290  				Digest:    digest,
   291  				Size:      size,
   292  			},
   293  		},
   294  	}
   295  	if err := engine.PutIndex(ctx, idx); err != nil {
   296  		t.Fatalf("error writing index: %+v", err)
   297  	}
   298  
   299  	err = engineExt.GC(ctx, errFunc)
   300  	// expect this to fail
   301  	if err == nil {
   302  		t.Fatalf("GC failed: %+v", err)
   303  	}
   304  
   305  	err = engineExt.GC(ctx, gcSkipFunc(t, odigest))
   306  	// expect this to succeed but not perform GC
   307  	if err != nil {
   308  		t.Fatalf("GC failed: %+v", err)
   309  	}
   310  	b, err := engine.ListBlobs(ctx)
   311  	if err != nil {
   312  		t.Fatalf("unable to list blobs: %+v", err)
   313  	}
   314  	if len(b) != 3 {
   315  		t.Fatalf("expected all entries in blob list after skip GC policy: %#v", b)
   316  	}
   317  
   318  	err = engineExt.GC(ctx, gcOkFunc(t, odigest, digest))
   319  	// expect this to succeed
   320  	if err != nil {
   321  		t.Fatalf("GC failed: %+v", err)
   322  	}
   323  
   324  	b, err = engine.ListBlobs(ctx)
   325  	if err != nil {
   326  		t.Fatalf("unable to list blobs: %+v", err)
   327  	}
   328  	if len(b) != 2 {
   329  		t.Fatalf("expected blob list with two entries after GC: %#v", b)
   330  	}
   331  }