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.
150 lines
4.1 KiB
150 lines
4.1 KiB
|
2 months ago
|
package directstream
|
||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
"errors"
|
||
|
|
"strings"
|
||
|
|
"sync"
|
||
|
|
)
|
||
|
|
|
||
|
|
type ticketEndpoint interface {
|
||
|
|
TicketConsumer
|
||
|
|
TicketLifecycle
|
||
|
|
Status(context.Context) (TicketServiceSnapshot, error)
|
||
|
|
}
|
||
|
|
|
||
|
|
// TicketSocketPool lets one tera-sessiond broker accept Direct Mode tickets
|
||
|
|
// issued by more than one tera-api process. Each consumed binding is pinned to
|
||
|
|
// the authority that accepted it so heartbeats, events, writer release, and
|
||
|
|
// close requests return to the same API process.
|
||
|
|
type TicketSocketPool struct {
|
||
|
|
endpoints []ticketEndpoint
|
||
|
|
|
||
|
|
mu sync.RWMutex
|
||
|
|
bindings map[TicketBinding]ticketEndpoint
|
||
|
|
}
|
||
|
|
|
||
|
|
func NewTicketSocketPool(paths []string) *TicketSocketPool {
|
||
|
|
endpoints := make([]ticketEndpoint, 0, len(paths))
|
||
|
|
seen := make(map[string]struct{}, len(paths))
|
||
|
|
for _, path := range paths {
|
||
|
|
path = strings.TrimSpace(path)
|
||
|
|
if path == "" {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
if _, exists := seen[path]; exists {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
seen[path] = struct{}{}
|
||
|
|
endpoints = append(endpoints, NewTicketSocketClient(path))
|
||
|
|
}
|
||
|
|
if len(endpoints) == 0 {
|
||
|
|
endpoints = append(endpoints, NewTicketSocketClient(DefaultTicketSocketPath))
|
||
|
|
}
|
||
|
|
return newTicketSocketPool(endpoints)
|
||
|
|
}
|
||
|
|
|
||
|
|
func newTicketSocketPool(endpoints []ticketEndpoint) *TicketSocketPool {
|
||
|
|
return &TicketSocketPool{
|
||
|
|
endpoints: append([]ticketEndpoint(nil), endpoints...),
|
||
|
|
bindings: make(map[TicketBinding]ticketEndpoint),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func (p *TicketSocketPool) ConsumeTicket(ctx context.Context, ticket string, binding TicketBinding) (TicketClaims, error) {
|
||
|
|
var failures []error
|
||
|
|
for _, endpoint := range p.endpoints {
|
||
|
|
claims, err := endpoint.ConsumeTicket(ctx, ticket, binding)
|
||
|
|
if err != nil {
|
||
|
|
failures = append(failures, err)
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
p.mu.Lock()
|
||
|
|
p.bindings[binding] = endpoint
|
||
|
|
p.mu.Unlock()
|
||
|
|
return claims, nil
|
||
|
|
}
|
||
|
|
if len(failures) == 0 {
|
||
|
|
return TicketClaims{}, ErrInvalidTicket
|
||
|
|
}
|
||
|
|
return TicketClaims{}, errors.Join(failures...)
|
||
|
|
}
|
||
|
|
|
||
|
|
func (p *TicketSocketPool) ReleaseDirectWriter(ctx context.Context, binding TicketBinding) error {
|
||
|
|
endpoint, err := p.endpoint(binding)
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
return endpoint.ReleaseDirectWriter(ctx, binding)
|
||
|
|
}
|
||
|
|
|
||
|
|
func (p *TicketSocketPool) HeartbeatDirectStream(ctx context.Context, binding TicketBinding) error {
|
||
|
|
endpoint, err := p.endpoint(binding)
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
return endpoint.HeartbeatDirectStream(ctx, binding)
|
||
|
|
}
|
||
|
|
|
||
|
|
func (p *TicketSocketPool) RecordDirectStreamEvent(ctx context.Context, binding TicketBinding, event SessionEvent) error {
|
||
|
|
endpoint, err := p.endpoint(binding)
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
return endpoint.RecordDirectStreamEvent(ctx, binding, event)
|
||
|
|
}
|
||
|
|
|
||
|
|
func (p *TicketSocketPool) CloseDirectStream(ctx context.Context, binding TicketBinding) error {
|
||
|
|
p.mu.Lock()
|
||
|
|
endpoint, exists := p.bindings[binding]
|
||
|
|
if exists {
|
||
|
|
delete(p.bindings, binding)
|
||
|
|
}
|
||
|
|
p.mu.Unlock()
|
||
|
|
if !exists {
|
||
|
|
return ErrTicketBindingInactive
|
||
|
|
}
|
||
|
|
return endpoint.CloseDirectStream(ctx, binding)
|
||
|
|
}
|
||
|
|
|
||
|
|
func (p *TicketSocketPool) Status(ctx context.Context) (TicketServiceSnapshot, error) {
|
||
|
|
var result TicketServiceSnapshot
|
||
|
|
successes := 0
|
||
|
|
var failures []error
|
||
|
|
for _, endpoint := range p.endpoints {
|
||
|
|
status, err := endpoint.Status(ctx)
|
||
|
|
if err != nil {
|
||
|
|
failures = append(failures, err)
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
successes++
|
||
|
|
result.ActiveBindings += status.ActiveBindings
|
||
|
|
result.MaxActiveBindings += status.MaxActiveBindings
|
||
|
|
result.Consumed += status.Consumed
|
||
|
|
result.Heartbeats += status.Heartbeats
|
||
|
|
result.ReleasedWriters += status.ReleasedWriters
|
||
|
|
result.ClosedStreams += status.ClosedStreams
|
||
|
|
result.ExpiredStreams += status.ExpiredStreams
|
||
|
|
result.LifecycleFailures += status.LifecycleFailures
|
||
|
|
result.SessionEvents += status.SessionEvents
|
||
|
|
result.SessionEventFailures += status.SessionEventFailures
|
||
|
|
}
|
||
|
|
if successes > 0 {
|
||
|
|
return result, nil
|
||
|
|
}
|
||
|
|
if len(failures) == 0 {
|
||
|
|
return result, errors.New("no direct stream ticket authorities are configured")
|
||
|
|
}
|
||
|
|
return result, errors.Join(failures...)
|
||
|
|
}
|
||
|
|
|
||
|
|
func (p *TicketSocketPool) endpoint(binding TicketBinding) (ticketEndpoint, error) {
|
||
|
|
p.mu.RLock()
|
||
|
|
endpoint, exists := p.bindings[binding]
|
||
|
|
p.mu.RUnlock()
|
||
|
|
if !exists {
|
||
|
|
return nil, ErrTicketBindingInactive
|
||
|
|
}
|
||
|
|
return endpoint, nil
|
||
|
|
}
|