Implemented gRPC client and server for snapshot management, enabling remote operations like listing, fetching, and downloading snapshots. Updated interfaces to support the new gRPC implementation and integrated server start functionality into the API.
246 lines
7.7 KiB
Go
246 lines
7.7 KiB
Go
package agate
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"unprism.ru/KRBL/agate/grpc"
|
|
|
|
"unprism.ru/KRBL/agate/store"
|
|
)
|
|
|
|
// AgateOptions defines configuration options for the Agate library.
|
|
type AgateOptions struct {
|
|
// WorkDir is the directory where snapshots will be stored and managed.
|
|
WorkDir string
|
|
|
|
// OpenFunc is called after a snapshot is restored to initialize resources.
|
|
// The parameter is the directory where the snapshot was extracted.
|
|
OpenFunc func(dir string) error
|
|
|
|
// CloseFunc is called before a snapshot is created or restored to clean up resources.
|
|
CloseFunc func() error
|
|
|
|
// MetadataStore is the implementation of the metadata store to use.
|
|
// Use the stores package to initialize the default implementation:
|
|
// metadataStore, err := stores.NewDefaultMetadataStore(metadataDir)
|
|
MetadataStore store.MetadataStore
|
|
|
|
// BlobStore is the implementation of the blob store to use.
|
|
// Use the stores package to initialize the default implementation:
|
|
// blobStore, err := stores.NewDefaultBlobStore(blobsDir)
|
|
BlobStore store.BlobStore
|
|
}
|
|
|
|
// Agate is the main entry point for the snapshot library.
|
|
type Agate struct {
|
|
manager SnapshotManager
|
|
options AgateOptions
|
|
metadataDir string
|
|
blobsDir string
|
|
}
|
|
|
|
// New initializes a new instance of the Agate library with the given options.
|
|
func New(options AgateOptions) (*Agate, error) {
|
|
if options.WorkDir == "" {
|
|
return nil, errors.New("work directory cannot be empty")
|
|
}
|
|
|
|
// Create the work directory if it doesn't exist
|
|
if err := os.MkdirAll(options.WorkDir, 0755); err != nil {
|
|
return nil, fmt.Errorf("failed to create work directory: %w", err)
|
|
}
|
|
|
|
// Create subdirectories for metadata and blobs
|
|
metadataDir := filepath.Join(options.WorkDir, "metadata")
|
|
blobsDir := filepath.Join(options.WorkDir, "blobs")
|
|
|
|
if err := os.MkdirAll(metadataDir, 0755); err != nil {
|
|
return nil, fmt.Errorf("failed to create metadata directory: %w", err)
|
|
}
|
|
|
|
if err := os.MkdirAll(blobsDir, 0755); err != nil {
|
|
return nil, fmt.Errorf("failed to create blobs directory: %w", err)
|
|
}
|
|
|
|
var metadataStore store.MetadataStore
|
|
var blobStore store.BlobStore
|
|
var err error
|
|
|
|
// Use provided stores or initialize default ones
|
|
if options.MetadataStore != nil {
|
|
metadataStore = options.MetadataStore
|
|
} else {
|
|
// For default implementation, the user needs to initialize and provide the stores
|
|
return nil, errors.New("metadata store must be provided")
|
|
}
|
|
|
|
if options.BlobStore != nil {
|
|
blobStore = options.BlobStore
|
|
} else {
|
|
// For default implementation, the user needs to initialize and provide the stores
|
|
return nil, errors.New("blob store must be provided")
|
|
}
|
|
|
|
// Create the snapshot manager
|
|
manager, err := CreateSnapshotManager(metadataStore, blobStore)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create snapshot manager: %w", err)
|
|
}
|
|
|
|
return &Agate{
|
|
manager: manager,
|
|
options: options,
|
|
metadataDir: metadataDir,
|
|
blobsDir: blobsDir,
|
|
}, nil
|
|
}
|
|
|
|
// SaveSnapshot creates a new snapshot from the current state of the work directory.
|
|
// If parentID is provided, it will be set as the parent of the new snapshot.
|
|
// Returns the ID of the created snapshot.
|
|
func (a *Agate) SaveSnapshot(ctx context.Context, name string, parentID string) (string, error) {
|
|
// Call CloseFunc if provided
|
|
if a.options.CloseFunc != nil {
|
|
if err := a.options.CloseFunc(); err != nil {
|
|
return "", fmt.Errorf("failed to close resources before snapshot: %w", err)
|
|
}
|
|
}
|
|
|
|
// Create the snapshot
|
|
snapshot, err := a.manager.CreateSnapshot(ctx, a.options.WorkDir, name, parentID)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to create snapshot: %w", err)
|
|
}
|
|
|
|
// Call OpenFunc if provided
|
|
if a.options.OpenFunc != nil {
|
|
if err := a.options.OpenFunc(a.options.WorkDir); err != nil {
|
|
return "", fmt.Errorf("failed to open resources after snapshot: %w", err)
|
|
}
|
|
}
|
|
|
|
return snapshot.ID, nil
|
|
}
|
|
|
|
// RestoreSnapshot extracts a snapshot to the work directory.
|
|
func (a *Agate) RestoreSnapshot(ctx context.Context, snapshotID string) error {
|
|
// Call CloseFunc if provided
|
|
if a.options.CloseFunc != nil {
|
|
if err := a.options.CloseFunc(); err != nil {
|
|
return fmt.Errorf("failed to close resources before restore: %w", err)
|
|
}
|
|
}
|
|
|
|
// Extract the snapshot
|
|
if err := a.manager.ExtractSnapshot(ctx, snapshotID, a.options.WorkDir); err != nil {
|
|
return fmt.Errorf("failed to extract snapshot: %w", err)
|
|
}
|
|
|
|
// Call OpenFunc if provided
|
|
if a.options.OpenFunc != nil {
|
|
if err := a.options.OpenFunc(a.options.WorkDir); err != nil {
|
|
return fmt.Errorf("failed to open resources after restore: %w", err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ListSnapshots returns a list of all available snapshots.
|
|
func (a *Agate) ListSnapshots(ctx context.Context) ([]store.SnapshotInfo, error) {
|
|
return a.manager.ListSnapshots(ctx)
|
|
}
|
|
|
|
// GetSnapshotDetails returns detailed information about a specific snapshot.
|
|
func (a *Agate) GetSnapshotDetails(ctx context.Context, snapshotID string) (*store.Snapshot, error) {
|
|
return a.manager.GetSnapshotDetails(ctx, snapshotID)
|
|
}
|
|
|
|
// DeleteSnapshot removes a snapshot.
|
|
func (a *Agate) DeleteSnapshot(ctx context.Context, snapshotID string) error {
|
|
return a.manager.DeleteSnapshot(ctx, snapshotID)
|
|
}
|
|
|
|
// Close releases all resources used by the Agate instance.
|
|
func (a *Agate) Close() error {
|
|
// Currently, we don't have a way to close the manager directly
|
|
// This would be a good addition in the future
|
|
return nil
|
|
}
|
|
|
|
// StartServer starts a gRPC server to share snapshots.
|
|
func (a *Agate) StartServer(ctx context.Context, address string) error {
|
|
_, err := grpc.RunServer(ctx, a.manager, address)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to start server: %w", err)
|
|
}
|
|
|
|
// We don't store the server reference because we don't have a way to stop it yet
|
|
// In a future version, we could add a StopServer method
|
|
|
|
return nil
|
|
}
|
|
|
|
// ConnectRemote connects to a remote snapshot server.
|
|
// Returns a client that can be used to interact with the remote server.
|
|
func (a *Agate) ConnectRemote(address string) (*grpc.SnapshotClient, error) {
|
|
client, err := grpc.ConnectToServer(address)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to connect to remote server: %w", err)
|
|
}
|
|
|
|
return client, nil
|
|
}
|
|
|
|
// GetRemoteSnapshotList retrieves a list of snapshots from a remote server.
|
|
func (a *Agate) GetRemoteSnapshotList(ctx context.Context, address string) ([]store.SnapshotInfo, error) {
|
|
client, err := a.ConnectRemote(address)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer client.Close()
|
|
|
|
return client.ListSnapshots(ctx)
|
|
}
|
|
|
|
// GetRemoteSnapshot downloads a snapshot from a remote server.
|
|
// If localParentID is provided, it will be used to optimize the download by skipping files that already exist locally.
|
|
func (a *Agate) GetRemoteSnapshot(ctx context.Context, address string, snapshotID string, localParentID string) error {
|
|
client, err := a.ConnectRemote(address)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer client.Close()
|
|
|
|
// Create a temporary directory for the downloaded snapshot
|
|
tempDir := filepath.Join(a.options.WorkDir, "temp", snapshotID)
|
|
if err := os.MkdirAll(tempDir, 0755); err != nil {
|
|
return fmt.Errorf("failed to create temporary directory: %w", err)
|
|
}
|
|
|
|
// Download the snapshot
|
|
if err := client.DownloadSnapshot(ctx, snapshotID, tempDir, localParentID); err != nil {
|
|
return fmt.Errorf("failed to download snapshot: %w", err)
|
|
}
|
|
|
|
// Get the snapshot details to create a local copy
|
|
details, err := client.FetchSnapshotDetails(ctx, snapshotID)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get snapshot details: %w", err)
|
|
}
|
|
|
|
// Create a local snapshot from the downloaded files
|
|
_, err = a.manager.CreateSnapshot(ctx, tempDir, details.Name, localParentID)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create local snapshot: %w", err)
|
|
}
|
|
|
|
// Clean up the temporary directory
|
|
os.RemoveAll(tempDir)
|
|
|
|
return nil
|
|
}
|