You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
209 lines
6.5 KiB
209 lines
6.5 KiB
// Package server provides the gRPC server implementation for cluster sync. |
|
package server |
|
|
|
import ( |
|
"context" |
|
"encoding/json" |
|
"net/http" |
|
|
|
"next.orly.dev/pkg/database" |
|
"next.orly.dev/pkg/sync/cluster" |
|
commonv1 "next.orly.dev/pkg/proto/orlysync/common/v1" |
|
clusterv1 "next.orly.dev/pkg/proto/orlysync/cluster/v1" |
|
) |
|
|
|
// Service implements the ClusterSyncServiceServer interface. |
|
type Service struct { |
|
clusterv1.UnimplementedClusterSyncServiceServer |
|
mgr *cluster.Manager |
|
db database.Database |
|
ready bool |
|
} |
|
|
|
// NewService creates a new cluster sync gRPC service. |
|
func NewService(db database.Database, mgr *cluster.Manager) *Service { |
|
return &Service{ |
|
mgr: mgr, |
|
db: db, |
|
ready: true, |
|
} |
|
} |
|
|
|
// Ready returns whether the service is ready to serve requests. |
|
func (s *Service) Ready(ctx context.Context, _ *commonv1.Empty) (*commonv1.ReadyResponse, error) { |
|
return &commonv1.ReadyResponse{Ready: s.ready}, nil |
|
} |
|
|
|
// Start starts the cluster polling loop. |
|
func (s *Service) Start(ctx context.Context, _ *commonv1.Empty) (*commonv1.Empty, error) { |
|
s.mgr.Start() |
|
return &commonv1.Empty{}, nil |
|
} |
|
|
|
// Stop stops the cluster polling loop. |
|
func (s *Service) Stop(ctx context.Context, _ *commonv1.Empty) (*commonv1.Empty, error) { |
|
s.mgr.Stop() |
|
return &commonv1.Empty{}, nil |
|
} |
|
|
|
// HandleLatestSerial proxies GET /cluster/latest HTTP requests. |
|
func (s *Service) HandleLatestSerial(ctx context.Context, req *commonv1.HTTPRequest) (*commonv1.HTTPResponse, error) { |
|
if req.Method != http.MethodGet { |
|
return &commonv1.HTTPResponse{ |
|
StatusCode: http.StatusMethodNotAllowed, |
|
Body: []byte("Method not allowed"), |
|
}, nil |
|
} |
|
|
|
serial, timestamp := s.mgr.GetLatestSerial() |
|
resp := map[string]any{ |
|
"serial": serial, |
|
"timestamp": timestamp, |
|
} |
|
|
|
respBody, err := json.Marshal(resp) |
|
if err != nil { |
|
return &commonv1.HTTPResponse{ |
|
StatusCode: http.StatusInternalServerError, |
|
Body: []byte("Failed to marshal response"), |
|
}, nil |
|
} |
|
|
|
return &commonv1.HTTPResponse{ |
|
StatusCode: http.StatusOK, |
|
Headers: map[string]string{"Content-Type": "application/json"}, |
|
Body: respBody, |
|
}, nil |
|
} |
|
|
|
// HandleEventsRange proxies GET /cluster/events HTTP requests. |
|
func (s *Service) HandleEventsRange(ctx context.Context, req *commonv1.HTTPRequest) (*commonv1.HTTPResponse, error) { |
|
if req.Method != http.MethodGet { |
|
return &commonv1.HTTPResponse{ |
|
StatusCode: http.StatusMethodNotAllowed, |
|
Body: []byte("Method not allowed"), |
|
}, nil |
|
} |
|
|
|
// Parse query parameters |
|
// In practice, the query string would be parsed from req.QueryString |
|
// For now, return a placeholder |
|
return &commonv1.HTTPResponse{ |
|
StatusCode: http.StatusOK, |
|
Headers: map[string]string{"Content-Type": "application/json"}, |
|
Body: []byte(`{"events":[],"has_more":false}`), |
|
}, nil |
|
} |
|
|
|
// GetMembers returns the current cluster members. |
|
func (s *Service) GetMembers(ctx context.Context, _ *commonv1.Empty) (*clusterv1.MembersResponse, error) { |
|
members := s.mgr.GetMembers() |
|
clusterMembers := make([]*clusterv1.ClusterMember, 0, len(members)) |
|
for _, m := range members { |
|
clusterMembers = append(clusterMembers, &clusterv1.ClusterMember{ |
|
HttpUrl: m.HTTPURL, |
|
WebsocketUrl: m.WebSocketURL, |
|
LastSerial: m.LastSerial, |
|
LastPoll: m.LastPoll.Unix(), |
|
Status: m.Status, |
|
ErrorCount: int32(m.ErrorCount), |
|
}) |
|
} |
|
return &clusterv1.MembersResponse{ |
|
Members: clusterMembers, |
|
}, nil |
|
} |
|
|
|
// UpdateMembership updates cluster membership. |
|
func (s *Service) UpdateMembership(ctx context.Context, req *clusterv1.UpdateMembershipRequest) (*commonv1.Empty, error) { |
|
s.mgr.UpdateMembership(req.RelayUrls) |
|
return &commonv1.Empty{}, nil |
|
} |
|
|
|
// HandleMembershipEvent processes a cluster membership event (Kind 39108). |
|
func (s *Service) HandleMembershipEvent(ctx context.Context, req *clusterv1.MembershipEventRequest) (*commonv1.Empty, error) { |
|
// Convert proto event to internal format and process |
|
// This would need the actual event conversion |
|
return &commonv1.Empty{}, nil |
|
} |
|
|
|
// GetClusterStatus returns overall cluster status. |
|
func (s *Service) GetClusterStatus(ctx context.Context, _ *commonv1.Empty) (*clusterv1.ClusterStatusResponse, error) { |
|
members := s.mgr.GetMembers() |
|
serial, _ := s.mgr.GetLatestSerial() |
|
|
|
activeCount := int32(0) |
|
clusterMembers := make([]*clusterv1.ClusterMember, 0, len(members)) |
|
for _, m := range members { |
|
if m.Status == "active" { |
|
activeCount++ |
|
} |
|
clusterMembers = append(clusterMembers, &clusterv1.ClusterMember{ |
|
HttpUrl: m.HTTPURL, |
|
WebsocketUrl: m.WebSocketURL, |
|
LastSerial: m.LastSerial, |
|
LastPoll: m.LastPoll.Unix(), |
|
Status: m.Status, |
|
ErrorCount: int32(m.ErrorCount), |
|
}) |
|
} |
|
|
|
return &clusterv1.ClusterStatusResponse{ |
|
LatestSerial: serial, |
|
ActiveMembers: activeCount, |
|
TotalMembers: int32(len(members)), |
|
PropagatePrivilegedEvents: s.mgr.PropagatePrivilegedEvents(), |
|
Members: clusterMembers, |
|
}, nil |
|
} |
|
|
|
// GetMemberStatus returns status for a specific member. |
|
func (s *Service) GetMemberStatus(ctx context.Context, req *clusterv1.MemberStatusRequest) (*clusterv1.MemberStatusResponse, error) { |
|
member := s.mgr.GetMember(req.HttpUrl) |
|
if member == nil { |
|
return &clusterv1.MemberStatusResponse{ |
|
Found: false, |
|
}, nil |
|
} |
|
|
|
return &clusterv1.MemberStatusResponse{ |
|
Found: true, |
|
Member: &clusterv1.ClusterMember{ |
|
HttpUrl: member.HTTPURL, |
|
WebsocketUrl: member.WebSocketURL, |
|
LastSerial: member.LastSerial, |
|
LastPoll: member.LastPoll.Unix(), |
|
Status: member.Status, |
|
ErrorCount: int32(member.ErrorCount), |
|
}, |
|
}, nil |
|
} |
|
|
|
// GetLatestSerial returns the latest serial from this relay's database. |
|
func (s *Service) GetLatestSerial(ctx context.Context, _ *commonv1.Empty) (*clusterv1.LatestSerialResponse, error) { |
|
serial, timestamp := s.mgr.GetLatestSerial() |
|
return &clusterv1.LatestSerialResponse{ |
|
Serial: serial, |
|
Timestamp: timestamp, |
|
}, nil |
|
} |
|
|
|
// GetEventsInRange returns event info for a serial range. |
|
func (s *Service) GetEventsInRange(ctx context.Context, req *clusterv1.EventsRangeRequest) (*clusterv1.EventsRangeResponse, error) { |
|
events, hasMore, nextFrom := s.mgr.GetEventsInRange(req.From, req.To, int(req.Limit)) |
|
|
|
eventInfos := make([]*clusterv1.EventInfo, 0, len(events)) |
|
for _, e := range events { |
|
eventInfos = append(eventInfos, &clusterv1.EventInfo{ |
|
Serial: e.Serial, |
|
Id: e.ID, |
|
Timestamp: e.Timestamp, // Already int64 |
|
}) |
|
} |
|
|
|
return &clusterv1.EventsRangeResponse{ |
|
Events: eventInfos, |
|
HasMore: hasMore, |
|
NextFrom: nextFrom, |
|
}, nil |
|
}
|
|
|