/* Copyright The Velero Contributors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package kopia import ( "context" "strings" "time" "github.com/pkg/errors" "github.com/vmware-tanzu/velero/pkg/repository/udmrepo" "github.com/kopia/kopia/repo" "github.com/kopia/kopia/repo/content" "github.com/kopia/kopia/repo/content/index" "github.com/kopia/kopia/repo/manifest" "github.com/kopia/kopia/repo/object" ) // shimRepository which is one adapter for unified repo and kopia. // it implement kopia RepositoryWriter interfaces type shimRepository struct { udmRepo udmrepo.BackupRepo } // shimObjectWriter object writer for unifited repo type shimObjectWriter struct { repoWriter udmrepo.ObjectWriter } // shimObjectReader object reader for unifited repo type shimObjectReader struct { repoReader udmrepo.ObjectReader } func NewShimRepo(repo udmrepo.BackupRepo) repo.RepositoryWriter { return &shimRepository{ udmRepo: repo, } } // OpenObject open specific object func (sr *shimRepository) OpenObject(ctx context.Context, id object.ID) (object.Reader, error) { reader, err := sr.udmRepo.OpenObject(ctx, udmrepo.ID(id.String())) if err != nil { return nil, errors.Wrapf(err, "failed to open object with id %v", id) } if reader == nil { return nil, err } return &shimObjectReader{ repoReader: reader, }, err } // VerifyObject not supported func (sr *shimRepository) VerifyObject(ctx context.Context, id object.ID) ([]content.ID, error) { return nil, errors.New("VerifyObject is not supported") } // Get one or more manifest data that match the specific manifest id func (sr *shimRepository) GetManifest(ctx context.Context, id manifest.ID, payload interface{}) (*manifest.EntryMetadata, error) { repoMani := udmrepo.RepoManifest{ Payload: payload, } if err := sr.udmRepo.GetManifest(ctx, udmrepo.ID(id), &repoMani); err != nil { return nil, errors.Wrapf(err, "failed to get manifest with id %v", id) } return GetKopiaManifestEntry(repoMani.Metadata), nil } // Get one or more manifest data that match the given labels func (sr *shimRepository) FindManifests(ctx context.Context, labels map[string]string) ([]*manifest.EntryMetadata, error) { metadata, err := sr.udmRepo.FindManifests(ctx, udmrepo.ManifestFilter{Labels: labels}) if err != nil { return nil, errors.Wrapf(err, "failed to get manifests with labels %v", labels) } return GetKopiaManifestEntries(metadata), nil } // GetKopiaManifestEntry get metadata from specific ManifestEntryMetadata func GetKopiaManifestEntry(uMani *udmrepo.ManifestEntryMetadata) *manifest.EntryMetadata { var ret manifest.EntryMetadata ret.ID = manifest.ID(uMani.ID) ret.Labels = uMani.Labels ret.Length = int(uMani.Length) ret.ModTime = uMani.ModTime return &ret } // GetKopiaManifestEntries get metadata list from specific ManifestEntryMetadata func GetKopiaManifestEntries(uMani []*udmrepo.ManifestEntryMetadata) []*manifest.EntryMetadata { var ret []*manifest.EntryMetadata for _, entry := range uMani { var e manifest.EntryMetadata e.ID = manifest.ID(entry.ID) e.Labels = entry.Labels e.Length = int(entry.Length) e.ModTime = entry.ModTime ret = append(ret, &e) } return ret } // Time Get the local time of the unified repo func (sr *shimRepository) Time() time.Time { return sr.udmRepo.Time() } // ClientOptions is not supported by unified repo func (sr *shimRepository) ClientOptions() repo.ClientOptions { return repo.ClientOptions{} } // Refresh not supported func (sr *shimRepository) Refresh(ctx context.Context) error { return errors.New("Refresh is not supported") } // ContentInfo not supported func (sr *shimRepository) ContentInfo(ctx context.Context, contentID content.ID) (content.Info, error) { return index.Info{}, errors.New("ContentInfo is not supported") } // PrefetchContents is not supported by unified repo func (sr *shimRepository) PrefetchContents(ctx context.Context, contentIDs []content.ID, hint string) []content.ID { return nil } // PrefetchObjects is not supported by unified repo func (sr *shimRepository) PrefetchObjects(ctx context.Context, objectIDs []object.ID, hint string) ([]content.ID, error) { return nil, errors.New("PrefetchObjects is not supported") } // UpdateDescription is not supported by unified repo func (sr *shimRepository) UpdateDescription(d string) { } // NewWriter is not supported by unified repo func (sr *shimRepository) NewWriter(ctx context.Context, option repo.WriteSessionOptions) (context.Context, repo.RepositoryWriter, error) { return nil, nil, errors.New("NewWriter is not supported") } // Close will close unified repo func (sr *shimRepository) Close(ctx context.Context) error { return sr.udmRepo.Close(ctx) } // NewObjectWriter creates an object writer func (sr *shimRepository) NewObjectWriter(ctx context.Context, option object.WriterOptions) object.Writer { var opt udmrepo.ObjectWriteOptions opt.Description = option.Description opt.Prefix = udmrepo.ID(option.Prefix) opt.FullPath = "" opt.AccessMode = udmrepo.ObjectDataAccessModeFile opt.AsyncWrites = option.AsyncWrites if strings.HasPrefix(option.Description, "DIR:") { opt.DataType = udmrepo.ObjectDataTypeMetadata } else { opt.DataType = udmrepo.ObjectDataTypeData } writer := sr.udmRepo.NewObjectWriter(ctx, opt) if writer == nil { return nil } return &shimObjectWriter{ repoWriter: writer, } } // PutManifest saves the given manifest payload with a set of labels. func (sr *shimRepository) PutManifest(ctx context.Context, labels map[string]string, payload interface{}) (manifest.ID, error) { id, err := sr.udmRepo.PutManifest(ctx, udmrepo.RepoManifest{ Payload: payload, Metadata: &udmrepo.ManifestEntryMetadata{ Labels: labels, }, }) return manifest.ID(id), err } // DeleteManifest deletes the manifest with a given ID. func (sr *shimRepository) DeleteManifest(ctx context.Context, id manifest.ID) error { return sr.udmRepo.DeleteManifest(ctx, udmrepo.ID(id)) } func (sr *shimRepository) ReplaceManifests(ctx context.Context, labels map[string]string, payload interface{}) (manifest.ID, error) { const minReplaceManifestTimeDelta = 100 * time.Millisecond md, err := sr.FindManifests(ctx, labels) if err != nil { return "", errors.Wrap(err, "unable to load manifests") } for _, em := range md { age := sr.Time().Sub(em.ModTime) if age < minReplaceManifestTimeDelta { time.Sleep(minReplaceManifestTimeDelta) } if err := sr.DeleteManifest(ctx, em.ID); err != nil { return "", errors.Wrapf(err, "unable to delete previous manifest %v", em.ID) } } return sr.PutManifest(ctx, labels, payload) } // Flush all the unifited repository data func (sr *shimRepository) Flush(ctx context.Context) error { return sr.udmRepo.Flush(ctx) } func (sr *shimRepository) ConcatenateObjects(ctx context.Context, objectIDs []object.ID) (object.ID, error) { if len(objectIDs) == 0 { return object.EmptyID, errors.New("object list is empty") } ids := []udmrepo.ID{} for _, id := range objectIDs { ids = append(ids, udmrepo.ID(id.String())) } id, err := sr.udmRepo.ConcatenateObjects(ctx, ids) if err != nil { return object.EmptyID, err } return object.ParseID(string(id)) } func (sr *shimRepository) OnSuccessfulFlush(callback repo.RepositoryWriterCallback) { } // Flush all the unifited repository data func (sr *shimObjectReader) Read(p []byte) (n int, err error) { return sr.repoReader.Read(p) } func (sr *shimObjectReader) Seek(offset int64, whence int) (int64, error) { return sr.repoReader.Seek(offset, whence) } // Close current io for ObjectReader func (sr *shimObjectReader) Close() error { return sr.repoReader.Close() } // Length returns the logical size of the object func (sr *shimObjectReader) Length() int64 { return sr.repoReader.Length() } // Write data func (sr *shimObjectWriter) Write(p []byte) (n int, err error) { return sr.repoWriter.Write(p) } // Periodically called to preserve the state of data written to the repo so far. func (sr *shimObjectWriter) Checkpoint() (object.ID, error) { id, err := sr.repoWriter.Checkpoint() if err != nil { return object.ID{}, err } objID, err := object.ParseID(string(id)) if err != nil { return object.ID{}, errors.Wrapf(err, "error to parse object ID from %v", id) } return objID, err } // Result returns the object's unified identifier after the write completes. func (sr *shimObjectWriter) Result() (object.ID, error) { id, err := sr.repoWriter.Result() if err != nil { return object.ID{}, err } objID, err := object.ParseID(string(id)) if err != nil { return object.ID{}, errors.Wrapf(err, "error to parse object ID from %v", id) } return objID, err } // Close closes the repository and releases all resources. func (sr *shimObjectWriter) Close() error { return sr.repoWriter.Close() }