package blobovniczatree import ( "context" "errors" "os" "path/filepath" "strings" "git.frostfs.info/TrueCloudLab/frostfs-node/internal/logs" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util" "go.uber.org/zap" "golang.org/x/sync/errgroup" ) var errFailedToChangeExtensionReadOnly = errors.New("failed to change blobovnicza extension: read only mode") // Open opens blobovnicza tree. func (b *Blobovniczas) Open(readOnly bool) error { b.readOnly = readOnly b.metrics.SetMode(readOnly) b.openManagers() return nil } // Init initializes blobovnicza tree. // // Should be called exactly once. func (b *Blobovniczas) Init() error { b.log.Debug(logs.BlobovniczatreeInitializingBlobovniczas) b.log.Debug(logs.BlobovniczaTreeFixingFileExtensions) if err := b.addDBExtensionToDBs(b.rootPath, 0); err != nil { b.log.Error(logs.BlobovniczaTreeFixingFileExtensionsFailed, zap.Error(err)) return err } b.log.Debug(logs.BlobovniczaTreeFixingFileExtensionsCompletedSuccessfully) if b.readOnly { b.log.Debug(logs.BlobovniczatreeReadonlyModeSkipBlobovniczasInitialization) return nil } return b.initializeDBs(context.TODO()) } func (b *Blobovniczas) initializeDBs(ctx context.Context) error { err := util.MkdirAllX(b.rootPath, b.perm) if err != nil { return err } eg, egCtx := errgroup.WithContext(ctx) eg.SetLimit(b.blzInitWorkerCount) visited := make(map[string]struct{}) err = b.iterateExistingDBPaths(egCtx, func(p string) (bool, error) { visited[p] = struct{}{} eg.Go(func() error { shBlz := b.getBlobovniczaWithoutCaching(p) _, err := shBlz.Open() if err != nil { return err } defer shBlz.Close() b.log.Debug(logs.BlobovniczatreeBlobovniczaSuccessfullyInitializedClosing, zap.String("id", p)) return nil }) return false, nil }) if err != nil { _ = eg.Wait() return err } err = b.iterateSortedLeaves(egCtx, nil, func(p string) (bool, error) { if _, found := visited[p]; found { return false, nil } eg.Go(func() error { shBlz := b.getBlobovniczaWithoutCaching(p) _, err := shBlz.Open() if err != nil { return err } defer shBlz.Close() b.log.Debug(logs.BlobovniczatreeBlobovniczaSuccessfullyInitializedClosing, zap.String("id", p)) return nil }) return false, nil }) if err != nil { _ = eg.Wait() return err } return eg.Wait() } func (b *Blobovniczas) openManagers() { b.commondbManager.Open() // order important b.activeDBManager.Open() b.dbCache.Open() } // Close implements common.Storage. func (b *Blobovniczas) Close() error { b.dbCache.Close() // order important b.activeDBManager.Close() b.commondbManager.Close() return nil } // returns blobovnicza with path p // // If blobovnicza is already cached, instance from cache is returned w/o changes. func (b *Blobovniczas) getBlobovnicza(p string) *sharedDB { return b.dbCache.GetOrCreate(p) } func (b *Blobovniczas) getBlobovniczaWithoutCaching(p string) *sharedDB { return b.commondbManager.GetByPath(p) } func (b *Blobovniczas) addDBExtensionToDBs(path string, depth uint64) error { entries, err := os.ReadDir(path) if os.IsNotExist(err) && depth == 0 { return nil } for _, entry := range entries { if entry.IsDir() { if err := b.addDBExtensionToDBs(filepath.Join(path, entry.Name()), depth+1); err != nil { return err } continue } if strings.HasSuffix(entry.Name(), dbExtension) { continue } if b.readOnly { return errFailedToChangeExtensionReadOnly } sourcePath := filepath.Join(path, entry.Name()) targetPath := filepath.Join(path, entry.Name()+dbExtension) b.log.Debug(logs.BlobovniczaTreeFixingFileExtensionForFile, zap.String("source", sourcePath), zap.String("target", targetPath)) if err := os.Rename(sourcePath, targetPath); err != nil { b.log.Error(logs.BlobovniczaTreeFixingFileExtensionFailed, zap.String("source", sourcePath), zap.String("target", targetPath), zap.Error(err)) return err } b.log.Debug(logs.BlobovniczaTreeFixingFileExtensionCompletedSuccessfully, zap.String("source", sourcePath), zap.String("target", targetPath)) } return nil }