mirror of
https://github.com/psviderski/uncloud.git
synced 2026-08-28 20:13:33 +00:00
feat(distlock): add gRPC Lease server that adapts a machine-local Store
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
package distlock
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/psviderski/uncloud/internal/machine/api/pb"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"google.golang.org/protobuf/types/known/durationpb"
|
||||
)
|
||||
|
||||
// Server adapts a machine-local Store to the Lease gRPC service.
|
||||
type Server struct {
|
||||
pb.UnimplementedLeaseServer
|
||||
store Store
|
||||
}
|
||||
|
||||
// NewServer creates a machine-local lease server.
|
||||
func NewServer(store Store) *Server {
|
||||
return &Server{store: store}
|
||||
}
|
||||
|
||||
// Acquire creates a lease when the resource has no unexpired lease.
|
||||
func (s *Server) Acquire(ctx context.Context, req *pb.AcquireLeaseRequest) (*pb.AcquireLeaseResponse, error) {
|
||||
ttl, err := validateLeaseRequest(req.Resource, req.Token, req.Ttl)
|
||||
if err != nil {
|
||||
return nil, status.Error(codes.InvalidArgument, err.Error())
|
||||
}
|
||||
|
||||
acquired, err := s.store.Acquire(ctx, req.Resource, req.Token, ttl)
|
||||
if err != nil {
|
||||
return nil, storeStatusError(ctx, "acquire lease", err)
|
||||
}
|
||||
return &pb.AcquireLeaseResponse{Acquired: acquired}, nil
|
||||
}
|
||||
|
||||
// Renew extends an unexpired lease when its ownership token matches.
|
||||
func (s *Server) Renew(ctx context.Context, req *pb.RenewLeaseRequest) (*pb.RenewLeaseResponse, error) {
|
||||
ttl, err := validateLeaseRequest(req.Resource, req.Token, req.Ttl)
|
||||
if err != nil {
|
||||
return nil, status.Error(codes.InvalidArgument, err.Error())
|
||||
}
|
||||
|
||||
renewed, err := s.store.Renew(ctx, req.Resource, req.Token, ttl)
|
||||
if err != nil {
|
||||
return nil, storeStatusError(ctx, "renew lease", err)
|
||||
}
|
||||
return &pb.RenewLeaseResponse{Renewed: renewed}, nil
|
||||
}
|
||||
|
||||
// Release removes an unexpired lease when its ownership token matches.
|
||||
func (s *Server) Release(ctx context.Context, req *pb.ReleaseLeaseRequest) (*pb.ReleaseLeaseResponse, error) {
|
||||
if err := validateResourceToken(req.Resource, req.Token); err != nil {
|
||||
return nil, status.Error(codes.InvalidArgument, err.Error())
|
||||
}
|
||||
|
||||
released, err := s.store.Release(ctx, req.Resource, req.Token)
|
||||
if err != nil {
|
||||
return nil, storeStatusError(ctx, "release lease", err)
|
||||
}
|
||||
return &pb.ReleaseLeaseResponse{Released: released}, nil
|
||||
}
|
||||
|
||||
func validateLeaseRequest(resource string, token []byte, ttl *durationpb.Duration) (time.Duration, error) {
|
||||
if err := validateResourceToken(resource, token); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if ttl == nil {
|
||||
return 0, fmt.Errorf("TTL is not set")
|
||||
}
|
||||
if err := ttl.CheckValid(); err != nil {
|
||||
return 0, fmt.Errorf("invalid TTL: %w", err)
|
||||
}
|
||||
duration := ttl.AsDuration()
|
||||
if duration <= 0 {
|
||||
return 0, fmt.Errorf("TTL must be positive")
|
||||
}
|
||||
return duration, nil
|
||||
}
|
||||
|
||||
func validateResourceToken(resource string, token []byte) error {
|
||||
if resource == "" {
|
||||
return fmt.Errorf("resource is empty")
|
||||
}
|
||||
if len(token) == 0 {
|
||||
return fmt.Errorf("token is empty")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func storeStatusError(ctx context.Context, operation string, err error) error {
|
||||
if ctxErr := ctx.Err(); ctxErr != nil {
|
||||
return status.FromContextError(ctxErr).Err()
|
||||
}
|
||||
return status.Error(codes.Internal, fmt.Sprintf("%s: %v", operation, err))
|
||||
}
|
||||
Reference in New Issue
Block a user