github.com/uber/kraken@v0.1.4/origin/blobserver/server_test.go (about)

     1  // Copyright (c) 2016-2019 Uber Technologies, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  package blobserver
    15  
    16  import (
    17  	"bytes"
    18  	"errors"
    19  	"fmt"
    20  	"io/ioutil"
    21  	"net/http"
    22  	"testing"
    23  	"time"
    24  
    25  	"github.com/golang/mock/gomock"
    26  	"github.com/stretchr/testify/require"
    27  
    28  	"github.com/uber/kraken/core"
    29  	"github.com/uber/kraken/lib/backend/backenderrors"
    30  	"github.com/uber/kraken/lib/persistedretry"
    31  	"github.com/uber/kraken/lib/persistedretry/writeback"
    32  	"github.com/uber/kraken/lib/store/metadata"
    33  	"github.com/uber/kraken/origin/blobclient"
    34  	"github.com/uber/kraken/utils/httputil"
    35  	"github.com/uber/kraken/utils/mockutil"
    36  	"github.com/uber/kraken/utils/testutil"
    37  )
    38  
    39  func TestHealth(t *testing.T) {
    40  	require := require.New(t)
    41  
    42  	cp := newTestClientProvider()
    43  
    44  	s := newTestServer(t, master1, hashRingMaxReplica(), cp)
    45  	defer s.cleanup()
    46  
    47  	resp, err := httputil.Get(
    48  		fmt.Sprintf("http://%s/health", s.addr))
    49  	defer resp.Body.Close()
    50  	require.NoError(err)
    51  	b, err := ioutil.ReadAll(resp.Body)
    52  	require.NoError(err)
    53  	require.Equal("OK\n", string(b))
    54  }
    55  
    56  func TestStatHandlerLocalNotFound(t *testing.T) {
    57  	require := require.New(t)
    58  
    59  	cp := newTestClientProvider()
    60  
    61  	s := newTestServer(t, master1, hashRingMaxReplica(), cp)
    62  	defer s.cleanup()
    63  
    64  	d := core.DigestFixture()
    65  	namespace := core.TagFixture()
    66  
    67  	_, err := cp.Provide(s.host).StatLocal(namespace, d)
    68  	require.Equal(blobclient.ErrBlobNotFound, err)
    69  }
    70  
    71  func TestStatHandlerInvalidParam(t *testing.T) {
    72  	digest := core.DigestFixture()
    73  
    74  	tests := []struct {
    75  		desc   string
    76  		path   string
    77  		status int
    78  	}{
    79  		{
    80  			"empty namespace",
    81  			fmt.Sprintf("internal/namespace//blobs/%s", digest),
    82  			http.StatusBadRequest,
    83  		}, {
    84  			"invalid digest",
    85  			"internal/namespace/foo/blobs/bar",
    86  			http.StatusBadRequest,
    87  		}, {
    88  			"invalid local param",
    89  			fmt.Sprintf("internal/namespace/foo/blobs/%s?local=bar", digest),
    90  			http.StatusInternalServerError,
    91  		},
    92  	}
    93  	for _, test := range tests {
    94  		t.Run(test.desc, func(t *testing.T) {
    95  			require := require.New(t)
    96  
    97  			cp := newTestClientProvider()
    98  
    99  			s := newTestServer(t, master1, hashRingMaxReplica(), cp)
   100  			defer s.cleanup()
   101  
   102  			_, err := httputil.Head(fmt.Sprintf("http://%s/%s", s.addr, test.path))
   103  			require.Error(err)
   104  			require.True(httputil.IsStatus(err, test.status))
   105  		})
   106  	}
   107  }
   108  
   109  func TestStatHandlerNotFound(t *testing.T) {
   110  	require := require.New(t)
   111  
   112  	cp := newTestClientProvider()
   113  
   114  	s := newTestServer(t, master1, hashRingMaxReplica(), cp)
   115  	defer s.cleanup()
   116  
   117  	d := core.DigestFixture()
   118  	namespace := core.TagFixture()
   119  
   120  	backendClient := s.backendClient(namespace)
   121  
   122  	backendClient.EXPECT().Stat(namespace, d.Hex()).Return(nil, backenderrors.ErrBlobNotFound)
   123  
   124  	_, err := cp.Provide(master1).Stat(namespace, d)
   125  	require.Equal(blobclient.ErrBlobNotFound, err)
   126  }
   127  
   128  func TestStatHandlerReturnSize(t *testing.T) {
   129  	require := require.New(t)
   130  
   131  	cp := newTestClientProvider()
   132  
   133  	s := newTestServer(t, master1, hashRingMaxReplica(), cp)
   134  	defer s.cleanup()
   135  
   136  	client := cp.Provide(s.host)
   137  	blob := core.SizedBlobFixture(256, 8)
   138  	namespace := core.TagFixture()
   139  
   140  	require.NoError(client.TransferBlob(blob.Digest, bytes.NewReader(blob.Content)))
   141  
   142  	ensureHasBlob(t, cp.Provide(s.host), namespace, blob)
   143  
   144  	bi, err := cp.Provide(master1).Stat(namespace, blob.Digest)
   145  	require.NoError(err)
   146  	require.NotNil(bi)
   147  	require.Equal(int64(256), bi.Size)
   148  }
   149  
   150  func TestDownloadBlobInvalidParam(t *testing.T) {
   151  	digest := core.DigestFixture()
   152  
   153  	tests := []struct {
   154  		desc   string
   155  		path   string
   156  		status int
   157  	}{
   158  		{
   159  			"empty namespace",
   160  			fmt.Sprintf("namespace//blobs/%s", digest),
   161  			http.StatusBadRequest,
   162  		}, {
   163  			"invalid digest",
   164  			"namespace/foo/blobs/bar",
   165  			http.StatusBadRequest,
   166  		},
   167  	}
   168  	for _, test := range tests {
   169  		t.Run(test.desc, func(t *testing.T) {
   170  			require := require.New(t)
   171  
   172  			cp := newTestClientProvider()
   173  
   174  			s := newTestServer(t, master1, hashRingMaxReplica(), cp)
   175  			defer s.cleanup()
   176  
   177  			_, err := httputil.Get(fmt.Sprintf("http://%s/%s", s.addr, test.path))
   178  			require.Error(err)
   179  			require.True(httputil.IsStatus(err, test.status))
   180  		})
   181  	}
   182  }
   183  
   184  func TestDownloadBlobNotFound(t *testing.T) {
   185  	require := require.New(t)
   186  
   187  	cp := newTestClientProvider()
   188  
   189  	s := newTestServer(t, master1, hashRingMaxReplica(), cp)
   190  	defer s.cleanup()
   191  
   192  	d := core.DigestFixture()
   193  	namespace := core.TagFixture()
   194  
   195  	backendClient := s.backendClient(namespace)
   196  	backendClient.EXPECT().Stat(namespace, d.Hex()).Return(nil, backenderrors.ErrBlobNotFound)
   197  
   198  	err := cp.Provide(master1).DownloadBlob(namespace, d, ioutil.Discard)
   199  	require.Error(err)
   200  	require.Equal(http.StatusNotFound, err.(httputil.StatusError).Status)
   201  }
   202  
   203  func TestDeleteBlob(t *testing.T) {
   204  	require := require.New(t)
   205  
   206  	cp := newTestClientProvider()
   207  
   208  	s := newTestServer(t, master1, hashRingMaxReplica(), cp)
   209  	defer s.cleanup()
   210  
   211  	client := cp.Provide(s.host)
   212  
   213  	blob := core.NewBlobFixture()
   214  	namespace := core.TagFixture()
   215  
   216  	require.NoError(client.TransferBlob(blob.Digest, bytes.NewReader(blob.Content)))
   217  
   218  	ensureHasBlob(t, cp.Provide(s.host), namespace, blob)
   219  
   220  	require.NoError(client.DeleteBlob(blob.Digest))
   221  
   222  	_, err := client.StatLocal(namespace, blob.Digest)
   223  	require.Equal(blobclient.ErrBlobNotFound, err)
   224  }
   225  
   226  func TestDeleteBlobInvalidParam(t *testing.T) {
   227  	require := require.New(t)
   228  
   229  	cp := newTestClientProvider()
   230  
   231  	s := newTestServer(t, master1, hashRingMaxReplica(), cp)
   232  	defer s.cleanup()
   233  
   234  	_, err := httputil.Delete(fmt.Sprintf("http://%s/internal/blobs/foo", s.addr))
   235  	require.Error(err)
   236  	require.True(httputil.IsStatus(err, http.StatusBadRequest))
   237  }
   238  
   239  func TestGetLocationsOK(t *testing.T) {
   240  	require := require.New(t)
   241  
   242  	cp := newTestClientProvider()
   243  	ring := hashRingSomeReplica()
   244  
   245  	s := newTestServer(t, master1, ring, cp)
   246  	defer s.cleanup()
   247  
   248  	blob := computeBlobForHosts(ring, master1, master2)
   249  
   250  	locs, err := cp.Provide(s.host).Locations(blob.Digest)
   251  	require.NoError(err)
   252  	require.ElementsMatch([]string{master1, master2}, locs)
   253  }
   254  
   255  func TestGetPeerContextOK(t *testing.T) {
   256  	require := require.New(t)
   257  
   258  	cp := newTestClientProvider()
   259  
   260  	s := newTestServer(t, master1, hashRingSomeReplica(), cp)
   261  	defer s.cleanup()
   262  
   263  	pctx, err := cp.Provide(master1).GetPeerContext()
   264  	require.NoError(err)
   265  	require.Equal(s.pctx, pctx)
   266  }
   267  
   268  func TestGetMetaInfoDownloadsBlobAndReplicates(t *testing.T) {
   269  	require := require.New(t)
   270  
   271  	ring := hashRingSomeReplica()
   272  	cp := newTestClientProvider()
   273  	namespace := core.TagFixture()
   274  
   275  	s1 := newTestServer(t, master1, ring, cp)
   276  	defer s1.cleanup()
   277  
   278  	s2 := newTestServer(t, master2, ring, cp)
   279  	defer s2.cleanup()
   280  
   281  	blob := computeBlobForHosts(ring, s1.host, s2.host)
   282  
   283  	backendClient := s1.backendClient(namespace)
   284  	backendClient.EXPECT().Stat(namespace,
   285  		blob.Digest.Hex()).Return(core.NewBlobInfo(int64(len(blob.Content))), nil).AnyTimes()
   286  	backendClient.EXPECT().Download(namespace, blob.Digest.Hex(), mockutil.MatchWriter(blob.Content)).Return(nil)
   287  
   288  	mi, err := cp.Provide(master1).GetMetaInfo(namespace, blob.Digest)
   289  	require.True(httputil.IsAccepted(err))
   290  	require.Nil(mi)
   291  
   292  	require.NoError(testutil.PollUntilTrue(5*time.Second, func() bool {
   293  		_, err := cp.Provide(master1).GetMetaInfo(namespace, blob.Digest)
   294  		return !httputil.IsAccepted(err)
   295  	}))
   296  
   297  	mi, err = cp.Provide(master1).GetMetaInfo(namespace, blob.Digest)
   298  	require.NoError(err)
   299  	require.NotNil(mi)
   300  	require.Equal(len(blob.Content), int(mi.Length()))
   301  
   302  	// Ensure blob was replicated to other master.
   303  	require.NoError(testutil.PollUntilTrue(5*time.Second, func() bool {
   304  		_, err := cp.Provide(master2).StatLocal(namespace, blob.Digest)
   305  		return err == nil
   306  	}))
   307  }
   308  
   309  func TestGetMetaInfoBlobNotFound(t *testing.T) {
   310  	require := require.New(t)
   311  
   312  	cp := newTestClientProvider()
   313  
   314  	s := newTestServer(t, master1, hashRingMaxReplica(), cp)
   315  	defer s.cleanup()
   316  
   317  	d := core.DigestFixture()
   318  	namespace := core.TagFixture()
   319  
   320  	backendClient := s.backendClient(namespace)
   321  	backendClient.EXPECT().Stat(namespace, d.Hex()).Return(nil, backenderrors.ErrBlobNotFound)
   322  
   323  	mi, err := cp.Provide(master1).GetMetaInfo(namespace, d)
   324  	require.True(httputil.IsNotFound(err))
   325  	require.Nil(mi)
   326  }
   327  
   328  func TestGetMetaInfoInvalidParam(t *testing.T) {
   329  	digest := core.DigestFixture()
   330  
   331  	tests := []struct {
   332  		desc   string
   333  		path   string
   334  		status int
   335  	}{
   336  		{
   337  			"empty namespace",
   338  			fmt.Sprintf("internal/namespace//blobs/%s/metainfo", digest),
   339  			http.StatusBadRequest,
   340  		}, {
   341  			"invalid digest",
   342  			"internal/namespace/foo/blobs/bar/metainfo",
   343  			http.StatusBadRequest,
   344  		},
   345  	}
   346  	for _, test := range tests {
   347  		t.Run(test.desc, func(t *testing.T) {
   348  			require := require.New(t)
   349  
   350  			cp := newTestClientProvider()
   351  
   352  			s := newTestServer(t, master1, hashRingMaxReplica(), cp)
   353  			defer s.cleanup()
   354  
   355  			_, err := httputil.Get(fmt.Sprintf("http://%s/%s", s.addr, test.path))
   356  			require.Error(err)
   357  			require.True(httputil.IsStatus(err, test.status))
   358  		})
   359  	}
   360  }
   361  
   362  func TestTransferBlob(t *testing.T) {
   363  	require := require.New(t)
   364  
   365  	cp := newTestClientProvider()
   366  
   367  	s := newTestServer(t, master1, hashRingMaxReplica(), cp)
   368  	defer s.cleanup()
   369  
   370  	blob := core.NewBlobFixture()
   371  	namespace := core.TagFixture()
   372  
   373  	err := cp.Provide(master1).TransferBlob(blob.Digest, bytes.NewReader(blob.Content))
   374  	require.NoError(err)
   375  	ensureHasBlob(t, cp.Provide(master1), namespace, blob)
   376  
   377  	// Ensure metainfo was generated.
   378  	var tm metadata.TorrentMeta
   379  	require.NoError(s.cas.GetCacheFileMetadata(blob.Digest.Hex(), &tm))
   380  
   381  	// Pushing again should be a no-op.
   382  	err = cp.Provide(master1).TransferBlob(blob.Digest, bytes.NewReader(blob.Content))
   383  	require.NoError(err)
   384  	ensureHasBlob(t, cp.Provide(master1), namespace, blob)
   385  }
   386  
   387  func TestTransferBlobInvalidParam(t *testing.T) {
   388  	t.Run("StartInvalidDigest", func(t *testing.T) {
   389  		require := require.New(t)
   390  
   391  		cp := newTestClientProvider()
   392  		s := newTestServer(t, master1, hashRingMaxReplica(), cp)
   393  		defer s.cleanup()
   394  
   395  		_, err := httputil.Post(
   396  			fmt.Sprintf("http://%s/internal/blobs/foo/uploads", s.addr))
   397  		require.Error(err)
   398  		require.True(httputil.IsStatus(err, http.StatusBadRequest))
   399  	})
   400  	t.Run("PatchInvalidDigest", func(t *testing.T) {
   401  		require := require.New(t)
   402  
   403  		cp := newTestClientProvider()
   404  		s := newTestServer(t, master1, hashRingMaxReplica(), cp)
   405  		defer s.cleanup()
   406  
   407  		d := core.DigestFixture()
   408  		_, err := httputil.Post(
   409  			fmt.Sprintf("http://%s/internal/blobs/%s/uploads", s.addr, d.String()))
   410  		require.NoError(err)
   411  		_, err = httputil.Patch(
   412  			fmt.Sprintf("http://%s/internal/blobs/foo/uploads/bar", s.addr),
   413  			httputil.SendHeaders(map[string]string{
   414  				"Content-Range": fmt.Sprintf("%d-%d", 0, 0),
   415  			}))
   416  		require.Error(err)
   417  		require.True(httputil.IsStatus(err, http.StatusBadRequest))
   418  	})
   419  	t.Run("PatchNonexistentUploadUUID", func(t *testing.T) {
   420  		require := require.New(t)
   421  
   422  		cp := newTestClientProvider()
   423  		s := newTestServer(t, master1, hashRingMaxReplica(), cp)
   424  		defer s.cleanup()
   425  
   426  		d := core.DigestFixture()
   427  		_, err := httputil.Post(
   428  			fmt.Sprintf("http://%s/internal/blobs/%s/uploads", s.addr, d.String()))
   429  		require.NoError(err)
   430  
   431  		_, err = httputil.Patch(
   432  			fmt.Sprintf("http://%s/internal/blobs/%s/uploads/bar", s.addr, d.String()),
   433  			httputil.SendHeaders(map[string]string{
   434  				"Content-Range": fmt.Sprintf("%d-%d", 0, 0),
   435  			}))
   436  		require.Error(err)
   437  		require.True(httputil.IsStatus(err, http.StatusNotFound))
   438  	})
   439  	t.Run("CommitInvalidDigest", func(t *testing.T) {
   440  		require := require.New(t)
   441  
   442  		cp := newTestClientProvider()
   443  		s := newTestServer(t, master1, hashRingMaxReplica(), cp)
   444  		defer s.cleanup()
   445  
   446  		d := core.DigestFixture()
   447  		_, err := httputil.Post(
   448  			fmt.Sprintf("http://%s/internal/blobs/%s/uploads", s.addr, d.String()))
   449  		require.NoError(err)
   450  
   451  		_, err = httputil.Put(
   452  			fmt.Sprintf("http://%s/internal/blobs/foo/uploads/bar", s.addr))
   453  		require.Error(err)
   454  		require.True(httputil.IsStatus(err, http.StatusBadRequest))
   455  	})
   456  	t.Run("CommitNonexistentUploadUUID", func(t *testing.T) {
   457  		require := require.New(t)
   458  
   459  		cp := newTestClientProvider()
   460  		s := newTestServer(t, master1, hashRingMaxReplica(), cp)
   461  		defer s.cleanup()
   462  
   463  		d := core.DigestFixture()
   464  		_, err := httputil.Post(
   465  			fmt.Sprintf("http://%s/internal/blobs/%s/uploads", s.addr, d.String()))
   466  		require.NoError(err)
   467  
   468  		_, err = httputil.Put(
   469  			fmt.Sprintf("http://%s/internal/blobs/%s/uploads/bar", s.addr, d.String()))
   470  		require.Error(err)
   471  		fmt.Println(err)
   472  		require.True(httputil.IsStatus(err, http.StatusNotFound))
   473  	})
   474  }
   475  
   476  func TestTransferBlobSmallChunkSize(t *testing.T) {
   477  	require := require.New(t)
   478  
   479  	s := newTestServer(t, master1, hashRingMaxReplica(), newTestClientProvider())
   480  	defer s.cleanup()
   481  
   482  	blob := core.SizedBlobFixture(1000, 1)
   483  	namespace := core.TagFixture()
   484  
   485  	client := blobclient.New(s.addr, blobclient.WithChunkSize(13))
   486  
   487  	err := client.TransferBlob(blob.Digest, bytes.NewReader(blob.Content))
   488  	require.NoError(err)
   489  	ensureHasBlob(t, client, namespace, blob)
   490  }
   491  
   492  func TestOverwriteMetainfo(t *testing.T) {
   493  	require := require.New(t)
   494  
   495  	cp := newTestClientProvider()
   496  
   497  	s := newTestServer(t, master1, hashRingMaxReplica(), cp)
   498  	defer s.cleanup()
   499  
   500  	blob := core.NewBlobFixture()
   501  	namespace := core.TagFixture()
   502  
   503  	err := cp.Provide(master1).TransferBlob(blob.Digest, bytes.NewReader(blob.Content))
   504  	require.NoError(err)
   505  
   506  	mi, err := cp.Provide(master1).GetMetaInfo(namespace, blob.Digest)
   507  	require.NoError(err)
   508  	require.Equal(int64(4), mi.PieceLength())
   509  
   510  	err = cp.Provide(master1).OverwriteMetaInfo(blob.Digest, 16)
   511  	require.NoError(err)
   512  
   513  	mi, err = cp.Provide(master1).GetMetaInfo(namespace, blob.Digest)
   514  	require.NoError(err)
   515  	require.Equal(int64(16), mi.PieceLength())
   516  }
   517  
   518  func TestReplicateToRemote(t *testing.T) {
   519  	require := require.New(t)
   520  
   521  	cp := newTestClientProvider()
   522  
   523  	s := newTestServer(t, master1, hashRingMaxReplica(), cp)
   524  	defer s.cleanup()
   525  
   526  	blob := core.NewBlobFixture()
   527  	namespace := core.TagFixture()
   528  
   529  	require.NoError(cp.Provide(master1).TransferBlob(blob.Digest, bytes.NewReader(blob.Content)))
   530  
   531  	remote := "remote:80"
   532  
   533  	remoteCluster := s.expectRemoteCluster(remote)
   534  	remoteCluster.EXPECT().UploadBlob(
   535  		namespace, blob.Digest, mockutil.MatchReader(blob.Content)).Return(nil)
   536  
   537  	require.NoError(cp.Provide(master1).ReplicateToRemote(namespace, blob.Digest, remote))
   538  }
   539  
   540  func TestReplicateToRemoteInvalidParam(t *testing.T) {
   541  	digest := core.DigestFixture()
   542  
   543  	tests := []struct {
   544  		desc   string
   545  		path   string
   546  		status int
   547  	}{
   548  		{
   549  			"empty namespace",
   550  			fmt.Sprintf("namespace//blobs/%s/remote/bar", digest),
   551  			http.StatusBadRequest,
   552  		}, {
   553  			"invalid digest",
   554  			"namespace/hello/blobs/foo/remote/bar",
   555  			http.StatusBadRequest,
   556  		},
   557  	}
   558  	for _, test := range tests {
   559  		t.Run(test.desc, func(t *testing.T) {
   560  			require := require.New(t)
   561  
   562  			cp := newTestClientProvider()
   563  
   564  			s := newTestServer(t, master1, hashRingMaxReplica(), cp)
   565  			defer s.cleanup()
   566  
   567  			_, err := httputil.Post(fmt.Sprintf("http://%s/%s", s.addr, test.path))
   568  			require.Error(err)
   569  			require.True(httputil.IsStatus(err, test.status))
   570  		})
   571  	}
   572  }
   573  
   574  func TestReplicateToRemoteWhenBlobInStorageBackend(t *testing.T) {
   575  	require := require.New(t)
   576  
   577  	cp := newTestClientProvider()
   578  
   579  	s := newTestServer(t, master1, hashRingMaxReplica(), cp)
   580  	defer s.cleanup()
   581  
   582  	blob := core.NewBlobFixture()
   583  	namespace := core.TagFixture()
   584  
   585  	backendClient := s.backendClient(namespace)
   586  	backendClient.EXPECT().Stat(namespace,
   587  		blob.Digest.Hex()).Return(core.NewBlobInfo(int64(len(blob.Content))), nil).AnyTimes()
   588  	backendClient.EXPECT().Download(namespace, blob.Digest.Hex(), mockutil.MatchWriter(blob.Content)).Return(nil)
   589  
   590  	remote := "remote:80"
   591  
   592  	remoteCluster := s.expectRemoteCluster(remote)
   593  	remoteCluster.EXPECT().UploadBlob(
   594  		namespace, blob.Digest, mockutil.MatchReader(blob.Content)).Return(nil)
   595  
   596  	require.NoError(testutil.PollUntilTrue(5*time.Second, func() bool {
   597  		err := cp.Provide(master1).ReplicateToRemote(namespace, blob.Digest, remote)
   598  		return !httputil.IsAccepted(err)
   599  	}))
   600  }
   601  
   602  func TestUploadBlobDuplicatesWriteBackTaskToReplicas(t *testing.T) {
   603  	require := require.New(t)
   604  
   605  	ring := hashRingSomeReplica()
   606  	namespace := core.TagFixture()
   607  
   608  	cp := newTestClientProvider()
   609  
   610  	s1 := newTestServer(t, master1, ring, cp)
   611  	defer s1.cleanup()
   612  
   613  	s2 := newTestServer(t, master2, ring, cp)
   614  	defer s2.cleanup()
   615  
   616  	blob := computeBlobForHosts(ring, s1.host, s2.host)
   617  
   618  	s1.writeBackManager.EXPECT().Add(
   619  		writeback.MatchTask(writeback.NewTask(namespace, blob.Digest.Hex(), 0))).Return(nil)
   620  	s2.writeBackManager.EXPECT().Add(
   621  		writeback.MatchTask(writeback.NewTask(namespace, blob.Digest.Hex(), 30*time.Minute)))
   622  
   623  	err := cp.Provide(s1.host).UploadBlob(namespace, blob.Digest, bytes.NewReader(blob.Content))
   624  	require.NoError(err)
   625  
   626  	ensureHasBlob(t, cp.Provide(s1.host), namespace, blob)
   627  	ensureHasBlob(t, cp.Provide(s2.host), namespace, blob)
   628  
   629  	// Shouldn't be able to delete blob since it is still being written back.
   630  	require.Error(cp.Provide(s1.host).DeleteBlob(blob.Digest))
   631  	require.Error(cp.Provide(s2.host).DeleteBlob(blob.Digest))
   632  }
   633  
   634  func TestUploadBlobRetriesWriteBackFailure(t *testing.T) {
   635  	require := require.New(t)
   636  
   637  	ring := hashRingNoReplica()
   638  	namespace := core.TagFixture()
   639  
   640  	cp := newTestClientProvider()
   641  
   642  	s := newTestServer(t, master1, ring, cp)
   643  	defer s.cleanup()
   644  
   645  	blob := computeBlobForHosts(ring, s.host)
   646  
   647  	expectedTask := writeback.MatchTask(writeback.NewTask(namespace, blob.Digest.Hex(), 0))
   648  
   649  	gomock.InOrder(
   650  		s.writeBackManager.EXPECT().Add(expectedTask).Return(errors.New("some error")),
   651  		s.writeBackManager.EXPECT().Add(expectedTask).Return(nil),
   652  	)
   653  
   654  	// Upload should "fail" because we failed to add a write-back task, but blob
   655  	// should still be present.
   656  	err := cp.Provide(s.host).UploadBlob(namespace, blob.Digest, bytes.NewReader(blob.Content))
   657  	require.Error(err)
   658  	ensureHasBlob(t, cp.Provide(s.host), namespace, blob)
   659  
   660  	// Uploading again should succeed.
   661  	err = cp.Provide(s.host).UploadBlob(namespace, blob.Digest, bytes.NewReader(blob.Content))
   662  	require.NoError(err)
   663  
   664  	// Shouldn't be able to delete blob since it is still being written back.
   665  	require.Error(cp.Provide(s.host).DeleteBlob(blob.Digest))
   666  }
   667  
   668  func TestUploadBlobResilientToDuplicationFailure(t *testing.T) {
   669  	require := require.New(t)
   670  
   671  	ring := hashRingSomeReplica()
   672  	namespace := core.TagFixture()
   673  
   674  	cp := newTestClientProvider()
   675  
   676  	s := newTestServer(t, master1, ring, cp)
   677  	defer s.cleanup()
   678  
   679  	cp.register(master2, blobclient.New("localhost:0"))
   680  
   681  	blob := computeBlobForHosts(ring, s.host, master2)
   682  
   683  	s.writeBackManager.EXPECT().Add(
   684  		writeback.MatchTask(writeback.NewTask(namespace, blob.Digest.Hex(), 0))).Return(nil)
   685  
   686  	err := cp.Provide(s.host).UploadBlob(namespace, blob.Digest, bytes.NewReader(blob.Content))
   687  	require.NoError(err)
   688  
   689  	ensureHasBlob(t, cp.Provide(s.host), namespace, blob)
   690  }
   691  
   692  func TestForceCleanupTTL(t *testing.T) {
   693  	require := require.New(t)
   694  
   695  	ring := hashRingNoReplica()
   696  	namespace := core.TagFixture()
   697  
   698  	cp := newTestClientProvider()
   699  
   700  	s := newTestServer(t, master1, ring, cp)
   701  	defer s.cleanup()
   702  
   703  	client := cp.Provide(s.host)
   704  
   705  	blob := computeBlobForHosts(ring, s.host)
   706  
   707  	s.writeBackManager.EXPECT().Add(
   708  		writeback.MatchTask(writeback.NewTask(namespace, blob.Digest.Hex(), 0))).Return(nil)
   709  
   710  	require.NoError(client.UploadBlob(namespace, blob.Digest, bytes.NewReader(blob.Content)))
   711  
   712  	ensureHasBlob(t, client, namespace, blob)
   713  
   714  	// Since the blob was just uploaded, it should not be deleted on force cleanup.
   715  	require.NoError(client.ForceCleanup(12 * time.Hour))
   716  	ensureHasBlob(t, client, namespace, blob)
   717  
   718  	s.clk.Add(14 * time.Hour)
   719  
   720  	s.writeBackManager.EXPECT().Find(writeback.NewNameQuery(blob.Digest.Hex())).Return(nil, nil)
   721  
   722  	require.NoError(client.ForceCleanup(12 * time.Hour))
   723  
   724  	_, err := client.StatLocal(namespace, blob.Digest)
   725  	require.Error(err)
   726  	require.Equal(blobclient.ErrBlobNotFound, err)
   727  }
   728  
   729  func TestForceCleanupNonOwner(t *testing.T) {
   730  	require := require.New(t)
   731  
   732  	ring := hashRingNoReplica()
   733  	namespace := core.TagFixture()
   734  
   735  	cp := newTestClientProvider()
   736  
   737  	s1 := newTestServer(t, master1, ring, cp)
   738  	defer s1.cleanup()
   739  
   740  	s2 := newTestServer(t, master2, ring, cp)
   741  	defer s2.cleanup()
   742  
   743  	client := cp.Provide(s1.host)
   744  
   745  	// s1 does not own blob, but will still accept the upload. On ForceCleanup, it
   746  	// should be removed.
   747  	blob := computeBlobForHosts(ring, s2.host)
   748  
   749  	s1.writeBackManager.EXPECT().Add(
   750  		writeback.MatchTask(writeback.NewTask(namespace, blob.Digest.Hex(), 0))).Return(nil)
   751  
   752  	s2.writeBackManager.EXPECT().Add(
   753  		writeback.MatchTask(writeback.NewTask(namespace, blob.Digest.Hex(), 30*time.Minute)))
   754  
   755  	require.NoError(client.UploadBlob(namespace, blob.Digest, bytes.NewReader(blob.Content)))
   756  
   757  	ensureHasBlob(t, client, namespace, blob)
   758  
   759  	s1.writeBackManager.EXPECT().Find(writeback.NewNameQuery(blob.Digest.Hex())).Return(nil, nil)
   760  
   761  	require.NoError(client.ForceCleanup(12 * time.Hour))
   762  
   763  	_, err := client.StatLocal(namespace, blob.Digest)
   764  	require.Error(err)
   765  	require.Equal(blobclient.ErrBlobNotFound, err)
   766  }
   767  
   768  func TestForceCleanupWriteBackFailures(t *testing.T) {
   769  	require := require.New(t)
   770  
   771  	ring := hashRingNoReplica()
   772  	namespace := core.TagFixture()
   773  
   774  	cp := newTestClientProvider()
   775  
   776  	s := newTestServer(t, master1, ring, cp)
   777  	defer s.cleanup()
   778  
   779  	client := cp.Provide(s.host)
   780  
   781  	blob := computeBlobForHosts(ring, s.host)
   782  
   783  	task := writeback.NewTask(namespace, blob.Digest.Hex(), 0)
   784  
   785  	s.writeBackManager.EXPECT().Add(writeback.MatchTask(task)).Return(nil)
   786  
   787  	require.NoError(client.UploadBlob(namespace, blob.Digest, bytes.NewReader(blob.Content)))
   788  
   789  	ensureHasBlob(t, client, namespace, blob)
   790  
   791  	s.clk.Add(14 * time.Hour)
   792  
   793  	// If there exists a writeback task, and it fails to manually execute it,
   794  	// the blob should not be deleted.
   795  	s.writeBackManager.EXPECT().Find(
   796  		writeback.NewNameQuery(blob.Digest.Hex())).Return([]persistedretry.Task{task}, nil)
   797  
   798  	s.writeBackManager.EXPECT().SyncExec(task).Return(errors.New("some error"))
   799  
   800  	require.NoError(client.ForceCleanup(12 * time.Hour))
   801  
   802  	ensureHasBlob(t, client, namespace, blob)
   803  }