All checks were successful
Go Tests / Run Tests (1.24.2) (pull_request) Successful in 9m49s
This commit implements the replication transport layer as part of Phase 2 of the replication plan. Key components include: - Add protocol buffer definitions for replication services - Implement WALReplicator extension for processor management - Create replication service server implementation - Add replication client and server transport implementations - Implement storage snapshot interface for bootstrap operations - Standardize package naming across replication components
200 lines
6.0 KiB
Go
200 lines
6.0 KiB
Go
package transport
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"sync"
|
|
|
|
"github.com/KevoDB/kevo/pkg/grpc/service"
|
|
"github.com/KevoDB/kevo/pkg/replication"
|
|
"github.com/KevoDB/kevo/pkg/transport"
|
|
)
|
|
|
|
// ReplicationGRPCServer implements the ReplicationServer interface using gRPC
|
|
type ReplicationGRPCServer struct {
|
|
transportManager *GRPCTransportManager
|
|
replicationService *service.ReplicationServiceServer
|
|
options transport.TransportOptions
|
|
replicas map[string]*transport.ReplicaInfo
|
|
mu sync.RWMutex
|
|
}
|
|
|
|
// NewReplicationGRPCServer creates a new ReplicationGRPCServer
|
|
func NewReplicationGRPCServer(
|
|
transportManager *GRPCTransportManager,
|
|
replicator *replication.WALReplicator,
|
|
applier *replication.WALApplier,
|
|
serializer *replication.EntrySerializer,
|
|
storageSnapshot replication.StorageSnapshot,
|
|
options transport.TransportOptions,
|
|
) (*ReplicationGRPCServer, error) {
|
|
// Create replication service
|
|
replicationService := service.NewReplicationService(
|
|
replicator,
|
|
applier,
|
|
serializer,
|
|
storageSnapshot,
|
|
)
|
|
|
|
return &ReplicationGRPCServer{
|
|
transportManager: transportManager,
|
|
replicationService: replicationService,
|
|
options: options,
|
|
replicas: make(map[string]*transport.ReplicaInfo),
|
|
}, nil
|
|
}
|
|
|
|
// Start starts the server and returns immediately
|
|
func (s *ReplicationGRPCServer) Start() error {
|
|
// Register the replication service with the transport manager
|
|
if err := s.transportManager.RegisterService(s.replicationService); err != nil {
|
|
return fmt.Errorf("failed to register replication service: %w", err)
|
|
}
|
|
|
|
// Start the transport manager if it's not already started
|
|
if err := s.transportManager.Start(); err != nil {
|
|
return fmt.Errorf("failed to start transport manager: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Serve starts the server and blocks until it's stopped
|
|
func (s *ReplicationGRPCServer) Serve() error {
|
|
if err := s.Start(); err != nil {
|
|
return err
|
|
}
|
|
|
|
// This will block until the context is cancelled
|
|
<-context.Background().Done()
|
|
return nil
|
|
}
|
|
|
|
// Stop stops the server gracefully
|
|
func (s *ReplicationGRPCServer) Stop(ctx context.Context) error {
|
|
return s.transportManager.Stop(ctx)
|
|
}
|
|
|
|
// SetRequestHandler sets the handler for incoming requests
|
|
// Not used for the replication server as it uses a dedicated service
|
|
func (s *ReplicationGRPCServer) SetRequestHandler(handler transport.RequestHandler) {
|
|
// No-op for replication server
|
|
}
|
|
|
|
// RegisterReplica registers a new replica
|
|
func (s *ReplicationGRPCServer) RegisterReplica(replicaInfo *transport.ReplicaInfo) error {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
s.replicas[replicaInfo.ID] = replicaInfo
|
|
return nil
|
|
}
|
|
|
|
// UpdateReplicaStatus updates the status of a replica
|
|
func (s *ReplicationGRPCServer) UpdateReplicaStatus(replicaID string, status transport.ReplicaStatus, lsn uint64) error {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
replica, exists := s.replicas[replicaID]
|
|
if !exists {
|
|
return fmt.Errorf("replica not found: %s", replicaID)
|
|
}
|
|
|
|
replica.Status = status
|
|
replica.CurrentLSN = lsn
|
|
return nil
|
|
}
|
|
|
|
// GetReplicaInfo returns information about a specific replica
|
|
func (s *ReplicationGRPCServer) GetReplicaInfo(replicaID string) (*transport.ReplicaInfo, error) {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
|
|
replica, exists := s.replicas[replicaID]
|
|
if !exists {
|
|
return nil, fmt.Errorf("replica not found: %s", replicaID)
|
|
}
|
|
|
|
return replica, nil
|
|
}
|
|
|
|
// ListReplicas returns information about all connected replicas
|
|
func (s *ReplicationGRPCServer) ListReplicas() ([]*transport.ReplicaInfo, error) {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
|
|
result := make([]*transport.ReplicaInfo, 0, len(s.replicas))
|
|
for _, replica := range s.replicas {
|
|
result = append(result, replica)
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// StreamWALEntriesToReplica streams WAL entries to a specific replica
|
|
func (s *ReplicationGRPCServer) StreamWALEntriesToReplica(ctx context.Context, replicaID string, fromLSN uint64) error {
|
|
// This is handled by the gRPC service directly
|
|
return nil
|
|
}
|
|
|
|
// RegisterReplicationTransport registers the gRPC replication transport with the registry
|
|
func init() {
|
|
// Register replication server factory
|
|
transport.RegisterReplicationServerTransport("grpc", func(address string, options transport.TransportOptions) (transport.ReplicationServer, error) {
|
|
// Create gRPC transport manager
|
|
grpcOptions := &GRPCTransportOptions{
|
|
ListenAddr: address,
|
|
ConnectionTimeout: options.Timeout,
|
|
DialTimeout: options.Timeout,
|
|
}
|
|
|
|
// Add TLS configuration if enabled
|
|
if options.TLSEnabled {
|
|
tlsConfig, err := LoadServerTLSConfig(options.CertFile, options.KeyFile, options.CAFile)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to load TLS config: %w", err)
|
|
}
|
|
grpcOptions.TLSConfig = tlsConfig
|
|
}
|
|
|
|
// Create transport manager
|
|
manager, err := NewGRPCTransportManager(grpcOptions)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create gRPC transport manager: %w", err)
|
|
}
|
|
|
|
// For registration, we return a placeholder that will be properly initialized
|
|
// by the caller with the required components
|
|
return &ReplicationGRPCServer{
|
|
transportManager: manager,
|
|
options: options,
|
|
replicas: make(map[string]*transport.ReplicaInfo),
|
|
}, nil
|
|
})
|
|
|
|
// Register replication client factory
|
|
transport.RegisterReplicationClientTransport("grpc", func(endpoint string, options transport.TransportOptions) (transport.ReplicationClient, error) {
|
|
// For registration, we return a placeholder that will be properly initialized
|
|
// by the caller with the required components
|
|
return &ReplicationGRPCClient{
|
|
endpoint: endpoint,
|
|
options: options,
|
|
}, nil
|
|
})
|
|
}
|
|
|
|
// WithReplicator adds a replicator to the replication server
|
|
func (s *ReplicationGRPCServer) WithReplicator(
|
|
replicator *replication.WALReplicator,
|
|
applier *replication.WALApplier,
|
|
serializer *replication.EntrySerializer,
|
|
storageSnapshot replication.StorageSnapshot,
|
|
) *ReplicationGRPCServer {
|
|
s.replicationService = service.NewReplicationService(
|
|
replicator,
|
|
applier,
|
|
serializer,
|
|
storageSnapshot,
|
|
)
|
|
return s
|
|
} |