registry/storage/locks.go (158 lines of code) (raw):

package storage import ( "context" "encoding/json" "errors" "fmt" "github.com/docker/distribution" "github.com/docker/distribution/log" "github.com/docker/distribution/registry/storage/driver" ) // Locker provides access to information about storage locks. type Locker interface { // IsLocked returns true if the locker is locked. IsLocked(ctx context.Context) (bool, error) // Lock applies the lock. Lock(ctx context.Context) error // Unlock removes a lock. Unlock(ctx context.Context) error } // lockers hold a database and filesystem lockers // and implements the distribution.Lockers interface type lockers struct { DB Locker FS Locker } // versionedLock exists to fill the lock file with content to avoid an empty // file which could have possible storage driver implementation differences. // Using a version allows us the option to fill the lock file with data in the // future, while maintaining compatibility for historic locks. type versionedLock struct { // Version is the lock version. Version int `json:"version"` } // DatabaseInUseLocker is a locker that signals that this object storage is // managed by the metadata database. type DatabaseInUseLocker struct { Driver driver.StorageDriver } // IsLocked returns true if the lock file is present. func (l *DatabaseInUseLocker) IsLocked(ctx context.Context) (bool, error) { path, err := l.path() if err != nil { return false, err } fi, err := l.Driver.Stat(ctx, path) if err != nil { if errors.As(err, &driver.PathNotFoundError{}) { return false, nil } return false, err } if fi.IsDir() { log.GetLogger(log.WithContext(ctx)).WithFields(log.Fields{ "path": path, "lock": "databaseInUse", }).Warn("lock path should not be a directory") return false, err } return true, nil } // Lock applies the lock by writing a lock file. func (l *DatabaseInUseLocker) Lock(ctx context.Context) error { locked, err := l.IsLocked(ctx) if err != nil { return err } if locked { return nil } path, err := l.path() if err != nil { return err } vl := versionedLock{Version: 1} b, err := json.Marshal(&vl) if err != nil { return fmt.Errorf("marshaling lockfile json") } return l.Driver.PutContent(ctx, path, b) } // Unlock removes a lock by removing a lock file. This method is idempotent. func (l *DatabaseInUseLocker) Unlock(ctx context.Context) error { locked, err := l.IsLocked(ctx) if err != nil { return err } if !locked { return nil } path, err := l.path() if err != nil { return err } return l.Driver.Delete(ctx, path) } func (*DatabaseInUseLocker) path() (string, error) { return pathFor(lockFilePathSpec{name: "database-in-use"}) } // FilesystemInUseLocker is a locker that signals that this object storage is // managed by the filesystem metadata. type FilesystemInUseLocker struct { Driver driver.StorageDriver } // IsLocked returns true if the lock file is present. func (l *FilesystemInUseLocker) IsLocked(ctx context.Context) (bool, error) { path, err := l.path() if err != nil { return false, err } fi, err := l.Driver.Stat(ctx, path) if err != nil { if errors.As(err, &driver.PathNotFoundError{}) { return false, nil } return false, err } if fi.IsDir() { log.GetLogger(log.WithContext(ctx)).WithFields(log.Fields{ "path": path, "lock": "filesystemInUse", }).Warn("lock path should not be a directory") return false, err } return true, nil } // Lock applies the lock by writing a lock file. func (l *FilesystemInUseLocker) Lock(ctx context.Context) error { locked, err := l.IsLocked(ctx) if err != nil { return err } if locked { return nil } path, err := l.path() if err != nil { return err } vl := versionedLock{Version: 1} b, err := json.Marshal(&vl) if err != nil { return fmt.Errorf("marshaling lockfile json") } return l.Driver.PutContent(ctx, path, b) } // Unlock removes a lock by removing a lock file. This method is idempotent. func (l *FilesystemInUseLocker) Unlock(ctx context.Context) error { locked, err := l.IsLocked(ctx) if err != nil { return err } if !locked { return nil } path, err := l.path() if err != nil { return err } return l.Driver.Delete(ctx, path) } func (*FilesystemInUseLocker) path() (string, error) { return pathFor(lockFilePathSpec{name: "filesystem-in-use"}) } var _ distribution.Lockers = &lockers{} func (l *lockers) DBLock(ctx context.Context) error { return l.DB.Lock(ctx) } func (l *lockers) DBUnlock(ctx context.Context) error { return l.DB.Unlock(ctx) } func (l *lockers) DBIsLocked(ctx context.Context) (bool, error) { return l.DB.IsLocked(ctx) } func (l *lockers) FSLock(ctx context.Context) error { return l.FS.Lock(ctx) } func (l *lockers) FSUnlock(ctx context.Context) error { return l.FS.Unlock(ctx) }