mirror of
https://github.com/goharbor/harbor.git
synced 2024-12-20 07:37:38 +01:00
handle blob status chanage in put blob middlware (#12315)
* handle blob status chanage in put blob middlware After blob is uploaded success, the middleware will update the blob status accordingly. Signed-off-by: wang yan <wangyan@vmware.com>
This commit is contained in:
parent
7d50a6aab6
commit
468ba50a7e
@ -15,17 +15,57 @@
|
|||||||
package blob
|
package blob
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"fmt"
|
||||||
"strconv"
|
"github.com/goharbor/harbor/src/lib/errors"
|
||||||
|
|
||||||
"github.com/goharbor/harbor/src/lib/log"
|
"github.com/goharbor/harbor/src/lib/log"
|
||||||
|
blob_models "github.com/goharbor/harbor/src/pkg/blob/models"
|
||||||
"github.com/goharbor/harbor/src/pkg/distribution"
|
"github.com/goharbor/harbor/src/pkg/distribution"
|
||||||
"github.com/goharbor/harbor/src/server/middleware"
|
"github.com/goharbor/harbor/src/server/middleware"
|
||||||
|
"github.com/goharbor/harbor/src/server/middleware/requestid"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PutBlobUploadMiddleware middleware to create Blob and ProjectBlob after PUT /v2/<name>/blobs/uploads/<session_id> success
|
// PutBlobUploadMiddleware middleware is to update the blob status according to the different situation before the request passed into proxy(distribution).
|
||||||
|
// And it creates Blob and ProjectBlob after PUT /v2/<name>/blobs/uploads/<session_id>?digest=<digest> success - http.StatusCreated
|
||||||
|
// Why to use the middleware to handle blob status?
|
||||||
|
// 1, As Put blob will always happen after head blob gets a 404, but the 404 could be caused by blob status is deleting, which is marked by GC.
|
||||||
|
// 2, It has to deal with the concurrence blob push.
|
||||||
func PutBlobUploadMiddleware() func(http.Handler) http.Handler {
|
func PutBlobUploadMiddleware() func(http.Handler) http.Handler {
|
||||||
return middleware.AfterResponse(func(w http.ResponseWriter, r *http.Request, statusCode int) error {
|
|
||||||
|
before := middleware.BeforeRequest(func(r *http.Request) error {
|
||||||
|
ctx := r.Context()
|
||||||
|
logger := log.G(ctx)
|
||||||
|
|
||||||
|
v := r.URL.Query()
|
||||||
|
digest := v.Get("digest")
|
||||||
|
|
||||||
|
// digest empty is handled by the blob controller GET method
|
||||||
|
bb, err := blobController.Get(r.Context(), digest)
|
||||||
|
if err != nil {
|
||||||
|
if errors.IsNotFoundErr(err) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch bb.Status {
|
||||||
|
case blob_models.StatusNone, blob_models.StatusDelete, blob_models.StatusDeleteFailed:
|
||||||
|
err := blobController.Touch(r.Context(), bb)
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorf("failed to update blob: %s status to StatusNone, error:%v", bb.Digest, err)
|
||||||
|
return errors.Wrapf(err, fmt.Sprintf("the request id is: %s", r.Header.Get(requestid.HeaderXRequestID)))
|
||||||
|
}
|
||||||
|
case blob_models.StatusDeleting:
|
||||||
|
logger.Warningf(fmt.Sprintf("the asking blob is in GC, mark it as non existing, request id: %s", r.Header.Get(requestid.HeaderXRequestID)))
|
||||||
|
return errors.New(nil).WithMessage(fmt.Sprintf("the asking blob is in GC, mark it as non existing, request id: %s", r.Header.Get(requestid.HeaderXRequestID))).WithCode(errors.NotFoundCode)
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
after := middleware.AfterResponse(func(w http.ResponseWriter, r *http.Request, statusCode int) error {
|
||||||
if statusCode != http.StatusCreated {
|
if statusCode != http.StatusCreated {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -63,4 +103,6 @@ func PutBlobUploadMiddleware() func(http.Handler) http.Handler {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
|
return middleware.Chain(before, after)
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,8 @@ package blob
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
pkg_blob "github.com/goharbor/harbor/src/pkg/blob"
|
||||||
|
blob_models "github.com/goharbor/harbor/src/pkg/blob/models"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
@ -37,12 +39,11 @@ func (suite *PutBlobUploadMiddlewareTestSuite) SetupSuite() {
|
|||||||
|
|
||||||
func (suite *PutBlobUploadMiddlewareTestSuite) TestDataInBody() {
|
func (suite *PutBlobUploadMiddlewareTestSuite) TestDataInBody() {
|
||||||
suite.WithProject(func(projectID int64, projectName string) {
|
suite.WithProject(func(projectID int64, projectName string) {
|
||||||
req := suite.NewRequest(http.MethodPut, fmt.Sprintf("/v2/%s/photon/blobs/uploads/%s", projectName, uuid.New().String()), nil)
|
digest := suite.DigestString()
|
||||||
|
req := suite.NewRequest(http.MethodPut, fmt.Sprintf("/v2/%s/photon/blobs/uploads/%s?digest=%s", projectName, uuid.New().String(), digest), nil)
|
||||||
req.Header.Set("Content-Length", "512")
|
req.Header.Set("Content-Length", "512")
|
||||||
res := httptest.NewRecorder()
|
res := httptest.NewRecorder()
|
||||||
|
|
||||||
digest := suite.DigestString()
|
|
||||||
|
|
||||||
next := suite.NextHandler(http.StatusCreated, map[string]string{"Docker-Content-Digest": digest})
|
next := suite.NextHandler(http.StatusCreated, map[string]string{"Docker-Content-Digest": digest})
|
||||||
PutBlobUploadMiddleware()(next).ServeHTTP(res, req)
|
PutBlobUploadMiddleware()(next).ServeHTTP(res, req)
|
||||||
|
|
||||||
@ -60,7 +61,8 @@ func (suite *PutBlobUploadMiddlewareTestSuite) TestDataInBody() {
|
|||||||
func (suite *PutBlobUploadMiddlewareTestSuite) TestWithoutBody() {
|
func (suite *PutBlobUploadMiddlewareTestSuite) TestWithoutBody() {
|
||||||
suite.WithProject(func(projectID int64, projectName string) {
|
suite.WithProject(func(projectID int64, projectName string) {
|
||||||
sessionID := uuid.New().String()
|
sessionID := uuid.New().String()
|
||||||
path := fmt.Sprintf("/v2/%s/photon/blobs/uploads/%s", projectName, sessionID)
|
digest := suite.DigestString()
|
||||||
|
path := fmt.Sprintf("/v2/%s/photon/blobs/uploads/%s?digest=%s", projectName, sessionID, digest)
|
||||||
|
|
||||||
{
|
{
|
||||||
req := httptest.NewRequest(http.MethodPatch, path, nil)
|
req := httptest.NewRequest(http.MethodPatch, path, nil)
|
||||||
@ -74,8 +76,6 @@ func (suite *PutBlobUploadMiddlewareTestSuite) TestWithoutBody() {
|
|||||||
req := suite.NewRequest(http.MethodPut, path, nil)
|
req := suite.NewRequest(http.MethodPut, path, nil)
|
||||||
res := httptest.NewRecorder()
|
res := httptest.NewRecorder()
|
||||||
|
|
||||||
digest := suite.DigestString()
|
|
||||||
|
|
||||||
next := suite.NextHandler(http.StatusCreated, map[string]string{"Docker-Content-Digest": digest})
|
next := suite.NextHandler(http.StatusCreated, map[string]string{"Docker-Content-Digest": digest})
|
||||||
PutBlobUploadMiddleware()(next).ServeHTTP(res, req)
|
PutBlobUploadMiddleware()(next).ServeHTTP(res, req)
|
||||||
suite.Equal(http.StatusCreated, res.Code)
|
suite.Equal(http.StatusCreated, res.Code)
|
||||||
@ -92,6 +92,71 @@ func (suite *PutBlobUploadMiddlewareTestSuite) TestWithoutBody() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (suite *PutBlobUploadMiddlewareTestSuite) TestBlobInDeleting() {
|
||||||
|
suite.WithProject(func(projectID int64, projectName string) {
|
||||||
|
digest := suite.DigestString()
|
||||||
|
|
||||||
|
id, err := blob.Ctl.Ensure(suite.Context(), digest, "application/octet-stream", 512)
|
||||||
|
suite.Nil(err)
|
||||||
|
|
||||||
|
// status-none -> status-delete -> status-deleting
|
||||||
|
_, err = pkg_blob.Mgr.UpdateBlobStatus(suite.Context(), &blob_models.Blob{ID: id, Status: blob_models.StatusDelete})
|
||||||
|
suite.Nil(err)
|
||||||
|
_, err = pkg_blob.Mgr.UpdateBlobStatus(suite.Context(), &blob_models.Blob{ID: id, Status: blob_models.StatusDeleting, Version: 1})
|
||||||
|
suite.Nil(err)
|
||||||
|
|
||||||
|
req := suite.NewRequest(http.MethodPut, fmt.Sprintf("/v2/%s/photon/blobs/uploads/%s?digest=%s", projectName, uuid.New().String(), digest), nil)
|
||||||
|
req.Header.Set("Content-Length", "512")
|
||||||
|
res := httptest.NewRecorder()
|
||||||
|
|
||||||
|
next := suite.NextHandler(http.StatusCreated, map[string]string{"Docker-Content-Digest": digest})
|
||||||
|
PutBlobUploadMiddleware()(next).ServeHTTP(res, req)
|
||||||
|
suite.Equal(http.StatusNotFound, res.Code)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *PutBlobUploadMiddlewareTestSuite) TestBlobInDelete() {
|
||||||
|
suite.WithProject(func(projectID int64, projectName string) {
|
||||||
|
digest := suite.DigestString()
|
||||||
|
|
||||||
|
id, err := blob.Ctl.Ensure(suite.Context(), digest, "application/octet-stream", 512)
|
||||||
|
suite.Nil(err)
|
||||||
|
|
||||||
|
_, err = pkg_blob.Mgr.UpdateBlobStatus(suite.Context(), &blob_models.Blob{ID: id, Status: blob_models.StatusDelete})
|
||||||
|
suite.Nil(err)
|
||||||
|
|
||||||
|
req := suite.NewRequest(http.MethodPut, fmt.Sprintf("/v2/%s/photon/blobs/uploads/%s?digest=%s", projectName, uuid.New().String(), digest), nil)
|
||||||
|
req.Header.Set("Content-Length", "512")
|
||||||
|
res := httptest.NewRecorder()
|
||||||
|
|
||||||
|
next := suite.NextHandler(http.StatusCreated, map[string]string{"Docker-Content-Digest": digest})
|
||||||
|
PutBlobUploadMiddleware()(next).ServeHTTP(res, req)
|
||||||
|
suite.Equal(http.StatusCreated, res.Code)
|
||||||
|
|
||||||
|
exist, err := blob.Ctl.Exist(suite.Context(), digest, blob.IsAssociatedWithProject(projectID))
|
||||||
|
suite.Nil(err)
|
||||||
|
suite.True(exist)
|
||||||
|
|
||||||
|
blob, err := blob.Ctl.Get(suite.Context(), digest)
|
||||||
|
if suite.Nil(err) {
|
||||||
|
suite.Equal(digest, blob.Digest)
|
||||||
|
suite.Equal(int64(512), blob.Size)
|
||||||
|
suite.Equal(blob_models.StatusNone, blob.Status)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *PutBlobUploadMiddlewareTestSuite) TestRequestWithoutDigest() {
|
||||||
|
suite.WithProject(func(projectID int64, projectName string) {
|
||||||
|
req := suite.NewRequest(http.MethodPut, fmt.Sprintf("/v2/%s/photon/blobs/uploads/%s", projectName, uuid.New().String()), nil)
|
||||||
|
req.Header.Set("Content-Length", "512")
|
||||||
|
next := suite.NextHandler(http.StatusCreated, map[string]string{})
|
||||||
|
res := httptest.NewRecorder()
|
||||||
|
PutBlobUploadMiddleware()(next).ServeHTTP(res, req)
|
||||||
|
suite.Equal(http.StatusBadRequest, res.Code)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestPutBlobUploadMiddlewareTestSuite(t *testing.T) {
|
func TestPutBlobUploadMiddlewareTestSuite(t *testing.T) {
|
||||||
suite.Run(t, &PutBlobUploadMiddlewareTestSuite{})
|
suite.Run(t, &PutBlobUploadMiddlewareTestSuite{})
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user