-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request juju#16613 from SimonRichardson/objectstore-state
juju#16613 Implementation of object store domain state layer. The concept of the state layer ensures that you can have multiple paths pointing to the same hash. As long as the metadata hash and size are the same on insertion then you can add them. As we're going to be storing the hash as the file path for some of the backend, they need to be unique. It's possible we could use an alias instead of the hash to make it readable, but we can do that later if required. I had to change the schema to push the size into the metadata as it's possible, but unlikely that a size could be different for a hash. In that scenario, the hash function is broken and we can't accept the metadata. ## Checklist - [x] Code style: imports ordered, good names, simple structure, etc - [x] Comments saying why design decisions were made - [x] Go unit tests, with comments saying what you're testing ## QA steps ```sh $ TEST_PACKAGES="./domain/objectstore/..." make run-go-tests ``` ## Links **Jira card:** JUJU-4971
- Loading branch information
Showing
18 changed files
with
1,014 additions
and
21 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
// Copyright 2023 Canonical Ltd. | ||
// Licensed under the AGPLv3, see LICENCE file for details. | ||
|
||
package objectstore | ||
|
||
// Metadata represents the metadata for an object. | ||
type Metadata struct { | ||
// Hash is the hash of the object. | ||
Hash string | ||
// Path is the path to the object. | ||
Path string | ||
// Size is the size of the object. | ||
Size int64 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
// Copyright 2023 Canonical Ltd. | ||
// Licensed under the AGPLv3, see LICENCE file for details. | ||
|
||
package objectstore | ||
|
||
import "github.com/juju/errors" | ||
|
||
// ErrHashAndSizeAlreadyExists is returned when a hash already exists, but | ||
// the associated size is different. This should never happen, it means that | ||
// there is a collision in the hash function. | ||
const ErrHashAndSizeAlreadyExists = errors.ConstError("hash exists for different file size") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
// Copyright 2023 Canonical Ltd. | ||
// Licensed under the AGPLv3, see LICENCE file for details. | ||
|
||
package objectstore | ||
|
||
import ( | ||
"testing" | ||
|
||
gc "gopkg.in/check.v1" | ||
) | ||
|
||
func TestPackage(t *testing.T) { | ||
gc.TestingT(t) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
// Copyright 2023 Canonical Ltd. | ||
// Licensed under the AGPLv3, see LICENCE file for details. | ||
|
||
package service | ||
|
||
import ( | ||
"testing" | ||
|
||
gc "gopkg.in/check.v1" | ||
) | ||
|
||
//go:generate go run go.uber.org/mock/mockgen -package service -destination state_mock_test.go github.com/juju/juju/domain/objectstore/service State,WatcherFactory | ||
|
||
func TestPackage(t *testing.T) { | ||
gc.TestingT(t) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
// Copyright 2023 Canonical Ltd. | ||
// Licensed under the AGPLv3, see LICENCE file for details. | ||
|
||
package service | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
|
||
"github.com/juju/utils/v3" | ||
|
||
"github.com/juju/juju/core/changestream" | ||
coreobjectstore "github.com/juju/juju/core/objectstore" | ||
"github.com/juju/juju/core/watcher" | ||
"github.com/juju/juju/domain" | ||
"github.com/juju/juju/domain/objectstore" | ||
) | ||
|
||
// State describes retrieval and persistence methods for the coreobjectstore. | ||
type State interface { | ||
// GetMetadata returns the persistence metadata for the specified path. | ||
GetMetadata(ctx context.Context, path string) (objectstore.Metadata, error) | ||
// PutMetadata adds a new specified path for the persistence metadata. | ||
PutMetadata(ctx context.Context, metadata objectstore.Metadata) error | ||
// RemoveMetadata removes the specified path for the persistence metadata. | ||
RemoveMetadata(ctx context.Context, path string) error | ||
// InitialWatchStatement returns the table and the initial watch statement | ||
// for the persistence metadata. | ||
InitialWatchStatement() (string, string) | ||
} | ||
|
||
// WatcherFactory describes methods for creating watchers. | ||
type WatcherFactory interface { | ||
// NewNamespaceWatcher returns a new namespace watcher | ||
// for events based on the input change mask. | ||
NewNamespaceWatcher(string, changestream.ChangeType, string) (watcher.StringsWatcher, error) | ||
} | ||
|
||
// Service provides the API for working with the coreobjectstore. | ||
type Service struct { | ||
st State | ||
watcherFactory WatcherFactory | ||
} | ||
|
||
// NewService returns a new service reference wrapping the input state. | ||
func NewService(st State, watcherFactory WatcherFactory) *Service { | ||
return &Service{ | ||
st: st, | ||
watcherFactory: watcherFactory, | ||
} | ||
} | ||
|
||
// GetMetadata returns the persistence metadata for the specified path. | ||
func (s *Service) GetMetadata(ctx context.Context, path string) (coreobjectstore.Metadata, error) { | ||
metadata, err := s.st.GetMetadata(ctx, path) | ||
if err != nil { | ||
return coreobjectstore.Metadata{}, fmt.Errorf("retrieving metadata %s: %w", path, domain.CoerceError(err)) | ||
} | ||
return coreobjectstore.Metadata{ | ||
Path: metadata.Path, | ||
Hash: metadata.Hash, | ||
Size: metadata.Size, | ||
}, nil | ||
} | ||
|
||
// PutMetadata adds a new specified path for the persistence metadata. | ||
func (s *Service) PutMetadata(ctx context.Context, metadata coreobjectstore.Metadata) error { | ||
uuid, err := utils.NewUUID() | ||
if err != nil { | ||
return err | ||
} | ||
|
||
err = s.st.PutMetadata(ctx, objectstore.Metadata{ | ||
UUID: uuid.String(), | ||
Hash: metadata.Hash, | ||
Path: metadata.Path, | ||
Size: metadata.Size, | ||
}) | ||
if err != nil { | ||
return fmt.Errorf("adding path %s: %w", metadata.Path, err) | ||
} | ||
return nil | ||
} | ||
|
||
// RemoveMetadata removes the specified path for the persistence metadata. | ||
func (s *Service) RemoveMetadata(ctx context.Context, path string) error { | ||
err := s.st.RemoveMetadata(ctx, path) | ||
if err != nil { | ||
return fmt.Errorf("removing path %s: %w", path, err) | ||
} | ||
return nil | ||
} | ||
|
||
// Watch returns a watcher that emits the path changes that either have been | ||
// added or removed. | ||
func (s *Service) Watch() (watcher.StringsWatcher, error) { | ||
table, stmt := s.st.InitialWatchStatement() | ||
return s.watcherFactory.NewNamespaceWatcher( | ||
table, | ||
changestream.All, | ||
stmt, | ||
) | ||
} |
Oops, something went wrong.