feat(sync): additional changes for streaming

Signed-off-by: Vishwas Rajashekar <dev@vrajashkr.com>
This commit is contained in:
Vishwas Rajashekar
2026-05-16 18:44:10 +05:30
parent e2aa088e0d
commit 2fb691cd3b
14 changed files with 421 additions and 110 deletions
+2 -9
View File
@@ -43,7 +43,6 @@ const (
type Controller struct {
Config *config.Config
Router *mux.Router
StreamManager sync.StreamManager
MetaDB mTypes.MetaDB
StoreController storage.StoreController
Log log.Logger
@@ -377,12 +376,6 @@ func (c *Controller) Init() error {
}
}
if extensionsConfig.IsStreamingEnabled() {
c.Log.Info().Msg("streaming sync enabled")
sm := sync.NewChunkingStreamManager(c.Config, c.Log)
c.StreamManager = sm
}
return nil
}
@@ -606,8 +599,7 @@ func (c *Controller) StartBackgroundTasks() {
// Always call EnableSyncExtension to ensure proper logging, even when sync is disabled
//nolint: contextcheck
syncOnDemand, err := ext.EnableSyncExtension(
c.Config, c.MetaDB, c.StoreController, c.taskScheduler, c.StreamManager, c.Log)
syncOnDemand, err := ext.EnableSyncExtension(c.Config, c.MetaDB, c.StoreController, c.taskScheduler, c.Log)
if err != nil {
c.Log.Error().Err(err).Msg("failed to start sync extension")
}
@@ -663,4 +655,5 @@ type SyncOnDemand interface {
SyncImage(ctx context.Context, repo, reference string) error
SyncReferrers(ctx context.Context, repo string, subjectDigestStr string, referenceTypes []string) error
FetchManifest(ctx context.Context, repo, reference string) (manifest.Manifest, error)
StreamManager() sync.StreamManager
}
+35 -10
View File
@@ -1119,15 +1119,24 @@ func (rh *RouteHandler) CheckBlob(response http.ResponseWriter, request *http.Re
if err != nil {
details := zerr.GetDetails(err)
if errors.Is(err, zerr.ErrBadBlobDigest) { //nolint:gocritic,dupl // errorslint conflicts with gocritic:IfElseChain
details["digest"] = digest.String()
e := apiErr.NewError(apiErr.DIGEST_INVALID).AddDetail(details)
zcommon.WriteJSON(response, http.StatusBadRequest, apiErr.NewErrorList(e))
} else if errors.Is(err, zerr.ErrRepoNotFound) {
streamErr := rh.getBlobInfoFromStreamCache(digest.String(), response)
if streamErr == nil {
return
}
details["name"] = name
e := apiErr.NewError(apiErr.NAME_UNKNOWN).AddDetail(details)
zcommon.WriteJSON(response, http.StatusNotFound, apiErr.NewErrorList(e))
} else if errors.Is(err, zerr.ErrBlobNotFound) {
streamErr := rh.getBlobInfoFromStreamCache(digest.String(), response)
if streamErr == nil {
return
}
details["digest"] = digest.String()
e := apiErr.NewError(apiErr.BLOB_UNKNOWN).AddDetail(details)
zcommon.WriteJSON(response, http.StatusNotFound, apiErr.NewErrorList(e))
@@ -1153,6 +1162,30 @@ func (rh *RouteHandler) CheckBlob(response http.ResponseWriter, request *http.Re
response.WriteHeader(http.StatusOK)
}
func (rh *RouteHandler) getBlobInfoFromStreamCache(digest string, response http.ResponseWriter) error {
rh.c.Log.Debug().Str("digest", digest).Msg("checking stream cache for blob existence")
extConf := rh.c.Config.CopyExtensionsConfig()
if extConf.IsStreamingEnabled() {
// when streaming is enabled, the blob might exist in the stream cache
blobSize, blobMediaType, err := rh.c.SyncOnDemand.StreamManager().CachedBlobInfo(digest)
if err != nil {
rh.c.Log.Error().Err(err).Str("digest", digest).Msg("error checking stream cache for blob existence")
return err
}
blen := blobSize
response.Header().Set("Content-Length", strconv.FormatInt(blen, 10))
response.Header().Set("Accept-Ranges", "bytes")
response.Header().Set("Content-Type", blobMediaType)
response.Header().Set(constants.DistContentDigestKey, digest)
response.WriteHeader(http.StatusOK)
}
return nil
}
type httpRange struct {
start int64
end int64
@@ -1463,7 +1496,7 @@ func (rh *RouteHandler) GetBlob(response http.ResponseWriter, request *http.Requ
if errors.Is(err, zerr.ErrRepoNotFound) || errors.Is(err, zerr.ErrBlobNotFound) {
rh.c.Log.Info().Msg("blob was not found. Connecting client to stream")
copier, err := rh.c.StreamManager.ConnectClient(digest.String(), response)
copier, err := rh.c.SyncOnDemand.StreamManager().ConnectClient(digest.String(), response)
if err != nil {
rh.c.Log.Error().Err(err).Msg("failed to connect client to stream")
response.WriteHeader(http.StatusInternalServerError)
@@ -1471,7 +1504,6 @@ func (rh *RouteHandler) GetBlob(response http.ResponseWriter, request *http.Requ
return
}
// TODO: handle partial
err = copier.Copy()
if err != nil {
rh.c.Log.Error().Err(err).Msg("unexpected error during stream copy")
@@ -2678,7 +2710,7 @@ func getImageManifest(ctx context.Context, routeHandler *RouteHandler, imgStore
extConf := routeHandler.c.Config.CopyExtensionsConfig()
// if streaming enabled, return manifest immediately, start sync in background
// if streaming enabled, return manifest immediately
if extConf.IsStreamingEnabled() {
routeHandler.c.Log.Info().Str("repository", name).Str("reference", reference).
Msg("streaming is enabled. Direct fetching manifest.")
@@ -2699,13 +2731,6 @@ func getImageManifest(ctx context.Context, routeHandler *RouteHandler, imgStore
return imgStore.GetImageManifest(name, reference)
}
go func() {
if errSync := routeHandler.c.SyncOnDemand.SyncImage(ctx, name, reference); errSync != nil {
routeHandler.c.Log.Err(errSync).Str("repository", name).Str("reference", reference).
Msg("failed to sync image")
}
}()
return content, fetchedManifest.GetDescriptor().Digest, fetchedManifest.GetDescriptor().MediaType, nil
}