Squashed 'livekit-server/' content from commit 154b4d26

git-subtree-dir: livekit-server
git-subtree-split: 154b4d26b769c68a03c096124094b97bf61a996f
This commit is contained in:
2026-06-25 14:35:28 +09:00
commit 0da97ebd21
339 changed files with 114111 additions and 0 deletions
+36
View File
@@ -0,0 +1,36 @@
// Copyright 2023 LiveKit, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package routing
import (
"errors"
)
var (
ErrNotFound = errors.New("could not find object")
ErrIPNotSet = errors.New("ip address is required and not set")
ErrHandlerNotDefined = errors.New("handler not defined")
ErrIncorrectRTCNode = errors.New("current node isn't the RTC node for the room")
ErrNodeNotFound = errors.New("could not locate the node")
ErrNodeLimitReached = errors.New("reached configured limit for node")
ErrInvalidRouterMessage = errors.New("invalid router message")
ErrChannelClosed = errors.New("channel closed")
ErrChannelFull = errors.New("channel is full")
// errors when starting signal connection
ErrRequestChannelClosed = errors.New("request channel closed")
ErrCouldNotMigrateParticipant = errors.New("could not migrate participant")
ErrClientInfoNotSet = errors.New("client info not set")
)
+286
View File
@@ -0,0 +1,286 @@
// Copyright 2023 LiveKit, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package routing
import (
"context"
"encoding/json"
"github.com/redis/go-redis/v9"
"go.uber.org/atomic"
"go.uber.org/zap/zapcore"
"google.golang.org/protobuf/proto"
"github.com/livekit/livekit-server/pkg/utils"
"github.com/livekit/protocol/auth"
"github.com/livekit/protocol/livekit"
"github.com/livekit/protocol/logger"
"github.com/livekit/protocol/rpc"
)
//go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 -generate
// MessageSink is an abstraction for writing protobuf messages and having them read by a MessageSource,
// potentially on a different node via a transport
//
//counterfeiter:generate . MessageSink
type MessageSink interface {
WriteMessage(msg proto.Message) error
IsClosed() bool
Close()
ConnectionID() livekit.ConnectionID
}
// ----------
type NullMessageSink struct {
connID livekit.ConnectionID
isClosed atomic.Bool
}
func NewNullMessageSink(connID livekit.ConnectionID) *NullMessageSink {
return &NullMessageSink{
connID: connID,
}
}
func (n *NullMessageSink) WriteMessage(_msg proto.Message) error {
return nil
}
func (n *NullMessageSink) IsClosed() bool {
return n.isClosed.Load()
}
func (n *NullMessageSink) Close() {
n.isClosed.Store(true)
}
func (n *NullMessageSink) ConnectionID() livekit.ConnectionID {
return n.connID
}
// ------------------------------------------------
//counterfeiter:generate . MessageSource
type MessageSource interface {
// ReadChan exposes a one way channel to make it easier to use with select
ReadChan() <-chan proto.Message
IsClosed() bool
Close()
ConnectionID() livekit.ConnectionID
}
// ----------
type NullMessageSource struct {
connID livekit.ConnectionID
msgChan chan proto.Message
isClosed atomic.Bool
}
func NewNullMessageSource(connID livekit.ConnectionID) *NullMessageSource {
return &NullMessageSource{
connID: connID,
msgChan: make(chan proto.Message, 0),
}
}
func (n *NullMessageSource) ReadChan() <-chan proto.Message {
return n.msgChan
}
func (n *NullMessageSource) IsClosed() bool {
return n.isClosed.Load()
}
func (n *NullMessageSource) Close() {
if !n.isClosed.Swap(true) {
close(n.msgChan)
}
}
func (n *NullMessageSource) ConnectionID() livekit.ConnectionID {
return n.connID
}
// ------------------------------------------------
// Router allows multiple nodes to coordinate the participant session
//
//counterfeiter:generate . Router
type Router interface {
MessageRouter
RegisterNode() error
UnregisterNode() error
RemoveDeadNodes() error
ListNodes() ([]*livekit.Node, error)
GetNodeForRoom(ctx context.Context, roomName livekit.RoomName) (*livekit.Node, error)
SetNodeForRoom(ctx context.Context, roomName livekit.RoomName, nodeId livekit.NodeID) error
ClearRoomState(ctx context.Context, roomName livekit.RoomName) error
GetRegion() string
Start() error
Drain()
Stop()
}
type StartParticipantSignalResults struct {
ConnectionID livekit.ConnectionID
RequestSink MessageSink
ResponseSource MessageSource
NodeID livekit.NodeID
NodeSelectionReason string
}
type MessageRouter interface {
// CreateRoom starts an rtc room
CreateRoom(ctx context.Context, req *livekit.CreateRoomRequest) (res *livekit.Room, err error)
// StartParticipantSignal participant signal connection is ready to start
StartParticipantSignal(ctx context.Context, roomName livekit.RoomName, pi ParticipantInit) (res StartParticipantSignalResults, err error)
}
func CreateRouter(
rc redis.UniversalClient,
node LocalNode,
signalClient SignalClient,
roomManagerClient RoomManagerClient,
kps rpc.KeepalivePubSub,
) Router {
lr := NewLocalRouter(node, signalClient, roomManagerClient)
if rc != nil {
return NewRedisRouter(lr, rc, kps)
}
// local routing and store
logger.Infow("using single-node routing")
return lr
}
// ------------------------------------------------
type ParticipantInit struct {
Identity livekit.ParticipantIdentity
Name livekit.ParticipantName
Reconnect bool
ReconnectReason livekit.ReconnectReason
AutoSubscribe bool
Client *livekit.ClientInfo
Grants *auth.ClaimGrants
Region string
AdaptiveStream bool
ID livekit.ParticipantID
SubscriberAllowPause *bool
DisableICELite bool
CreateRoom *livekit.CreateRoomRequest
}
func (pi *ParticipantInit) MarshalLogObject(e zapcore.ObjectEncoder) error {
if pi == nil {
return nil
}
logBoolPtr := func(prop string, val *bool) {
if val == nil {
e.AddString(prop, "not-set")
} else {
e.AddBool(prop, *val)
}
}
e.AddString("Identity", string(pi.Identity))
logBoolPtr("Reconnect", &pi.Reconnect)
e.AddString("ReconnectReason", pi.ReconnectReason.String())
logBoolPtr("AutoSubscribe", &pi.AutoSubscribe)
e.AddObject("Client", logger.Proto(utils.ClientInfoWithoutAddress(pi.Client)))
e.AddObject("Grants", pi.Grants)
e.AddString("Region", pi.Region)
logBoolPtr("AdaptiveStream", &pi.AdaptiveStream)
e.AddString("ID", string(pi.ID))
logBoolPtr("SubscriberAllowPause", pi.SubscriberAllowPause)
logBoolPtr("DisableICELite", &pi.DisableICELite)
e.AddObject("CreateRoom", logger.Proto(pi.CreateRoom))
return nil
}
func (pi *ParticipantInit) ToStartSession(roomName livekit.RoomName, connectionID livekit.ConnectionID) (*livekit.StartSession, error) {
claims, err := json.Marshal(pi.Grants)
if err != nil {
return nil, err
}
ss := &livekit.StartSession{
RoomName: string(roomName),
Identity: string(pi.Identity),
Name: string(pi.Name),
// connection id is to allow the RTC node to identify where to route the message back to
ConnectionId: string(connectionID),
Reconnect: pi.Reconnect,
ReconnectReason: pi.ReconnectReason,
AutoSubscribe: pi.AutoSubscribe,
Client: pi.Client,
GrantsJson: string(claims),
AdaptiveStream: pi.AdaptiveStream,
ParticipantId: string(pi.ID),
DisableIceLite: pi.DisableICELite,
CreateRoom: pi.CreateRoom,
}
if pi.SubscriberAllowPause != nil {
subscriberAllowPause := *pi.SubscriberAllowPause
ss.SubscriberAllowPause = &subscriberAllowPause
}
return ss, nil
}
func ParticipantInitFromStartSession(ss *livekit.StartSession, region string) (*ParticipantInit, error) {
claims := &auth.ClaimGrants{}
if err := json.Unmarshal([]byte(ss.GrantsJson), claims); err != nil {
return nil, err
}
pi := &ParticipantInit{
Identity: livekit.ParticipantIdentity(ss.Identity),
Name: livekit.ParticipantName(ss.Name),
Reconnect: ss.Reconnect,
ReconnectReason: ss.ReconnectReason,
Client: ss.Client,
AutoSubscribe: ss.AutoSubscribe,
Grants: claims,
Region: region,
AdaptiveStream: ss.AdaptiveStream,
ID: livekit.ParticipantID(ss.ParticipantId),
DisableICELite: ss.DisableIceLite,
CreateRoom: ss.CreateRoom,
}
if ss.SubscriberAllowPause != nil {
subscriberAllowPause := *ss.SubscriberAllowPause
pi.SubscriberAllowPause = &subscriberAllowPause
}
// TODO: clean up after 1.7 eol
if pi.CreateRoom == nil {
pi.CreateRoom = &livekit.CreateRoomRequest{
Name: ss.RoomName,
}
}
return pi, nil
}
+171
View File
@@ -0,0 +1,171 @@
// Copyright 2023 LiveKit, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package routing
import (
"context"
"sync"
"time"
"go.uber.org/atomic"
"github.com/livekit/protocol/livekit"
"github.com/livekit/protocol/logger"
)
var _ Router = (*LocalRouter)(nil)
// a router of messages on the same node, basic implementation for local testing
type LocalRouter struct {
currentNode LocalNode
signalClient SignalClient
roomManagerClient RoomManagerClient
lock sync.RWMutex
// channels for each participant
requestChannels map[string]*MessageChannel
responseChannels map[string]*MessageChannel
isStarted atomic.Bool
}
func NewLocalRouter(
currentNode LocalNode,
signalClient SignalClient,
roomManagerClient RoomManagerClient,
) *LocalRouter {
return &LocalRouter{
currentNode: currentNode,
signalClient: signalClient,
roomManagerClient: roomManagerClient,
requestChannels: make(map[string]*MessageChannel),
responseChannels: make(map[string]*MessageChannel),
}
}
func (r *LocalRouter) GetNodeForRoom(_ context.Context, _ livekit.RoomName) (*livekit.Node, error) {
return r.currentNode.Clone(), nil
}
func (r *LocalRouter) SetNodeForRoom(_ context.Context, _ livekit.RoomName, _ livekit.NodeID) error {
return nil
}
func (r *LocalRouter) ClearRoomState(_ context.Context, _ livekit.RoomName) error {
return nil
}
func (r *LocalRouter) RegisterNode() error {
return nil
}
func (r *LocalRouter) UnregisterNode() error {
return nil
}
func (r *LocalRouter) RemoveDeadNodes() error {
return nil
}
func (r *LocalRouter) GetNode(nodeID livekit.NodeID) (*livekit.Node, error) {
if nodeID == r.currentNode.NodeID() {
return r.currentNode.Clone(), nil
}
return nil, ErrNotFound
}
func (r *LocalRouter) ListNodes() ([]*livekit.Node, error) {
return []*livekit.Node{
r.currentNode.Clone(),
}, nil
}
func (r *LocalRouter) CreateRoom(ctx context.Context, req *livekit.CreateRoomRequest) (res *livekit.Room, err error) {
return r.CreateRoomWithNodeID(ctx, req, r.currentNode.NodeID())
}
func (r *LocalRouter) CreateRoomWithNodeID(ctx context.Context, req *livekit.CreateRoomRequest, nodeID livekit.NodeID) (res *livekit.Room, err error) {
return r.roomManagerClient.CreateRoom(ctx, nodeID, req)
}
func (r *LocalRouter) StartParticipantSignal(ctx context.Context, roomName livekit.RoomName, pi ParticipantInit) (res StartParticipantSignalResults, err error) {
return r.StartParticipantSignalWithNodeID(ctx, roomName, pi, r.currentNode.NodeID())
}
func (r *LocalRouter) StartParticipantSignalWithNodeID(ctx context.Context, roomName livekit.RoomName, pi ParticipantInit, nodeID livekit.NodeID) (res StartParticipantSignalResults, err error) {
connectionID, reqSink, resSource, err := r.signalClient.StartParticipantSignal(ctx, roomName, pi, nodeID)
if err != nil {
logger.Errorw("could not handle new participant", err,
"room", roomName,
"participant", pi.Identity,
"connID", connectionID,
)
} else {
return StartParticipantSignalResults{
ConnectionID: connectionID,
RequestSink: reqSink,
ResponseSource: resSource,
NodeID: nodeID,
}, nil
}
return
}
func (r *LocalRouter) Start() error {
if r.isStarted.Swap(true) {
return nil
}
go r.statsWorker()
// go r.memStatsWorker()
return nil
}
func (r *LocalRouter) Drain() {
r.currentNode.SetState(livekit.NodeState_SHUTTING_DOWN)
}
func (r *LocalRouter) Stop() {}
func (r *LocalRouter) GetRegion() string {
return r.currentNode.Region()
}
func (r *LocalRouter) statsWorker() {
for {
if !r.isStarted.Load() {
return
}
// update every 10 seconds
<-time.After(statsUpdateInterval)
r.currentNode.UpdateNodeStats()
}
}
/*
func (r *LocalRouter) memStatsWorker() {
ticker := time.NewTicker(time.Second * 30)
defer ticker.Stop()
for {
<-ticker.C
var m runtime.MemStats
runtime.ReadMemStats(&m)
logger.Infow("memstats",
"mallocs", m.Mallocs, "frees", m.Frees, "m-f", m.Mallocs-m.Frees,
"hinuse", m.HeapInuse, "halloc", m.HeapAlloc, "frag", m.HeapInuse-m.HeapAlloc,
)
}
}
*/
+94
View File
@@ -0,0 +1,94 @@
// Copyright 2023 LiveKit, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package routing
import (
"sync"
"github.com/livekit/protocol/livekit"
"google.golang.org/protobuf/proto"
)
const DefaultMessageChannelSize = 200
type MessageChannel struct {
connectionID livekit.ConnectionID
msgChan chan proto.Message
onClose func()
isClosed bool
lock sync.RWMutex
}
func NewDefaultMessageChannel(connectionID livekit.ConnectionID) *MessageChannel {
return NewMessageChannel(connectionID, DefaultMessageChannelSize)
}
func NewMessageChannel(connectionID livekit.ConnectionID, size int) *MessageChannel {
return &MessageChannel{
connectionID: connectionID,
// allow some buffer to avoid blocked writes
msgChan: make(chan proto.Message, size),
}
}
func (m *MessageChannel) OnClose(f func()) {
m.onClose = f
}
func (m *MessageChannel) IsClosed() bool {
m.lock.RLock()
defer m.lock.RUnlock()
return m.isClosed
}
func (m *MessageChannel) WriteMessage(msg proto.Message) error {
m.lock.RLock()
defer m.lock.RUnlock()
if m.isClosed {
return ErrChannelClosed
}
select {
case m.msgChan <- msg:
// published
return nil
default:
// channel is full
return ErrChannelFull
}
}
func (m *MessageChannel) ReadChan() <-chan proto.Message {
return m.msgChan
}
func (m *MessageChannel) Close() {
m.lock.Lock()
if m.isClosed {
m.lock.Unlock()
return
}
m.isClosed = true
close(m.msgChan)
m.lock.Unlock()
if m.onClose != nil {
m.onClose()
}
}
func (m *MessageChannel) ConnectionID() livekit.ConnectionID {
return m.connectionID
}
+50
View File
@@ -0,0 +1,50 @@
// Copyright 2023 LiveKit, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package routing_test
import (
"sync"
"testing"
"github.com/livekit/protocol/livekit"
"github.com/livekit/livekit-server/pkg/routing"
)
func TestMessageChannel_WriteMessageClosed(t *testing.T) {
// ensure it doesn't panic when written to after closing
m := routing.NewMessageChannel(livekit.ConnectionID("test"), routing.DefaultMessageChannelSize)
go func() {
for msg := range m.ReadChan() {
if msg == nil {
return
}
}
}()
wg := sync.WaitGroup{}
wg.Add(1)
go func() {
defer wg.Done()
for i := 0; i < 100; i++ {
_ = m.WriteMessage(&livekit.SignalRequest{})
}
}()
_ = m.WriteMessage(&livekit.SignalRequest{})
m.Close()
_ = m.WriteMessage(&livekit.SignalRequest{})
wg.Wait()
}
+161
View File
@@ -0,0 +1,161 @@
// Copyright 2023 LiveKit, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package routing
import (
"runtime"
"sync"
"time"
"github.com/livekit/protocol/livekit"
"github.com/livekit/protocol/logger"
"github.com/livekit/protocol/utils"
"github.com/livekit/protocol/utils/guid"
"github.com/livekit/livekit-server/pkg/config"
"github.com/livekit/livekit-server/pkg/telemetry/prometheus"
)
type LocalNode interface {
Clone() *livekit.Node
SetNodeID(nodeID livekit.NodeID)
NodeID() livekit.NodeID
NodeType() livekit.NodeType
NodeIP() string
Region() string
SetState(state livekit.NodeState)
SetStats(stats *livekit.NodeStats)
UpdateNodeStats() bool
SecondsSinceNodeStatsUpdate() float64
}
type LocalNodeImpl struct {
lock sync.RWMutex
node *livekit.Node
// previous stats for computing averages
prevStats *livekit.NodeStats
}
func NewLocalNode(conf *config.Config) (*LocalNodeImpl, error) {
nodeID := guid.New(utils.NodePrefix)
if conf != nil && conf.RTC.NodeIP == "" {
return nil, ErrIPNotSet
}
l := &LocalNodeImpl{
node: &livekit.Node{
Id: nodeID,
NumCpus: uint32(runtime.NumCPU()),
State: livekit.NodeState_SERVING,
Stats: &livekit.NodeStats{
StartedAt: time.Now().Unix(),
UpdatedAt: time.Now().Unix(),
},
},
}
if conf != nil {
l.node.Ip = conf.RTC.NodeIP
l.node.Region = conf.Region
}
return l, nil
}
func NewLocalNodeFromNodeProto(node *livekit.Node) (*LocalNodeImpl, error) {
return &LocalNodeImpl{node: utils.CloneProto(node)}, nil
}
func (l *LocalNodeImpl) Clone() *livekit.Node {
l.lock.RLock()
defer l.lock.RUnlock()
return utils.CloneProto(l.node)
}
// for testing only
func (l *LocalNodeImpl) SetNodeID(nodeID livekit.NodeID) {
l.lock.Lock()
defer l.lock.Unlock()
l.node.Id = string(nodeID)
}
func (l *LocalNodeImpl) NodeID() livekit.NodeID {
l.lock.RLock()
defer l.lock.RUnlock()
return livekit.NodeID(l.node.Id)
}
func (l *LocalNodeImpl) NodeType() livekit.NodeType {
l.lock.RLock()
defer l.lock.RUnlock()
return l.node.Type
}
func (l *LocalNodeImpl) NodeIP() string {
l.lock.RLock()
defer l.lock.RUnlock()
return l.node.Ip
}
func (l *LocalNodeImpl) Region() string {
l.lock.RLock()
defer l.lock.RUnlock()
return l.node.Region
}
func (l *LocalNodeImpl) SetState(state livekit.NodeState) {
l.lock.Lock()
defer l.lock.Unlock()
l.node.State = state
}
// for testing only
func (l *LocalNodeImpl) SetStats(stats *livekit.NodeStats) {
l.lock.Lock()
defer l.lock.Unlock()
l.node.Stats = utils.CloneProto(stats)
}
func (l *LocalNodeImpl) UpdateNodeStats() bool {
l.lock.Lock()
defer l.lock.Unlock()
if l.prevStats == nil {
l.prevStats = l.node.Stats
}
updated, computedAvg, err := prometheus.GetUpdatedNodeStats(l.node.Stats, l.prevStats)
if err != nil {
logger.Errorw("could not update node stats", err)
return false
}
l.node.Stats = updated
if computedAvg {
l.prevStats = updated
}
return true
}
func (l *LocalNodeImpl) SecondsSinceNodeStatsUpdate() float64 {
l.lock.RLock()
defer l.lock.RUnlock()
return time.Since(time.Unix(l.node.Stats.UpdatedAt, 0)).Seconds()
}
+257
View File
@@ -0,0 +1,257 @@
// Copyright 2023 LiveKit, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package routing
import (
"bytes"
"context"
"runtime/pprof"
"time"
"github.com/pkg/errors"
"github.com/redis/go-redis/v9"
"go.uber.org/atomic"
"google.golang.org/protobuf/proto"
"github.com/livekit/protocol/livekit"
"github.com/livekit/protocol/logger"
"github.com/livekit/protocol/rpc"
"github.com/livekit/livekit-server/pkg/routing/selector"
)
const (
// expire participant mappings after a day
participantMappingTTL = 24 * time.Hour
statsUpdateInterval = 2 * time.Second
statsMaxDelaySeconds = float64(30)
// hash of node_id => Node proto
NodesKey = "nodes"
// hash of room_name => node_id
NodeRoomKey = "room_node_map"
)
var _ Router = (*RedisRouter)(nil)
// RedisRouter uses Redis pub/sub to route signaling messages across different nodes
// It relies on the RTC node to be the primary driver of the participant connection.
// Because
type RedisRouter struct {
*LocalRouter
rc redis.UniversalClient
kps rpc.KeepalivePubSub
ctx context.Context
isStarted atomic.Bool
cancel func()
}
func NewRedisRouter(lr *LocalRouter, rc redis.UniversalClient, kps rpc.KeepalivePubSub) *RedisRouter {
rr := &RedisRouter{
LocalRouter: lr,
rc: rc,
kps: kps,
}
rr.ctx, rr.cancel = context.WithCancel(context.Background())
return rr
}
func (r *RedisRouter) RegisterNode() error {
data, err := proto.Marshal(r.currentNode.Clone())
if err != nil {
return err
}
if err := r.rc.HSet(r.ctx, NodesKey, string(r.currentNode.NodeID()), data).Err(); err != nil {
return errors.Wrap(err, "could not register node")
}
return nil
}
func (r *RedisRouter) UnregisterNode() error {
// could be called after Stop(), so we'd want to use an unrelated context
return r.rc.HDel(context.Background(), NodesKey, string(r.currentNode.NodeID())).Err()
}
func (r *RedisRouter) RemoveDeadNodes() error {
nodes, err := r.ListNodes()
if err != nil {
return err
}
for _, n := range nodes {
if !selector.IsAvailable(n) {
if err := r.rc.HDel(context.Background(), NodesKey, n.Id).Err(); err != nil {
return err
}
}
}
return nil
}
// GetNodeForRoom finds the node where the room is hosted at
func (r *RedisRouter) GetNodeForRoom(_ context.Context, roomName livekit.RoomName) (*livekit.Node, error) {
nodeID, err := r.rc.HGet(r.ctx, NodeRoomKey, string(roomName)).Result()
if err == redis.Nil {
return nil, ErrNotFound
} else if err != nil {
return nil, errors.Wrap(err, "could not get node for room")
}
return r.GetNode(livekit.NodeID(nodeID))
}
func (r *RedisRouter) SetNodeForRoom(_ context.Context, roomName livekit.RoomName, nodeID livekit.NodeID) error {
return r.rc.HSet(r.ctx, NodeRoomKey, string(roomName), string(nodeID)).Err()
}
func (r *RedisRouter) ClearRoomState(_ context.Context, roomName livekit.RoomName) error {
if err := r.rc.HDel(context.Background(), NodeRoomKey, string(roomName)).Err(); err != nil {
return errors.Wrap(err, "could not clear room state")
}
return nil
}
func (r *RedisRouter) GetNode(nodeID livekit.NodeID) (*livekit.Node, error) {
data, err := r.rc.HGet(r.ctx, NodesKey, string(nodeID)).Result()
if err == redis.Nil {
return nil, ErrNotFound
} else if err != nil {
return nil, err
}
n := livekit.Node{}
if err = proto.Unmarshal([]byte(data), &n); err != nil {
return nil, err
}
return &n, nil
}
func (r *RedisRouter) ListNodes() ([]*livekit.Node, error) {
items, err := r.rc.HVals(r.ctx, NodesKey).Result()
if err != nil {
return nil, errors.Wrap(err, "could not list nodes")
}
nodes := make([]*livekit.Node, 0, len(items))
for _, item := range items {
n := livekit.Node{}
if err := proto.Unmarshal([]byte(item), &n); err != nil {
return nil, err
}
nodes = append(nodes, &n)
}
return nodes, nil
}
func (r *RedisRouter) CreateRoom(ctx context.Context, req *livekit.CreateRoomRequest) (res *livekit.Room, err error) {
rtcNode, err := r.GetNodeForRoom(ctx, livekit.RoomName(req.Name))
if err != nil {
return
}
return r.CreateRoomWithNodeID(ctx, req, livekit.NodeID(rtcNode.Id))
}
// StartParticipantSignal signal connection sets up paths to the RTC node, and starts to route messages to that message queue
func (r *RedisRouter) StartParticipantSignal(ctx context.Context, roomName livekit.RoomName, pi ParticipantInit) (res StartParticipantSignalResults, err error) {
rtcNode, err := r.GetNodeForRoom(ctx, roomName)
if err != nil {
return
}
return r.StartParticipantSignalWithNodeID(ctx, roomName, pi, livekit.NodeID(rtcNode.Id))
}
func (r *RedisRouter) Start() error {
if r.isStarted.Swap(true) {
return nil
}
workerStarted := make(chan error)
go r.statsWorker()
go r.keepaliveWorker(workerStarted)
// wait until worker is running
return <-workerStarted
}
func (r *RedisRouter) Drain() {
r.currentNode.SetState(livekit.NodeState_SHUTTING_DOWN)
if err := r.RegisterNode(); err != nil {
logger.Errorw("failed to mark as draining", err, "nodeID", r.currentNode.NodeID())
}
}
func (r *RedisRouter) Stop() {
if !r.isStarted.Swap(false) {
return
}
logger.Debugw("stopping RedisRouter")
_ = r.UnregisterNode()
r.cancel()
}
// update node stats and cleanup
func (r *RedisRouter) statsWorker() {
goroutineDumped := false
for r.ctx.Err() == nil {
// update periodically
select {
case <-time.After(statsUpdateInterval):
r.kps.PublishPing(r.ctx, r.currentNode.NodeID(), &rpc.KeepalivePing{Timestamp: time.Now().Unix()})
delaySeconds := r.currentNode.SecondsSinceNodeStatsUpdate()
if delaySeconds > statsMaxDelaySeconds {
if !goroutineDumped {
goroutineDumped = true
buf := bytes.NewBuffer(nil)
_ = pprof.Lookup("goroutine").WriteTo(buf, 2)
logger.Errorw("status update delayed, possible deadlock", nil,
"delay", delaySeconds,
"goroutines", buf.String())
}
} else {
goroutineDumped = false
}
case <-r.ctx.Done():
return
}
}
}
func (r *RedisRouter) keepaliveWorker(startedChan chan error) {
pings, err := r.kps.SubscribePing(r.ctx, r.currentNode.NodeID())
if err != nil {
startedChan <- err
return
}
close(startedChan)
for ping := range pings.Channel() {
if time.Since(time.Unix(ping.Timestamp, 0)) > statsUpdateInterval {
logger.Infow("keep alive too old, skipping", "timestamp", ping.Timestamp)
continue
}
if !r.currentNode.UpdateNodeStats() {
continue
}
// TODO: check stats against config.Limit values
if err := r.RegisterNode(); err != nil {
logger.Errorw("could not update node", err)
}
}
}
+63
View File
@@ -0,0 +1,63 @@
// Copyright 2023 LiveKit, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package routing
import (
"context"
"github.com/livekit/livekit-server/pkg/config"
"github.com/livekit/protocol/livekit"
"github.com/livekit/protocol/rpc"
"github.com/livekit/psrpc"
"github.com/livekit/psrpc/pkg/middleware"
)
//counterfeiter:generate . RoomManagerClient
type RoomManagerClient interface {
rpc.TypedRoomManagerClient
}
type roomManagerClient struct {
config config.RoomConfig
client rpc.TypedRoomManagerClient
}
func NewRoomManagerClient(clientParams rpc.ClientParams, config config.RoomConfig) (RoomManagerClient, error) {
c, err := rpc.NewTypedRoomManagerClient(
clientParams.Bus,
psrpc.WithClientChannelSize(clientParams.BufferSize),
middleware.WithClientMetrics(clientParams.Observer),
rpc.WithClientLogger(clientParams.Logger),
)
if err != nil {
return nil, err
}
return &roomManagerClient{
config: config,
client: c,
}, nil
}
func (c *roomManagerClient) CreateRoom(ctx context.Context, nodeID livekit.NodeID, req *livekit.CreateRoomRequest, opts ...psrpc.RequestOption) (*livekit.Room, error) {
return c.client.CreateRoom(ctx, nodeID, req, append(opts, psrpc.WithRequestInterceptors(middleware.NewRPCRetryInterceptor(middleware.RetryOptions{
MaxAttempts: c.config.CreateRoomAttempts,
Timeout: c.config.CreateRoomTimeout,
})))...)
}
func (c *roomManagerClient) Close() {
c.client.Close()
}
@@ -0,0 +1,273 @@
// Code generated by counterfeiter. DO NOT EDIT.
package routingfakes
import (
"sync"
"github.com/livekit/livekit-server/pkg/routing"
"github.com/livekit/protocol/livekit"
"google.golang.org/protobuf/proto"
)
type FakeMessageSink struct {
CloseStub func()
closeMutex sync.RWMutex
closeArgsForCall []struct {
}
ConnectionIDStub func() livekit.ConnectionID
connectionIDMutex sync.RWMutex
connectionIDArgsForCall []struct {
}
connectionIDReturns struct {
result1 livekit.ConnectionID
}
connectionIDReturnsOnCall map[int]struct {
result1 livekit.ConnectionID
}
IsClosedStub func() bool
isClosedMutex sync.RWMutex
isClosedArgsForCall []struct {
}
isClosedReturns struct {
result1 bool
}
isClosedReturnsOnCall map[int]struct {
result1 bool
}
WriteMessageStub func(proto.Message) error
writeMessageMutex sync.RWMutex
writeMessageArgsForCall []struct {
arg1 proto.Message
}
writeMessageReturns struct {
result1 error
}
writeMessageReturnsOnCall map[int]struct {
result1 error
}
invocations map[string][][]interface{}
invocationsMutex sync.RWMutex
}
func (fake *FakeMessageSink) Close() {
fake.closeMutex.Lock()
fake.closeArgsForCall = append(fake.closeArgsForCall, struct {
}{})
stub := fake.CloseStub
fake.recordInvocation("Close", []interface{}{})
fake.closeMutex.Unlock()
if stub != nil {
fake.CloseStub()
}
}
func (fake *FakeMessageSink) CloseCallCount() int {
fake.closeMutex.RLock()
defer fake.closeMutex.RUnlock()
return len(fake.closeArgsForCall)
}
func (fake *FakeMessageSink) CloseCalls(stub func()) {
fake.closeMutex.Lock()
defer fake.closeMutex.Unlock()
fake.CloseStub = stub
}
func (fake *FakeMessageSink) ConnectionID() livekit.ConnectionID {
fake.connectionIDMutex.Lock()
ret, specificReturn := fake.connectionIDReturnsOnCall[len(fake.connectionIDArgsForCall)]
fake.connectionIDArgsForCall = append(fake.connectionIDArgsForCall, struct {
}{})
stub := fake.ConnectionIDStub
fakeReturns := fake.connectionIDReturns
fake.recordInvocation("ConnectionID", []interface{}{})
fake.connectionIDMutex.Unlock()
if stub != nil {
return stub()
}
if specificReturn {
return ret.result1
}
return fakeReturns.result1
}
func (fake *FakeMessageSink) ConnectionIDCallCount() int {
fake.connectionIDMutex.RLock()
defer fake.connectionIDMutex.RUnlock()
return len(fake.connectionIDArgsForCall)
}
func (fake *FakeMessageSink) ConnectionIDCalls(stub func() livekit.ConnectionID) {
fake.connectionIDMutex.Lock()
defer fake.connectionIDMutex.Unlock()
fake.ConnectionIDStub = stub
}
func (fake *FakeMessageSink) ConnectionIDReturns(result1 livekit.ConnectionID) {
fake.connectionIDMutex.Lock()
defer fake.connectionIDMutex.Unlock()
fake.ConnectionIDStub = nil
fake.connectionIDReturns = struct {
result1 livekit.ConnectionID
}{result1}
}
func (fake *FakeMessageSink) ConnectionIDReturnsOnCall(i int, result1 livekit.ConnectionID) {
fake.connectionIDMutex.Lock()
defer fake.connectionIDMutex.Unlock()
fake.ConnectionIDStub = nil
if fake.connectionIDReturnsOnCall == nil {
fake.connectionIDReturnsOnCall = make(map[int]struct {
result1 livekit.ConnectionID
})
}
fake.connectionIDReturnsOnCall[i] = struct {
result1 livekit.ConnectionID
}{result1}
}
func (fake *FakeMessageSink) IsClosed() bool {
fake.isClosedMutex.Lock()
ret, specificReturn := fake.isClosedReturnsOnCall[len(fake.isClosedArgsForCall)]
fake.isClosedArgsForCall = append(fake.isClosedArgsForCall, struct {
}{})
stub := fake.IsClosedStub
fakeReturns := fake.isClosedReturns
fake.recordInvocation("IsClosed", []interface{}{})
fake.isClosedMutex.Unlock()
if stub != nil {
return stub()
}
if specificReturn {
return ret.result1
}
return fakeReturns.result1
}
func (fake *FakeMessageSink) IsClosedCallCount() int {
fake.isClosedMutex.RLock()
defer fake.isClosedMutex.RUnlock()
return len(fake.isClosedArgsForCall)
}
func (fake *FakeMessageSink) IsClosedCalls(stub func() bool) {
fake.isClosedMutex.Lock()
defer fake.isClosedMutex.Unlock()
fake.IsClosedStub = stub
}
func (fake *FakeMessageSink) IsClosedReturns(result1 bool) {
fake.isClosedMutex.Lock()
defer fake.isClosedMutex.Unlock()
fake.IsClosedStub = nil
fake.isClosedReturns = struct {
result1 bool
}{result1}
}
func (fake *FakeMessageSink) IsClosedReturnsOnCall(i int, result1 bool) {
fake.isClosedMutex.Lock()
defer fake.isClosedMutex.Unlock()
fake.IsClosedStub = nil
if fake.isClosedReturnsOnCall == nil {
fake.isClosedReturnsOnCall = make(map[int]struct {
result1 bool
})
}
fake.isClosedReturnsOnCall[i] = struct {
result1 bool
}{result1}
}
func (fake *FakeMessageSink) WriteMessage(arg1 proto.Message) error {
fake.writeMessageMutex.Lock()
ret, specificReturn := fake.writeMessageReturnsOnCall[len(fake.writeMessageArgsForCall)]
fake.writeMessageArgsForCall = append(fake.writeMessageArgsForCall, struct {
arg1 proto.Message
}{arg1})
stub := fake.WriteMessageStub
fakeReturns := fake.writeMessageReturns
fake.recordInvocation("WriteMessage", []interface{}{arg1})
fake.writeMessageMutex.Unlock()
if stub != nil {
return stub(arg1)
}
if specificReturn {
return ret.result1
}
return fakeReturns.result1
}
func (fake *FakeMessageSink) WriteMessageCallCount() int {
fake.writeMessageMutex.RLock()
defer fake.writeMessageMutex.RUnlock()
return len(fake.writeMessageArgsForCall)
}
func (fake *FakeMessageSink) WriteMessageCalls(stub func(proto.Message) error) {
fake.writeMessageMutex.Lock()
defer fake.writeMessageMutex.Unlock()
fake.WriteMessageStub = stub
}
func (fake *FakeMessageSink) WriteMessageArgsForCall(i int) proto.Message {
fake.writeMessageMutex.RLock()
defer fake.writeMessageMutex.RUnlock()
argsForCall := fake.writeMessageArgsForCall[i]
return argsForCall.arg1
}
func (fake *FakeMessageSink) WriteMessageReturns(result1 error) {
fake.writeMessageMutex.Lock()
defer fake.writeMessageMutex.Unlock()
fake.WriteMessageStub = nil
fake.writeMessageReturns = struct {
result1 error
}{result1}
}
func (fake *FakeMessageSink) WriteMessageReturnsOnCall(i int, result1 error) {
fake.writeMessageMutex.Lock()
defer fake.writeMessageMutex.Unlock()
fake.WriteMessageStub = nil
if fake.writeMessageReturnsOnCall == nil {
fake.writeMessageReturnsOnCall = make(map[int]struct {
result1 error
})
}
fake.writeMessageReturnsOnCall[i] = struct {
result1 error
}{result1}
}
func (fake *FakeMessageSink) Invocations() map[string][][]interface{} {
fake.invocationsMutex.RLock()
defer fake.invocationsMutex.RUnlock()
fake.closeMutex.RLock()
defer fake.closeMutex.RUnlock()
fake.connectionIDMutex.RLock()
defer fake.connectionIDMutex.RUnlock()
fake.isClosedMutex.RLock()
defer fake.isClosedMutex.RUnlock()
fake.writeMessageMutex.RLock()
defer fake.writeMessageMutex.RUnlock()
copiedInvocations := map[string][][]interface{}{}
for key, value := range fake.invocations {
copiedInvocations[key] = value
}
return copiedInvocations
}
func (fake *FakeMessageSink) recordInvocation(key string, args []interface{}) {
fake.invocationsMutex.Lock()
defer fake.invocationsMutex.Unlock()
if fake.invocations == nil {
fake.invocations = map[string][][]interface{}{}
}
if fake.invocations[key] == nil {
fake.invocations[key] = [][]interface{}{}
}
fake.invocations[key] = append(fake.invocations[key], args)
}
var _ routing.MessageSink = new(FakeMessageSink)
@@ -0,0 +1,264 @@
// Code generated by counterfeiter. DO NOT EDIT.
package routingfakes
import (
"sync"
"github.com/livekit/livekit-server/pkg/routing"
"github.com/livekit/protocol/livekit"
"google.golang.org/protobuf/proto"
)
type FakeMessageSource struct {
CloseStub func()
closeMutex sync.RWMutex
closeArgsForCall []struct {
}
ConnectionIDStub func() livekit.ConnectionID
connectionIDMutex sync.RWMutex
connectionIDArgsForCall []struct {
}
connectionIDReturns struct {
result1 livekit.ConnectionID
}
connectionIDReturnsOnCall map[int]struct {
result1 livekit.ConnectionID
}
IsClosedStub func() bool
isClosedMutex sync.RWMutex
isClosedArgsForCall []struct {
}
isClosedReturns struct {
result1 bool
}
isClosedReturnsOnCall map[int]struct {
result1 bool
}
ReadChanStub func() <-chan proto.Message
readChanMutex sync.RWMutex
readChanArgsForCall []struct {
}
readChanReturns struct {
result1 <-chan proto.Message
}
readChanReturnsOnCall map[int]struct {
result1 <-chan proto.Message
}
invocations map[string][][]interface{}
invocationsMutex sync.RWMutex
}
func (fake *FakeMessageSource) Close() {
fake.closeMutex.Lock()
fake.closeArgsForCall = append(fake.closeArgsForCall, struct {
}{})
stub := fake.CloseStub
fake.recordInvocation("Close", []interface{}{})
fake.closeMutex.Unlock()
if stub != nil {
fake.CloseStub()
}
}
func (fake *FakeMessageSource) CloseCallCount() int {
fake.closeMutex.RLock()
defer fake.closeMutex.RUnlock()
return len(fake.closeArgsForCall)
}
func (fake *FakeMessageSource) CloseCalls(stub func()) {
fake.closeMutex.Lock()
defer fake.closeMutex.Unlock()
fake.CloseStub = stub
}
func (fake *FakeMessageSource) ConnectionID() livekit.ConnectionID {
fake.connectionIDMutex.Lock()
ret, specificReturn := fake.connectionIDReturnsOnCall[len(fake.connectionIDArgsForCall)]
fake.connectionIDArgsForCall = append(fake.connectionIDArgsForCall, struct {
}{})
stub := fake.ConnectionIDStub
fakeReturns := fake.connectionIDReturns
fake.recordInvocation("ConnectionID", []interface{}{})
fake.connectionIDMutex.Unlock()
if stub != nil {
return stub()
}
if specificReturn {
return ret.result1
}
return fakeReturns.result1
}
func (fake *FakeMessageSource) ConnectionIDCallCount() int {
fake.connectionIDMutex.RLock()
defer fake.connectionIDMutex.RUnlock()
return len(fake.connectionIDArgsForCall)
}
func (fake *FakeMessageSource) ConnectionIDCalls(stub func() livekit.ConnectionID) {
fake.connectionIDMutex.Lock()
defer fake.connectionIDMutex.Unlock()
fake.ConnectionIDStub = stub
}
func (fake *FakeMessageSource) ConnectionIDReturns(result1 livekit.ConnectionID) {
fake.connectionIDMutex.Lock()
defer fake.connectionIDMutex.Unlock()
fake.ConnectionIDStub = nil
fake.connectionIDReturns = struct {
result1 livekit.ConnectionID
}{result1}
}
func (fake *FakeMessageSource) ConnectionIDReturnsOnCall(i int, result1 livekit.ConnectionID) {
fake.connectionIDMutex.Lock()
defer fake.connectionIDMutex.Unlock()
fake.ConnectionIDStub = nil
if fake.connectionIDReturnsOnCall == nil {
fake.connectionIDReturnsOnCall = make(map[int]struct {
result1 livekit.ConnectionID
})
}
fake.connectionIDReturnsOnCall[i] = struct {
result1 livekit.ConnectionID
}{result1}
}
func (fake *FakeMessageSource) IsClosed() bool {
fake.isClosedMutex.Lock()
ret, specificReturn := fake.isClosedReturnsOnCall[len(fake.isClosedArgsForCall)]
fake.isClosedArgsForCall = append(fake.isClosedArgsForCall, struct {
}{})
stub := fake.IsClosedStub
fakeReturns := fake.isClosedReturns
fake.recordInvocation("IsClosed", []interface{}{})
fake.isClosedMutex.Unlock()
if stub != nil {
return stub()
}
if specificReturn {
return ret.result1
}
return fakeReturns.result1
}
func (fake *FakeMessageSource) IsClosedCallCount() int {
fake.isClosedMutex.RLock()
defer fake.isClosedMutex.RUnlock()
return len(fake.isClosedArgsForCall)
}
func (fake *FakeMessageSource) IsClosedCalls(stub func() bool) {
fake.isClosedMutex.Lock()
defer fake.isClosedMutex.Unlock()
fake.IsClosedStub = stub
}
func (fake *FakeMessageSource) IsClosedReturns(result1 bool) {
fake.isClosedMutex.Lock()
defer fake.isClosedMutex.Unlock()
fake.IsClosedStub = nil
fake.isClosedReturns = struct {
result1 bool
}{result1}
}
func (fake *FakeMessageSource) IsClosedReturnsOnCall(i int, result1 bool) {
fake.isClosedMutex.Lock()
defer fake.isClosedMutex.Unlock()
fake.IsClosedStub = nil
if fake.isClosedReturnsOnCall == nil {
fake.isClosedReturnsOnCall = make(map[int]struct {
result1 bool
})
}
fake.isClosedReturnsOnCall[i] = struct {
result1 bool
}{result1}
}
func (fake *FakeMessageSource) ReadChan() <-chan proto.Message {
fake.readChanMutex.Lock()
ret, specificReturn := fake.readChanReturnsOnCall[len(fake.readChanArgsForCall)]
fake.readChanArgsForCall = append(fake.readChanArgsForCall, struct {
}{})
stub := fake.ReadChanStub
fakeReturns := fake.readChanReturns
fake.recordInvocation("ReadChan", []interface{}{})
fake.readChanMutex.Unlock()
if stub != nil {
return stub()
}
if specificReturn {
return ret.result1
}
return fakeReturns.result1
}
func (fake *FakeMessageSource) ReadChanCallCount() int {
fake.readChanMutex.RLock()
defer fake.readChanMutex.RUnlock()
return len(fake.readChanArgsForCall)
}
func (fake *FakeMessageSource) ReadChanCalls(stub func() <-chan proto.Message) {
fake.readChanMutex.Lock()
defer fake.readChanMutex.Unlock()
fake.ReadChanStub = stub
}
func (fake *FakeMessageSource) ReadChanReturns(result1 <-chan proto.Message) {
fake.readChanMutex.Lock()
defer fake.readChanMutex.Unlock()
fake.ReadChanStub = nil
fake.readChanReturns = struct {
result1 <-chan proto.Message
}{result1}
}
func (fake *FakeMessageSource) ReadChanReturnsOnCall(i int, result1 <-chan proto.Message) {
fake.readChanMutex.Lock()
defer fake.readChanMutex.Unlock()
fake.ReadChanStub = nil
if fake.readChanReturnsOnCall == nil {
fake.readChanReturnsOnCall = make(map[int]struct {
result1 <-chan proto.Message
})
}
fake.readChanReturnsOnCall[i] = struct {
result1 <-chan proto.Message
}{result1}
}
func (fake *FakeMessageSource) Invocations() map[string][][]interface{} {
fake.invocationsMutex.RLock()
defer fake.invocationsMutex.RUnlock()
fake.closeMutex.RLock()
defer fake.closeMutex.RUnlock()
fake.connectionIDMutex.RLock()
defer fake.connectionIDMutex.RUnlock()
fake.isClosedMutex.RLock()
defer fake.isClosedMutex.RUnlock()
fake.readChanMutex.RLock()
defer fake.readChanMutex.RUnlock()
copiedInvocations := map[string][][]interface{}{}
for key, value := range fake.invocations {
copiedInvocations[key] = value
}
return copiedInvocations
}
func (fake *FakeMessageSource) recordInvocation(key string, args []interface{}) {
fake.invocationsMutex.Lock()
defer fake.invocationsMutex.Unlock()
if fake.invocations == nil {
fake.invocations = map[string][][]interface{}{}
}
if fake.invocations[key] == nil {
fake.invocations[key] = [][]interface{}{}
}
fake.invocations[key] = append(fake.invocations[key], args)
}
var _ routing.MessageSource = new(FakeMessageSource)
@@ -0,0 +1,155 @@
// Code generated by counterfeiter. DO NOT EDIT.
package routingfakes
import (
"context"
"sync"
"github.com/livekit/livekit-server/pkg/routing"
"github.com/livekit/protocol/livekit"
"github.com/livekit/psrpc"
)
type FakeRoomManagerClient struct {
CloseStub func()
closeMutex sync.RWMutex
closeArgsForCall []struct {
}
CreateRoomStub func(context.Context, livekit.NodeID, *livekit.CreateRoomRequest, ...psrpc.RequestOption) (*livekit.Room, error)
createRoomMutex sync.RWMutex
createRoomArgsForCall []struct {
arg1 context.Context
arg2 livekit.NodeID
arg3 *livekit.CreateRoomRequest
arg4 []psrpc.RequestOption
}
createRoomReturns struct {
result1 *livekit.Room
result2 error
}
createRoomReturnsOnCall map[int]struct {
result1 *livekit.Room
result2 error
}
invocations map[string][][]interface{}
invocationsMutex sync.RWMutex
}
func (fake *FakeRoomManagerClient) Close() {
fake.closeMutex.Lock()
fake.closeArgsForCall = append(fake.closeArgsForCall, struct {
}{})
stub := fake.CloseStub
fake.recordInvocation("Close", []interface{}{})
fake.closeMutex.Unlock()
if stub != nil {
fake.CloseStub()
}
}
func (fake *FakeRoomManagerClient) CloseCallCount() int {
fake.closeMutex.RLock()
defer fake.closeMutex.RUnlock()
return len(fake.closeArgsForCall)
}
func (fake *FakeRoomManagerClient) CloseCalls(stub func()) {
fake.closeMutex.Lock()
defer fake.closeMutex.Unlock()
fake.CloseStub = stub
}
func (fake *FakeRoomManagerClient) CreateRoom(arg1 context.Context, arg2 livekit.NodeID, arg3 *livekit.CreateRoomRequest, arg4 ...psrpc.RequestOption) (*livekit.Room, error) {
fake.createRoomMutex.Lock()
ret, specificReturn := fake.createRoomReturnsOnCall[len(fake.createRoomArgsForCall)]
fake.createRoomArgsForCall = append(fake.createRoomArgsForCall, struct {
arg1 context.Context
arg2 livekit.NodeID
arg3 *livekit.CreateRoomRequest
arg4 []psrpc.RequestOption
}{arg1, arg2, arg3, arg4})
stub := fake.CreateRoomStub
fakeReturns := fake.createRoomReturns
fake.recordInvocation("CreateRoom", []interface{}{arg1, arg2, arg3, arg4})
fake.createRoomMutex.Unlock()
if stub != nil {
return stub(arg1, arg2, arg3, arg4...)
}
if specificReturn {
return ret.result1, ret.result2
}
return fakeReturns.result1, fakeReturns.result2
}
func (fake *FakeRoomManagerClient) CreateRoomCallCount() int {
fake.createRoomMutex.RLock()
defer fake.createRoomMutex.RUnlock()
return len(fake.createRoomArgsForCall)
}
func (fake *FakeRoomManagerClient) CreateRoomCalls(stub func(context.Context, livekit.NodeID, *livekit.CreateRoomRequest, ...psrpc.RequestOption) (*livekit.Room, error)) {
fake.createRoomMutex.Lock()
defer fake.createRoomMutex.Unlock()
fake.CreateRoomStub = stub
}
func (fake *FakeRoomManagerClient) CreateRoomArgsForCall(i int) (context.Context, livekit.NodeID, *livekit.CreateRoomRequest, []psrpc.RequestOption) {
fake.createRoomMutex.RLock()
defer fake.createRoomMutex.RUnlock()
argsForCall := fake.createRoomArgsForCall[i]
return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3, argsForCall.arg4
}
func (fake *FakeRoomManagerClient) CreateRoomReturns(result1 *livekit.Room, result2 error) {
fake.createRoomMutex.Lock()
defer fake.createRoomMutex.Unlock()
fake.CreateRoomStub = nil
fake.createRoomReturns = struct {
result1 *livekit.Room
result2 error
}{result1, result2}
}
func (fake *FakeRoomManagerClient) CreateRoomReturnsOnCall(i int, result1 *livekit.Room, result2 error) {
fake.createRoomMutex.Lock()
defer fake.createRoomMutex.Unlock()
fake.CreateRoomStub = nil
if fake.createRoomReturnsOnCall == nil {
fake.createRoomReturnsOnCall = make(map[int]struct {
result1 *livekit.Room
result2 error
})
}
fake.createRoomReturnsOnCall[i] = struct {
result1 *livekit.Room
result2 error
}{result1, result2}
}
func (fake *FakeRoomManagerClient) Invocations() map[string][][]interface{} {
fake.invocationsMutex.RLock()
defer fake.invocationsMutex.RUnlock()
fake.closeMutex.RLock()
defer fake.closeMutex.RUnlock()
fake.createRoomMutex.RLock()
defer fake.createRoomMutex.RUnlock()
copiedInvocations := map[string][][]interface{}{}
for key, value := range fake.invocations {
copiedInvocations[key] = value
}
return copiedInvocations
}
func (fake *FakeRoomManagerClient) recordInvocation(key string, args []interface{}) {
fake.invocationsMutex.Lock()
defer fake.invocationsMutex.Unlock()
if fake.invocations == nil {
fake.invocations = map[string][][]interface{}{}
}
if fake.invocations[key] == nil {
fake.invocations[key] = [][]interface{}{}
}
fake.invocations[key] = append(fake.invocations[key], args)
}
var _ routing.RoomManagerClient = new(FakeRoomManagerClient)
+893
View File
@@ -0,0 +1,893 @@
// Code generated by counterfeiter. DO NOT EDIT.
package routingfakes
import (
"context"
"sync"
"github.com/livekit/livekit-server/pkg/routing"
"github.com/livekit/protocol/livekit"
)
type FakeRouter struct {
ClearRoomStateStub func(context.Context, livekit.RoomName) error
clearRoomStateMutex sync.RWMutex
clearRoomStateArgsForCall []struct {
arg1 context.Context
arg2 livekit.RoomName
}
clearRoomStateReturns struct {
result1 error
}
clearRoomStateReturnsOnCall map[int]struct {
result1 error
}
CreateRoomStub func(context.Context, *livekit.CreateRoomRequest) (*livekit.Room, error)
createRoomMutex sync.RWMutex
createRoomArgsForCall []struct {
arg1 context.Context
arg2 *livekit.CreateRoomRequest
}
createRoomReturns struct {
result1 *livekit.Room
result2 error
}
createRoomReturnsOnCall map[int]struct {
result1 *livekit.Room
result2 error
}
DrainStub func()
drainMutex sync.RWMutex
drainArgsForCall []struct {
}
GetNodeForRoomStub func(context.Context, livekit.RoomName) (*livekit.Node, error)
getNodeForRoomMutex sync.RWMutex
getNodeForRoomArgsForCall []struct {
arg1 context.Context
arg2 livekit.RoomName
}
getNodeForRoomReturns struct {
result1 *livekit.Node
result2 error
}
getNodeForRoomReturnsOnCall map[int]struct {
result1 *livekit.Node
result2 error
}
GetRegionStub func() string
getRegionMutex sync.RWMutex
getRegionArgsForCall []struct {
}
getRegionReturns struct {
result1 string
}
getRegionReturnsOnCall map[int]struct {
result1 string
}
ListNodesStub func() ([]*livekit.Node, error)
listNodesMutex sync.RWMutex
listNodesArgsForCall []struct {
}
listNodesReturns struct {
result1 []*livekit.Node
result2 error
}
listNodesReturnsOnCall map[int]struct {
result1 []*livekit.Node
result2 error
}
RegisterNodeStub func() error
registerNodeMutex sync.RWMutex
registerNodeArgsForCall []struct {
}
registerNodeReturns struct {
result1 error
}
registerNodeReturnsOnCall map[int]struct {
result1 error
}
RemoveDeadNodesStub func() error
removeDeadNodesMutex sync.RWMutex
removeDeadNodesArgsForCall []struct {
}
removeDeadNodesReturns struct {
result1 error
}
removeDeadNodesReturnsOnCall map[int]struct {
result1 error
}
SetNodeForRoomStub func(context.Context, livekit.RoomName, livekit.NodeID) error
setNodeForRoomMutex sync.RWMutex
setNodeForRoomArgsForCall []struct {
arg1 context.Context
arg2 livekit.RoomName
arg3 livekit.NodeID
}
setNodeForRoomReturns struct {
result1 error
}
setNodeForRoomReturnsOnCall map[int]struct {
result1 error
}
StartStub func() error
startMutex sync.RWMutex
startArgsForCall []struct {
}
startReturns struct {
result1 error
}
startReturnsOnCall map[int]struct {
result1 error
}
StartParticipantSignalStub func(context.Context, livekit.RoomName, routing.ParticipantInit) (routing.StartParticipantSignalResults, error)
startParticipantSignalMutex sync.RWMutex
startParticipantSignalArgsForCall []struct {
arg1 context.Context
arg2 livekit.RoomName
arg3 routing.ParticipantInit
}
startParticipantSignalReturns struct {
result1 routing.StartParticipantSignalResults
result2 error
}
startParticipantSignalReturnsOnCall map[int]struct {
result1 routing.StartParticipantSignalResults
result2 error
}
StopStub func()
stopMutex sync.RWMutex
stopArgsForCall []struct {
}
UnregisterNodeStub func() error
unregisterNodeMutex sync.RWMutex
unregisterNodeArgsForCall []struct {
}
unregisterNodeReturns struct {
result1 error
}
unregisterNodeReturnsOnCall map[int]struct {
result1 error
}
invocations map[string][][]interface{}
invocationsMutex sync.RWMutex
}
func (fake *FakeRouter) ClearRoomState(arg1 context.Context, arg2 livekit.RoomName) error {
fake.clearRoomStateMutex.Lock()
ret, specificReturn := fake.clearRoomStateReturnsOnCall[len(fake.clearRoomStateArgsForCall)]
fake.clearRoomStateArgsForCall = append(fake.clearRoomStateArgsForCall, struct {
arg1 context.Context
arg2 livekit.RoomName
}{arg1, arg2})
stub := fake.ClearRoomStateStub
fakeReturns := fake.clearRoomStateReturns
fake.recordInvocation("ClearRoomState", []interface{}{arg1, arg2})
fake.clearRoomStateMutex.Unlock()
if stub != nil {
return stub(arg1, arg2)
}
if specificReturn {
return ret.result1
}
return fakeReturns.result1
}
func (fake *FakeRouter) ClearRoomStateCallCount() int {
fake.clearRoomStateMutex.RLock()
defer fake.clearRoomStateMutex.RUnlock()
return len(fake.clearRoomStateArgsForCall)
}
func (fake *FakeRouter) ClearRoomStateCalls(stub func(context.Context, livekit.RoomName) error) {
fake.clearRoomStateMutex.Lock()
defer fake.clearRoomStateMutex.Unlock()
fake.ClearRoomStateStub = stub
}
func (fake *FakeRouter) ClearRoomStateArgsForCall(i int) (context.Context, livekit.RoomName) {
fake.clearRoomStateMutex.RLock()
defer fake.clearRoomStateMutex.RUnlock()
argsForCall := fake.clearRoomStateArgsForCall[i]
return argsForCall.arg1, argsForCall.arg2
}
func (fake *FakeRouter) ClearRoomStateReturns(result1 error) {
fake.clearRoomStateMutex.Lock()
defer fake.clearRoomStateMutex.Unlock()
fake.ClearRoomStateStub = nil
fake.clearRoomStateReturns = struct {
result1 error
}{result1}
}
func (fake *FakeRouter) ClearRoomStateReturnsOnCall(i int, result1 error) {
fake.clearRoomStateMutex.Lock()
defer fake.clearRoomStateMutex.Unlock()
fake.ClearRoomStateStub = nil
if fake.clearRoomStateReturnsOnCall == nil {
fake.clearRoomStateReturnsOnCall = make(map[int]struct {
result1 error
})
}
fake.clearRoomStateReturnsOnCall[i] = struct {
result1 error
}{result1}
}
func (fake *FakeRouter) CreateRoom(arg1 context.Context, arg2 *livekit.CreateRoomRequest) (*livekit.Room, error) {
fake.createRoomMutex.Lock()
ret, specificReturn := fake.createRoomReturnsOnCall[len(fake.createRoomArgsForCall)]
fake.createRoomArgsForCall = append(fake.createRoomArgsForCall, struct {
arg1 context.Context
arg2 *livekit.CreateRoomRequest
}{arg1, arg2})
stub := fake.CreateRoomStub
fakeReturns := fake.createRoomReturns
fake.recordInvocation("CreateRoom", []interface{}{arg1, arg2})
fake.createRoomMutex.Unlock()
if stub != nil {
return stub(arg1, arg2)
}
if specificReturn {
return ret.result1, ret.result2
}
return fakeReturns.result1, fakeReturns.result2
}
func (fake *FakeRouter) CreateRoomCallCount() int {
fake.createRoomMutex.RLock()
defer fake.createRoomMutex.RUnlock()
return len(fake.createRoomArgsForCall)
}
func (fake *FakeRouter) CreateRoomCalls(stub func(context.Context, *livekit.CreateRoomRequest) (*livekit.Room, error)) {
fake.createRoomMutex.Lock()
defer fake.createRoomMutex.Unlock()
fake.CreateRoomStub = stub
}
func (fake *FakeRouter) CreateRoomArgsForCall(i int) (context.Context, *livekit.CreateRoomRequest) {
fake.createRoomMutex.RLock()
defer fake.createRoomMutex.RUnlock()
argsForCall := fake.createRoomArgsForCall[i]
return argsForCall.arg1, argsForCall.arg2
}
func (fake *FakeRouter) CreateRoomReturns(result1 *livekit.Room, result2 error) {
fake.createRoomMutex.Lock()
defer fake.createRoomMutex.Unlock()
fake.CreateRoomStub = nil
fake.createRoomReturns = struct {
result1 *livekit.Room
result2 error
}{result1, result2}
}
func (fake *FakeRouter) CreateRoomReturnsOnCall(i int, result1 *livekit.Room, result2 error) {
fake.createRoomMutex.Lock()
defer fake.createRoomMutex.Unlock()
fake.CreateRoomStub = nil
if fake.createRoomReturnsOnCall == nil {
fake.createRoomReturnsOnCall = make(map[int]struct {
result1 *livekit.Room
result2 error
})
}
fake.createRoomReturnsOnCall[i] = struct {
result1 *livekit.Room
result2 error
}{result1, result2}
}
func (fake *FakeRouter) Drain() {
fake.drainMutex.Lock()
fake.drainArgsForCall = append(fake.drainArgsForCall, struct {
}{})
stub := fake.DrainStub
fake.recordInvocation("Drain", []interface{}{})
fake.drainMutex.Unlock()
if stub != nil {
fake.DrainStub()
}
}
func (fake *FakeRouter) DrainCallCount() int {
fake.drainMutex.RLock()
defer fake.drainMutex.RUnlock()
return len(fake.drainArgsForCall)
}
func (fake *FakeRouter) DrainCalls(stub func()) {
fake.drainMutex.Lock()
defer fake.drainMutex.Unlock()
fake.DrainStub = stub
}
func (fake *FakeRouter) GetNodeForRoom(arg1 context.Context, arg2 livekit.RoomName) (*livekit.Node, error) {
fake.getNodeForRoomMutex.Lock()
ret, specificReturn := fake.getNodeForRoomReturnsOnCall[len(fake.getNodeForRoomArgsForCall)]
fake.getNodeForRoomArgsForCall = append(fake.getNodeForRoomArgsForCall, struct {
arg1 context.Context
arg2 livekit.RoomName
}{arg1, arg2})
stub := fake.GetNodeForRoomStub
fakeReturns := fake.getNodeForRoomReturns
fake.recordInvocation("GetNodeForRoom", []interface{}{arg1, arg2})
fake.getNodeForRoomMutex.Unlock()
if stub != nil {
return stub(arg1, arg2)
}
if specificReturn {
return ret.result1, ret.result2
}
return fakeReturns.result1, fakeReturns.result2
}
func (fake *FakeRouter) GetNodeForRoomCallCount() int {
fake.getNodeForRoomMutex.RLock()
defer fake.getNodeForRoomMutex.RUnlock()
return len(fake.getNodeForRoomArgsForCall)
}
func (fake *FakeRouter) GetNodeForRoomCalls(stub func(context.Context, livekit.RoomName) (*livekit.Node, error)) {
fake.getNodeForRoomMutex.Lock()
defer fake.getNodeForRoomMutex.Unlock()
fake.GetNodeForRoomStub = stub
}
func (fake *FakeRouter) GetNodeForRoomArgsForCall(i int) (context.Context, livekit.RoomName) {
fake.getNodeForRoomMutex.RLock()
defer fake.getNodeForRoomMutex.RUnlock()
argsForCall := fake.getNodeForRoomArgsForCall[i]
return argsForCall.arg1, argsForCall.arg2
}
func (fake *FakeRouter) GetNodeForRoomReturns(result1 *livekit.Node, result2 error) {
fake.getNodeForRoomMutex.Lock()
defer fake.getNodeForRoomMutex.Unlock()
fake.GetNodeForRoomStub = nil
fake.getNodeForRoomReturns = struct {
result1 *livekit.Node
result2 error
}{result1, result2}
}
func (fake *FakeRouter) GetNodeForRoomReturnsOnCall(i int, result1 *livekit.Node, result2 error) {
fake.getNodeForRoomMutex.Lock()
defer fake.getNodeForRoomMutex.Unlock()
fake.GetNodeForRoomStub = nil
if fake.getNodeForRoomReturnsOnCall == nil {
fake.getNodeForRoomReturnsOnCall = make(map[int]struct {
result1 *livekit.Node
result2 error
})
}
fake.getNodeForRoomReturnsOnCall[i] = struct {
result1 *livekit.Node
result2 error
}{result1, result2}
}
func (fake *FakeRouter) GetRegion() string {
fake.getRegionMutex.Lock()
ret, specificReturn := fake.getRegionReturnsOnCall[len(fake.getRegionArgsForCall)]
fake.getRegionArgsForCall = append(fake.getRegionArgsForCall, struct {
}{})
stub := fake.GetRegionStub
fakeReturns := fake.getRegionReturns
fake.recordInvocation("GetRegion", []interface{}{})
fake.getRegionMutex.Unlock()
if stub != nil {
return stub()
}
if specificReturn {
return ret.result1
}
return fakeReturns.result1
}
func (fake *FakeRouter) GetRegionCallCount() int {
fake.getRegionMutex.RLock()
defer fake.getRegionMutex.RUnlock()
return len(fake.getRegionArgsForCall)
}
func (fake *FakeRouter) GetRegionCalls(stub func() string) {
fake.getRegionMutex.Lock()
defer fake.getRegionMutex.Unlock()
fake.GetRegionStub = stub
}
func (fake *FakeRouter) GetRegionReturns(result1 string) {
fake.getRegionMutex.Lock()
defer fake.getRegionMutex.Unlock()
fake.GetRegionStub = nil
fake.getRegionReturns = struct {
result1 string
}{result1}
}
func (fake *FakeRouter) GetRegionReturnsOnCall(i int, result1 string) {
fake.getRegionMutex.Lock()
defer fake.getRegionMutex.Unlock()
fake.GetRegionStub = nil
if fake.getRegionReturnsOnCall == nil {
fake.getRegionReturnsOnCall = make(map[int]struct {
result1 string
})
}
fake.getRegionReturnsOnCall[i] = struct {
result1 string
}{result1}
}
func (fake *FakeRouter) ListNodes() ([]*livekit.Node, error) {
fake.listNodesMutex.Lock()
ret, specificReturn := fake.listNodesReturnsOnCall[len(fake.listNodesArgsForCall)]
fake.listNodesArgsForCall = append(fake.listNodesArgsForCall, struct {
}{})
stub := fake.ListNodesStub
fakeReturns := fake.listNodesReturns
fake.recordInvocation("ListNodes", []interface{}{})
fake.listNodesMutex.Unlock()
if stub != nil {
return stub()
}
if specificReturn {
return ret.result1, ret.result2
}
return fakeReturns.result1, fakeReturns.result2
}
func (fake *FakeRouter) ListNodesCallCount() int {
fake.listNodesMutex.RLock()
defer fake.listNodesMutex.RUnlock()
return len(fake.listNodesArgsForCall)
}
func (fake *FakeRouter) ListNodesCalls(stub func() ([]*livekit.Node, error)) {
fake.listNodesMutex.Lock()
defer fake.listNodesMutex.Unlock()
fake.ListNodesStub = stub
}
func (fake *FakeRouter) ListNodesReturns(result1 []*livekit.Node, result2 error) {
fake.listNodesMutex.Lock()
defer fake.listNodesMutex.Unlock()
fake.ListNodesStub = nil
fake.listNodesReturns = struct {
result1 []*livekit.Node
result2 error
}{result1, result2}
}
func (fake *FakeRouter) ListNodesReturnsOnCall(i int, result1 []*livekit.Node, result2 error) {
fake.listNodesMutex.Lock()
defer fake.listNodesMutex.Unlock()
fake.ListNodesStub = nil
if fake.listNodesReturnsOnCall == nil {
fake.listNodesReturnsOnCall = make(map[int]struct {
result1 []*livekit.Node
result2 error
})
}
fake.listNodesReturnsOnCall[i] = struct {
result1 []*livekit.Node
result2 error
}{result1, result2}
}
func (fake *FakeRouter) RegisterNode() error {
fake.registerNodeMutex.Lock()
ret, specificReturn := fake.registerNodeReturnsOnCall[len(fake.registerNodeArgsForCall)]
fake.registerNodeArgsForCall = append(fake.registerNodeArgsForCall, struct {
}{})
stub := fake.RegisterNodeStub
fakeReturns := fake.registerNodeReturns
fake.recordInvocation("RegisterNode", []interface{}{})
fake.registerNodeMutex.Unlock()
if stub != nil {
return stub()
}
if specificReturn {
return ret.result1
}
return fakeReturns.result1
}
func (fake *FakeRouter) RegisterNodeCallCount() int {
fake.registerNodeMutex.RLock()
defer fake.registerNodeMutex.RUnlock()
return len(fake.registerNodeArgsForCall)
}
func (fake *FakeRouter) RegisterNodeCalls(stub func() error) {
fake.registerNodeMutex.Lock()
defer fake.registerNodeMutex.Unlock()
fake.RegisterNodeStub = stub
}
func (fake *FakeRouter) RegisterNodeReturns(result1 error) {
fake.registerNodeMutex.Lock()
defer fake.registerNodeMutex.Unlock()
fake.RegisterNodeStub = nil
fake.registerNodeReturns = struct {
result1 error
}{result1}
}
func (fake *FakeRouter) RegisterNodeReturnsOnCall(i int, result1 error) {
fake.registerNodeMutex.Lock()
defer fake.registerNodeMutex.Unlock()
fake.RegisterNodeStub = nil
if fake.registerNodeReturnsOnCall == nil {
fake.registerNodeReturnsOnCall = make(map[int]struct {
result1 error
})
}
fake.registerNodeReturnsOnCall[i] = struct {
result1 error
}{result1}
}
func (fake *FakeRouter) RemoveDeadNodes() error {
fake.removeDeadNodesMutex.Lock()
ret, specificReturn := fake.removeDeadNodesReturnsOnCall[len(fake.removeDeadNodesArgsForCall)]
fake.removeDeadNodesArgsForCall = append(fake.removeDeadNodesArgsForCall, struct {
}{})
stub := fake.RemoveDeadNodesStub
fakeReturns := fake.removeDeadNodesReturns
fake.recordInvocation("RemoveDeadNodes", []interface{}{})
fake.removeDeadNodesMutex.Unlock()
if stub != nil {
return stub()
}
if specificReturn {
return ret.result1
}
return fakeReturns.result1
}
func (fake *FakeRouter) RemoveDeadNodesCallCount() int {
fake.removeDeadNodesMutex.RLock()
defer fake.removeDeadNodesMutex.RUnlock()
return len(fake.removeDeadNodesArgsForCall)
}
func (fake *FakeRouter) RemoveDeadNodesCalls(stub func() error) {
fake.removeDeadNodesMutex.Lock()
defer fake.removeDeadNodesMutex.Unlock()
fake.RemoveDeadNodesStub = stub
}
func (fake *FakeRouter) RemoveDeadNodesReturns(result1 error) {
fake.removeDeadNodesMutex.Lock()
defer fake.removeDeadNodesMutex.Unlock()
fake.RemoveDeadNodesStub = nil
fake.removeDeadNodesReturns = struct {
result1 error
}{result1}
}
func (fake *FakeRouter) RemoveDeadNodesReturnsOnCall(i int, result1 error) {
fake.removeDeadNodesMutex.Lock()
defer fake.removeDeadNodesMutex.Unlock()
fake.RemoveDeadNodesStub = nil
if fake.removeDeadNodesReturnsOnCall == nil {
fake.removeDeadNodesReturnsOnCall = make(map[int]struct {
result1 error
})
}
fake.removeDeadNodesReturnsOnCall[i] = struct {
result1 error
}{result1}
}
func (fake *FakeRouter) SetNodeForRoom(arg1 context.Context, arg2 livekit.RoomName, arg3 livekit.NodeID) error {
fake.setNodeForRoomMutex.Lock()
ret, specificReturn := fake.setNodeForRoomReturnsOnCall[len(fake.setNodeForRoomArgsForCall)]
fake.setNodeForRoomArgsForCall = append(fake.setNodeForRoomArgsForCall, struct {
arg1 context.Context
arg2 livekit.RoomName
arg3 livekit.NodeID
}{arg1, arg2, arg3})
stub := fake.SetNodeForRoomStub
fakeReturns := fake.setNodeForRoomReturns
fake.recordInvocation("SetNodeForRoom", []interface{}{arg1, arg2, arg3})
fake.setNodeForRoomMutex.Unlock()
if stub != nil {
return stub(arg1, arg2, arg3)
}
if specificReturn {
return ret.result1
}
return fakeReturns.result1
}
func (fake *FakeRouter) SetNodeForRoomCallCount() int {
fake.setNodeForRoomMutex.RLock()
defer fake.setNodeForRoomMutex.RUnlock()
return len(fake.setNodeForRoomArgsForCall)
}
func (fake *FakeRouter) SetNodeForRoomCalls(stub func(context.Context, livekit.RoomName, livekit.NodeID) error) {
fake.setNodeForRoomMutex.Lock()
defer fake.setNodeForRoomMutex.Unlock()
fake.SetNodeForRoomStub = stub
}
func (fake *FakeRouter) SetNodeForRoomArgsForCall(i int) (context.Context, livekit.RoomName, livekit.NodeID) {
fake.setNodeForRoomMutex.RLock()
defer fake.setNodeForRoomMutex.RUnlock()
argsForCall := fake.setNodeForRoomArgsForCall[i]
return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3
}
func (fake *FakeRouter) SetNodeForRoomReturns(result1 error) {
fake.setNodeForRoomMutex.Lock()
defer fake.setNodeForRoomMutex.Unlock()
fake.SetNodeForRoomStub = nil
fake.setNodeForRoomReturns = struct {
result1 error
}{result1}
}
func (fake *FakeRouter) SetNodeForRoomReturnsOnCall(i int, result1 error) {
fake.setNodeForRoomMutex.Lock()
defer fake.setNodeForRoomMutex.Unlock()
fake.SetNodeForRoomStub = nil
if fake.setNodeForRoomReturnsOnCall == nil {
fake.setNodeForRoomReturnsOnCall = make(map[int]struct {
result1 error
})
}
fake.setNodeForRoomReturnsOnCall[i] = struct {
result1 error
}{result1}
}
func (fake *FakeRouter) Start() error {
fake.startMutex.Lock()
ret, specificReturn := fake.startReturnsOnCall[len(fake.startArgsForCall)]
fake.startArgsForCall = append(fake.startArgsForCall, struct {
}{})
stub := fake.StartStub
fakeReturns := fake.startReturns
fake.recordInvocation("Start", []interface{}{})
fake.startMutex.Unlock()
if stub != nil {
return stub()
}
if specificReturn {
return ret.result1
}
return fakeReturns.result1
}
func (fake *FakeRouter) StartCallCount() int {
fake.startMutex.RLock()
defer fake.startMutex.RUnlock()
return len(fake.startArgsForCall)
}
func (fake *FakeRouter) StartCalls(stub func() error) {
fake.startMutex.Lock()
defer fake.startMutex.Unlock()
fake.StartStub = stub
}
func (fake *FakeRouter) StartReturns(result1 error) {
fake.startMutex.Lock()
defer fake.startMutex.Unlock()
fake.StartStub = nil
fake.startReturns = struct {
result1 error
}{result1}
}
func (fake *FakeRouter) StartReturnsOnCall(i int, result1 error) {
fake.startMutex.Lock()
defer fake.startMutex.Unlock()
fake.StartStub = nil
if fake.startReturnsOnCall == nil {
fake.startReturnsOnCall = make(map[int]struct {
result1 error
})
}
fake.startReturnsOnCall[i] = struct {
result1 error
}{result1}
}
func (fake *FakeRouter) StartParticipantSignal(arg1 context.Context, arg2 livekit.RoomName, arg3 routing.ParticipantInit) (routing.StartParticipantSignalResults, error) {
fake.startParticipantSignalMutex.Lock()
ret, specificReturn := fake.startParticipantSignalReturnsOnCall[len(fake.startParticipantSignalArgsForCall)]
fake.startParticipantSignalArgsForCall = append(fake.startParticipantSignalArgsForCall, struct {
arg1 context.Context
arg2 livekit.RoomName
arg3 routing.ParticipantInit
}{arg1, arg2, arg3})
stub := fake.StartParticipantSignalStub
fakeReturns := fake.startParticipantSignalReturns
fake.recordInvocation("StartParticipantSignal", []interface{}{arg1, arg2, arg3})
fake.startParticipantSignalMutex.Unlock()
if stub != nil {
return stub(arg1, arg2, arg3)
}
if specificReturn {
return ret.result1, ret.result2
}
return fakeReturns.result1, fakeReturns.result2
}
func (fake *FakeRouter) StartParticipantSignalCallCount() int {
fake.startParticipantSignalMutex.RLock()
defer fake.startParticipantSignalMutex.RUnlock()
return len(fake.startParticipantSignalArgsForCall)
}
func (fake *FakeRouter) StartParticipantSignalCalls(stub func(context.Context, livekit.RoomName, routing.ParticipantInit) (routing.StartParticipantSignalResults, error)) {
fake.startParticipantSignalMutex.Lock()
defer fake.startParticipantSignalMutex.Unlock()
fake.StartParticipantSignalStub = stub
}
func (fake *FakeRouter) StartParticipantSignalArgsForCall(i int) (context.Context, livekit.RoomName, routing.ParticipantInit) {
fake.startParticipantSignalMutex.RLock()
defer fake.startParticipantSignalMutex.RUnlock()
argsForCall := fake.startParticipantSignalArgsForCall[i]
return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3
}
func (fake *FakeRouter) StartParticipantSignalReturns(result1 routing.StartParticipantSignalResults, result2 error) {
fake.startParticipantSignalMutex.Lock()
defer fake.startParticipantSignalMutex.Unlock()
fake.StartParticipantSignalStub = nil
fake.startParticipantSignalReturns = struct {
result1 routing.StartParticipantSignalResults
result2 error
}{result1, result2}
}
func (fake *FakeRouter) StartParticipantSignalReturnsOnCall(i int, result1 routing.StartParticipantSignalResults, result2 error) {
fake.startParticipantSignalMutex.Lock()
defer fake.startParticipantSignalMutex.Unlock()
fake.StartParticipantSignalStub = nil
if fake.startParticipantSignalReturnsOnCall == nil {
fake.startParticipantSignalReturnsOnCall = make(map[int]struct {
result1 routing.StartParticipantSignalResults
result2 error
})
}
fake.startParticipantSignalReturnsOnCall[i] = struct {
result1 routing.StartParticipantSignalResults
result2 error
}{result1, result2}
}
func (fake *FakeRouter) Stop() {
fake.stopMutex.Lock()
fake.stopArgsForCall = append(fake.stopArgsForCall, struct {
}{})
stub := fake.StopStub
fake.recordInvocation("Stop", []interface{}{})
fake.stopMutex.Unlock()
if stub != nil {
fake.StopStub()
}
}
func (fake *FakeRouter) StopCallCount() int {
fake.stopMutex.RLock()
defer fake.stopMutex.RUnlock()
return len(fake.stopArgsForCall)
}
func (fake *FakeRouter) StopCalls(stub func()) {
fake.stopMutex.Lock()
defer fake.stopMutex.Unlock()
fake.StopStub = stub
}
func (fake *FakeRouter) UnregisterNode() error {
fake.unregisterNodeMutex.Lock()
ret, specificReturn := fake.unregisterNodeReturnsOnCall[len(fake.unregisterNodeArgsForCall)]
fake.unregisterNodeArgsForCall = append(fake.unregisterNodeArgsForCall, struct {
}{})
stub := fake.UnregisterNodeStub
fakeReturns := fake.unregisterNodeReturns
fake.recordInvocation("UnregisterNode", []interface{}{})
fake.unregisterNodeMutex.Unlock()
if stub != nil {
return stub()
}
if specificReturn {
return ret.result1
}
return fakeReturns.result1
}
func (fake *FakeRouter) UnregisterNodeCallCount() int {
fake.unregisterNodeMutex.RLock()
defer fake.unregisterNodeMutex.RUnlock()
return len(fake.unregisterNodeArgsForCall)
}
func (fake *FakeRouter) UnregisterNodeCalls(stub func() error) {
fake.unregisterNodeMutex.Lock()
defer fake.unregisterNodeMutex.Unlock()
fake.UnregisterNodeStub = stub
}
func (fake *FakeRouter) UnregisterNodeReturns(result1 error) {
fake.unregisterNodeMutex.Lock()
defer fake.unregisterNodeMutex.Unlock()
fake.UnregisterNodeStub = nil
fake.unregisterNodeReturns = struct {
result1 error
}{result1}
}
func (fake *FakeRouter) UnregisterNodeReturnsOnCall(i int, result1 error) {
fake.unregisterNodeMutex.Lock()
defer fake.unregisterNodeMutex.Unlock()
fake.UnregisterNodeStub = nil
if fake.unregisterNodeReturnsOnCall == nil {
fake.unregisterNodeReturnsOnCall = make(map[int]struct {
result1 error
})
}
fake.unregisterNodeReturnsOnCall[i] = struct {
result1 error
}{result1}
}
func (fake *FakeRouter) Invocations() map[string][][]interface{} {
fake.invocationsMutex.RLock()
defer fake.invocationsMutex.RUnlock()
fake.clearRoomStateMutex.RLock()
defer fake.clearRoomStateMutex.RUnlock()
fake.createRoomMutex.RLock()
defer fake.createRoomMutex.RUnlock()
fake.drainMutex.RLock()
defer fake.drainMutex.RUnlock()
fake.getNodeForRoomMutex.RLock()
defer fake.getNodeForRoomMutex.RUnlock()
fake.getRegionMutex.RLock()
defer fake.getRegionMutex.RUnlock()
fake.listNodesMutex.RLock()
defer fake.listNodesMutex.RUnlock()
fake.registerNodeMutex.RLock()
defer fake.registerNodeMutex.RUnlock()
fake.removeDeadNodesMutex.RLock()
defer fake.removeDeadNodesMutex.RUnlock()
fake.setNodeForRoomMutex.RLock()
defer fake.setNodeForRoomMutex.RUnlock()
fake.startMutex.RLock()
defer fake.startMutex.RUnlock()
fake.startParticipantSignalMutex.RLock()
defer fake.startParticipantSignalMutex.RUnlock()
fake.stopMutex.RLock()
defer fake.stopMutex.RUnlock()
fake.unregisterNodeMutex.RLock()
defer fake.unregisterNodeMutex.RUnlock()
copiedInvocations := map[string][][]interface{}{}
for key, value := range fake.invocations {
copiedInvocations[key] = value
}
return copiedInvocations
}
func (fake *FakeRouter) recordInvocation(key string, args []interface{}) {
fake.invocationsMutex.Lock()
defer fake.invocationsMutex.Unlock()
if fake.invocations == nil {
fake.invocations = map[string][][]interface{}{}
}
if fake.invocations[key] == nil {
fake.invocations[key] = [][]interface{}{}
}
fake.invocations[key] = append(fake.invocations[key], args)
}
var _ routing.Router = new(FakeRouter)
@@ -0,0 +1,199 @@
// Code generated by counterfeiter. DO NOT EDIT.
package routingfakes
import (
"context"
"sync"
"github.com/livekit/livekit-server/pkg/routing"
"github.com/livekit/protocol/livekit"
)
type FakeSignalClient struct {
ActiveCountStub func() int
activeCountMutex sync.RWMutex
activeCountArgsForCall []struct {
}
activeCountReturns struct {
result1 int
}
activeCountReturnsOnCall map[int]struct {
result1 int
}
StartParticipantSignalStub func(context.Context, livekit.RoomName, routing.ParticipantInit, livekit.NodeID) (livekit.ConnectionID, routing.MessageSink, routing.MessageSource, error)
startParticipantSignalMutex sync.RWMutex
startParticipantSignalArgsForCall []struct {
arg1 context.Context
arg2 livekit.RoomName
arg3 routing.ParticipantInit
arg4 livekit.NodeID
}
startParticipantSignalReturns struct {
result1 livekit.ConnectionID
result2 routing.MessageSink
result3 routing.MessageSource
result4 error
}
startParticipantSignalReturnsOnCall map[int]struct {
result1 livekit.ConnectionID
result2 routing.MessageSink
result3 routing.MessageSource
result4 error
}
invocations map[string][][]interface{}
invocationsMutex sync.RWMutex
}
func (fake *FakeSignalClient) ActiveCount() int {
fake.activeCountMutex.Lock()
ret, specificReturn := fake.activeCountReturnsOnCall[len(fake.activeCountArgsForCall)]
fake.activeCountArgsForCall = append(fake.activeCountArgsForCall, struct {
}{})
stub := fake.ActiveCountStub
fakeReturns := fake.activeCountReturns
fake.recordInvocation("ActiveCount", []interface{}{})
fake.activeCountMutex.Unlock()
if stub != nil {
return stub()
}
if specificReturn {
return ret.result1
}
return fakeReturns.result1
}
func (fake *FakeSignalClient) ActiveCountCallCount() int {
fake.activeCountMutex.RLock()
defer fake.activeCountMutex.RUnlock()
return len(fake.activeCountArgsForCall)
}
func (fake *FakeSignalClient) ActiveCountCalls(stub func() int) {
fake.activeCountMutex.Lock()
defer fake.activeCountMutex.Unlock()
fake.ActiveCountStub = stub
}
func (fake *FakeSignalClient) ActiveCountReturns(result1 int) {
fake.activeCountMutex.Lock()
defer fake.activeCountMutex.Unlock()
fake.ActiveCountStub = nil
fake.activeCountReturns = struct {
result1 int
}{result1}
}
func (fake *FakeSignalClient) ActiveCountReturnsOnCall(i int, result1 int) {
fake.activeCountMutex.Lock()
defer fake.activeCountMutex.Unlock()
fake.ActiveCountStub = nil
if fake.activeCountReturnsOnCall == nil {
fake.activeCountReturnsOnCall = make(map[int]struct {
result1 int
})
}
fake.activeCountReturnsOnCall[i] = struct {
result1 int
}{result1}
}
func (fake *FakeSignalClient) StartParticipantSignal(arg1 context.Context, arg2 livekit.RoomName, arg3 routing.ParticipantInit, arg4 livekit.NodeID) (livekit.ConnectionID, routing.MessageSink, routing.MessageSource, error) {
fake.startParticipantSignalMutex.Lock()
ret, specificReturn := fake.startParticipantSignalReturnsOnCall[len(fake.startParticipantSignalArgsForCall)]
fake.startParticipantSignalArgsForCall = append(fake.startParticipantSignalArgsForCall, struct {
arg1 context.Context
arg2 livekit.RoomName
arg3 routing.ParticipantInit
arg4 livekit.NodeID
}{arg1, arg2, arg3, arg4})
stub := fake.StartParticipantSignalStub
fakeReturns := fake.startParticipantSignalReturns
fake.recordInvocation("StartParticipantSignal", []interface{}{arg1, arg2, arg3, arg4})
fake.startParticipantSignalMutex.Unlock()
if stub != nil {
return stub(arg1, arg2, arg3, arg4)
}
if specificReturn {
return ret.result1, ret.result2, ret.result3, ret.result4
}
return fakeReturns.result1, fakeReturns.result2, fakeReturns.result3, fakeReturns.result4
}
func (fake *FakeSignalClient) StartParticipantSignalCallCount() int {
fake.startParticipantSignalMutex.RLock()
defer fake.startParticipantSignalMutex.RUnlock()
return len(fake.startParticipantSignalArgsForCall)
}
func (fake *FakeSignalClient) StartParticipantSignalCalls(stub func(context.Context, livekit.RoomName, routing.ParticipantInit, livekit.NodeID) (livekit.ConnectionID, routing.MessageSink, routing.MessageSource, error)) {
fake.startParticipantSignalMutex.Lock()
defer fake.startParticipantSignalMutex.Unlock()
fake.StartParticipantSignalStub = stub
}
func (fake *FakeSignalClient) StartParticipantSignalArgsForCall(i int) (context.Context, livekit.RoomName, routing.ParticipantInit, livekit.NodeID) {
fake.startParticipantSignalMutex.RLock()
defer fake.startParticipantSignalMutex.RUnlock()
argsForCall := fake.startParticipantSignalArgsForCall[i]
return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3, argsForCall.arg4
}
func (fake *FakeSignalClient) StartParticipantSignalReturns(result1 livekit.ConnectionID, result2 routing.MessageSink, result3 routing.MessageSource, result4 error) {
fake.startParticipantSignalMutex.Lock()
defer fake.startParticipantSignalMutex.Unlock()
fake.StartParticipantSignalStub = nil
fake.startParticipantSignalReturns = struct {
result1 livekit.ConnectionID
result2 routing.MessageSink
result3 routing.MessageSource
result4 error
}{result1, result2, result3, result4}
}
func (fake *FakeSignalClient) StartParticipantSignalReturnsOnCall(i int, result1 livekit.ConnectionID, result2 routing.MessageSink, result3 routing.MessageSource, result4 error) {
fake.startParticipantSignalMutex.Lock()
defer fake.startParticipantSignalMutex.Unlock()
fake.StartParticipantSignalStub = nil
if fake.startParticipantSignalReturnsOnCall == nil {
fake.startParticipantSignalReturnsOnCall = make(map[int]struct {
result1 livekit.ConnectionID
result2 routing.MessageSink
result3 routing.MessageSource
result4 error
})
}
fake.startParticipantSignalReturnsOnCall[i] = struct {
result1 livekit.ConnectionID
result2 routing.MessageSink
result3 routing.MessageSource
result4 error
}{result1, result2, result3, result4}
}
func (fake *FakeSignalClient) Invocations() map[string][][]interface{} {
fake.invocationsMutex.RLock()
defer fake.invocationsMutex.RUnlock()
fake.activeCountMutex.RLock()
defer fake.activeCountMutex.RUnlock()
fake.startParticipantSignalMutex.RLock()
defer fake.startParticipantSignalMutex.RUnlock()
copiedInvocations := map[string][][]interface{}{}
for key, value := range fake.invocations {
copiedInvocations[key] = value
}
return copiedInvocations
}
func (fake *FakeSignalClient) recordInvocation(key string, args []interface{}) {
fake.invocationsMutex.Lock()
defer fake.invocationsMutex.Unlock()
if fake.invocations == nil {
fake.invocations = map[string][][]interface{}{}
}
if fake.invocations[key] == nil {
fake.invocations[key] = [][]interface{}{}
}
fake.invocations[key] = append(fake.invocations[key], args)
}
var _ routing.SignalClient = new(FakeSignalClient)
+33
View File
@@ -0,0 +1,33 @@
// Copyright 2023 LiveKit, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package selector
import (
"github.com/livekit/protocol/livekit"
)
// AnySelector selects any available node with no limitations
type AnySelector struct {
SortBy string
}
func (s *AnySelector) SelectNode(nodes []*livekit.Node) (*livekit.Node, error) {
nodes = GetAvailableNodes(nodes)
if len(nodes) == 0 {
return nil, ErrNoAvailableNodes
}
return SelectSortedNode(nodes, s.SortBy)
}
+54
View File
@@ -0,0 +1,54 @@
// Copyright 2023 LiveKit, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package selector
import (
"github.com/livekit/protocol/livekit"
)
// CPULoadSelector eliminates nodes that have CPU usage higher than CPULoadLimit
// then selects a node from nodes that are not overloaded
type CPULoadSelector struct {
CPULoadLimit float32
SortBy string
}
func (s *CPULoadSelector) filterNodes(nodes []*livekit.Node) ([]*livekit.Node, error) {
nodes = GetAvailableNodes(nodes)
if len(nodes) == 0 {
return nil, ErrNoAvailableNodes
}
nodesLowLoad := make([]*livekit.Node, 0)
for _, node := range nodes {
stats := node.Stats
if stats.CpuLoad < s.CPULoadLimit {
nodesLowLoad = append(nodesLowLoad, node)
}
}
if len(nodesLowLoad) > 0 {
nodes = nodesLowLoad
}
return nodes, nil
}
func (s *CPULoadSelector) SelectNode(nodes []*livekit.Node) (*livekit.Node, error) {
nodes, err := s.filterNodes(nodes)
if err != nil {
return nil, err
}
return SelectSortedNode(nodes, s.SortBy)
}
+51
View File
@@ -0,0 +1,51 @@
// Copyright 2023 LiveKit, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package selector_test
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/livekit/protocol/livekit"
"github.com/livekit/livekit-server/pkg/routing/selector"
)
func TestCPULoadSelector_SelectNode(t *testing.T) {
sel := selector.CPULoadSelector{CPULoadLimit: 0.8, SortBy: "random"}
var nodes []*livekit.Node
_, err := sel.SelectNode(nodes)
require.Error(t, err, "should error no available nodes")
// Select a node with high load when no nodes with low load are available
nodes = []*livekit.Node{nodeLoadHigh}
if _, err := sel.SelectNode(nodes); err != nil {
t.Error(err)
}
// Select a node with low load when available
nodes = []*livekit.Node{nodeLoadLow, nodeLoadHigh}
for i := 0; i < 5; i++ {
node, err := sel.SelectNode(nodes)
if err != nil {
t.Error(err)
}
if node != nodeLoadLow {
t.Error("selected the wrong node")
}
}
}
+25
View File
@@ -0,0 +1,25 @@
// Copyright 2023 LiveKit, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package selector
import "errors"
var (
ErrNoAvailableNodes = errors.New("could not find any available nodes")
ErrCurrentRegionNotSet = errors.New("current region cannot be blank")
ErrCurrentRegionUnknownLatLon = errors.New("unknown lat and lon for the current region")
ErrSortByNotSet = errors.New("sort by option cannot be blank")
ErrSortByUnknown = errors.New("unknown sort by option")
)
+64
View File
@@ -0,0 +1,64 @@
// Copyright 2023 LiveKit, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package selector
import (
"errors"
"github.com/livekit/protocol/livekit"
"github.com/livekit/protocol/logger"
"github.com/livekit/livekit-server/pkg/config"
)
var ErrUnsupportedSelector = errors.New("unsupported node selector")
// NodeSelector selects an appropriate node to run the current session
type NodeSelector interface {
SelectNode(nodes []*livekit.Node) (*livekit.Node, error)
}
func CreateNodeSelector(conf *config.Config) (NodeSelector, error) {
kind := conf.NodeSelector.Kind
if kind == "" {
kind = "any"
}
switch kind {
case "any":
return &AnySelector{conf.NodeSelector.SortBy}, nil
case "cpuload":
return &CPULoadSelector{
CPULoadLimit: conf.NodeSelector.CPULoadLimit,
SortBy: conf.NodeSelector.SortBy,
}, nil
case "sysload":
return &SystemLoadSelector{
SysloadLimit: conf.NodeSelector.SysloadLimit,
SortBy: conf.NodeSelector.SortBy,
}, nil
case "regionaware":
s, err := NewRegionAwareSelector(conf.Region, conf.NodeSelector.Regions, conf.NodeSelector.SortBy)
if err != nil {
return nil, err
}
s.SysloadLimit = conf.NodeSelector.SysloadLimit
return s, nil
case "random":
logger.Warnw("random node selector is deprecated, please switch to \"any\" or another selector", nil)
return &AnySelector{conf.NodeSelector.SortBy}, nil
default:
return nil, ErrUnsupportedSelector
}
}
+125
View File
@@ -0,0 +1,125 @@
// Copyright 2023 LiveKit, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package selector
import (
"math"
"github.com/livekit/protocol/livekit"
"github.com/livekit/livekit-server/pkg/config"
)
// RegionAwareSelector prefers available nodes that are closest to the region of the current instance
type RegionAwareSelector struct {
SystemLoadSelector
CurrentRegion string
regionDistances map[string]float64
regions []config.RegionConfig
SortBy string
}
func NewRegionAwareSelector(currentRegion string, regions []config.RegionConfig, sortBy string) (*RegionAwareSelector, error) {
if currentRegion == "" {
return nil, ErrCurrentRegionNotSet
}
// build internal map of distances
s := &RegionAwareSelector{
CurrentRegion: currentRegion,
regionDistances: make(map[string]float64),
regions: regions,
SortBy: sortBy,
}
var currentRC *config.RegionConfig
for _, region := range regions {
if region.Name == currentRegion {
currentRC = &region
break
}
}
if currentRC == nil && len(regions) > 0 {
return nil, ErrCurrentRegionUnknownLatLon
}
if currentRC != nil {
for _, region := range regions {
s.regionDistances[region.Name] = distanceBetween(currentRC.Lat, currentRC.Lon, region.Lat, region.Lon)
}
}
return s, nil
}
func (s *RegionAwareSelector) SelectNode(nodes []*livekit.Node) (*livekit.Node, error) {
nodes, err := s.SystemLoadSelector.filterNodes(nodes)
if err != nil {
return nil, err
}
// find nodes nearest to current region
var nearestNodes []*livekit.Node
nearestRegion := ""
minDist := math.MaxFloat64
for _, node := range nodes {
if node.Region == nearestRegion {
nearestNodes = append(nearestNodes, node)
continue
}
if dist, ok := s.regionDistances[node.Region]; ok {
if dist < minDist {
minDist = dist
nearestRegion = node.Region
nearestNodes = nearestNodes[:0]
nearestNodes = append(nearestNodes, node)
}
}
}
if len(nearestNodes) > 0 {
nodes = nearestNodes
}
return SelectSortedNode(nodes, s.SortBy)
}
// haversine(θ) function
func hsin(theta float64) float64 {
return math.Pow(math.Sin(theta/2), 2)
}
var piBy180 = math.Pi / 180
// Haversine Distance Formula
// http://en.wikipedia.org/wiki/Haversine_formula
// from https://gist.github.com/cdipaolo/d3f8db3848278b49db68
func distanceBetween(lat1, lon1, lat2, lon2 float64) float64 {
// convert to radians
// must cast radius as float to multiply later
var la1, lo1, la2, lo2, r float64
la1 = lat1 * piBy180
lo1 = lon1 * piBy180
la2 = lat2 * piBy180
lo2 = lon2 * piBy180
r = 6378100 // Earth radius in METERS
// calculate
h := hsin(la2-la1) + math.Cos(la1)*math.Cos(la2)*hsin(lo2-lo1)
return 2 * r * math.Asin(math.Sqrt(h))
}
+165
View File
@@ -0,0 +1,165 @@
// Copyright 2023 LiveKit, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package selector_test
import (
"testing"
"time"
"github.com/stretchr/testify/require"
"github.com/livekit/protocol/livekit"
"github.com/livekit/protocol/utils"
"github.com/livekit/protocol/utils/guid"
"github.com/livekit/livekit-server/pkg/config"
"github.com/livekit/livekit-server/pkg/routing/selector"
)
const (
loadLimit = 0.5
regionWest = "us-west"
regionEast = "us-east"
regionSeattle = "seattle"
sortBy = "random"
)
func TestRegionAwareRouting(t *testing.T) {
rc := []config.RegionConfig{
{
Name: regionWest,
Lat: 37.64046607830567,
Lon: -120.88026233189062,
},
{
Name: regionEast,
Lat: 40.68914362140307,
Lon: -74.04445748616385,
},
{
Name: regionSeattle,
Lat: 47.620426730945454,
Lon: -122.34938468973702,
},
}
t.Run("works without region config", func(t *testing.T) {
nodes := []*livekit.Node{
newTestNodeInRegion("", false),
}
s, err := selector.NewRegionAwareSelector(regionEast, nil, sortBy)
require.NoError(t, err)
node, err := s.SelectNode(nodes)
require.NoError(t, err)
require.NotNil(t, node)
})
t.Run("picks available nodes in same region", func(t *testing.T) {
expectedNode := newTestNodeInRegion(regionEast, true)
nodes := []*livekit.Node{
newTestNodeInRegion(regionSeattle, true),
newTestNodeInRegion(regionWest, true),
expectedNode,
newTestNodeInRegion(regionEast, false),
}
s, err := selector.NewRegionAwareSelector(regionEast, rc, sortBy)
require.NoError(t, err)
s.SysloadLimit = loadLimit
node, err := s.SelectNode(nodes)
require.NoError(t, err)
require.Equal(t, expectedNode, node)
})
t.Run("picks available nodes in same region when current node is first in the list", func(t *testing.T) {
expectedNode := newTestNodeInRegion(regionEast, true)
nodes := []*livekit.Node{
expectedNode,
newTestNodeInRegion(regionSeattle, true),
newTestNodeInRegion(regionWest, true),
newTestNodeInRegion(regionEast, false),
}
s, err := selector.NewRegionAwareSelector(regionEast, rc, sortBy)
require.NoError(t, err)
s.SysloadLimit = loadLimit
node, err := s.SelectNode(nodes)
require.NoError(t, err)
require.Equal(t, expectedNode, node)
})
t.Run("picks closest node in a diff region", func(t *testing.T) {
expectedNode := newTestNodeInRegion(regionWest, true)
nodes := []*livekit.Node{
newTestNodeInRegion(regionSeattle, false),
expectedNode,
newTestNodeInRegion(regionEast, true),
}
s, err := selector.NewRegionAwareSelector(regionSeattle, rc, sortBy)
require.NoError(t, err)
s.SysloadLimit = loadLimit
node, err := s.SelectNode(nodes)
require.NoError(t, err)
require.Equal(t, expectedNode, node)
})
t.Run("handles multiple nodes in same region", func(t *testing.T) {
expectedNode := newTestNodeInRegion(regionWest, true)
nodes := []*livekit.Node{
newTestNodeInRegion(regionSeattle, false),
newTestNodeInRegion(regionEast, true),
newTestNodeInRegion(regionEast, true),
expectedNode,
expectedNode,
}
s, err := selector.NewRegionAwareSelector(regionSeattle, rc, sortBy)
require.NoError(t, err)
s.SysloadLimit = loadLimit
node, err := s.SelectNode(nodes)
require.NoError(t, err)
require.Equal(t, expectedNode, node)
})
t.Run("functions when current region is full", func(t *testing.T) {
nodes := []*livekit.Node{
newTestNodeInRegion(regionWest, true),
}
s, err := selector.NewRegionAwareSelector(regionEast, rc, sortBy)
require.NoError(t, err)
node, err := s.SelectNode(nodes)
require.NoError(t, err)
require.NotNil(t, node)
})
}
func newTestNodeInRegion(region string, available bool) *livekit.Node {
load := float32(0.4)
if !available {
load = 1.0
}
return &livekit.Node{
Id: guid.New(utils.NodePrefix),
Region: region,
State: livekit.NodeState_SERVING,
Stats: &livekit.NodeStats{
UpdatedAt: time.Now().Unix(),
NumCpus: 1,
LoadAvgLast1Min: load,
},
}
}
+64
View File
@@ -0,0 +1,64 @@
// Copyright 2023 LiveKit, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package selector_test
import (
"testing"
"github.com/livekit/protocol/livekit"
"github.com/livekit/livekit-server/pkg/routing/selector"
)
func SortByTest(t *testing.T, sortBy string) {
sel := selector.SystemLoadSelector{SortBy: sortBy}
nodes := []*livekit.Node{nodeLoadLow, nodeLoadMedium, nodeLoadHigh}
for i := 0; i < 5; i++ {
node, err := sel.SelectNode(nodes)
if err != nil {
t.Error(err)
}
if node != nodeLoadLow {
t.Error("selected the wrong node for SortBy:", sortBy)
}
}
}
func TestSortByErrors(t *testing.T) {
sel := selector.SystemLoadSelector{}
nodes := []*livekit.Node{nodeLoadLow, nodeLoadMedium, nodeLoadHigh}
// Test unset sort by option error
_, err := sel.SelectNode(nodes)
if err != selector.ErrSortByNotSet {
t.Error("shouldn't allow empty sortBy")
}
// Test unknown sort by option error
sel.SortBy = "testFail"
_, err = sel.SelectNode(nodes)
if err != selector.ErrSortByUnknown {
t.Error("shouldn't allow unknown sortBy")
}
}
func TestSortBy(t *testing.T) {
sortByTests := []string{"sysload", "cpuload", "rooms", "clients", "tracks", "bytespersec"}
for _, sortBy := range sortByTests {
SortByTest(t, sortBy)
}
}
+53
View File
@@ -0,0 +1,53 @@
// Copyright 2023 LiveKit, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package selector
import (
"github.com/livekit/protocol/livekit"
)
// SystemLoadSelector eliminates nodes that surpass has a per-cpu node higher than SysloadLimit
// then selects a node from nodes that are not overloaded
type SystemLoadSelector struct {
SysloadLimit float32
SortBy string
}
func (s *SystemLoadSelector) filterNodes(nodes []*livekit.Node) ([]*livekit.Node, error) {
nodes = GetAvailableNodes(nodes)
if len(nodes) == 0 {
return nil, ErrNoAvailableNodes
}
nodesLowLoad := make([]*livekit.Node, 0)
for _, node := range nodes {
if GetNodeSysload(node) < s.SysloadLimit {
nodesLowLoad = append(nodesLowLoad, node)
}
}
if len(nodesLowLoad) > 0 {
nodes = nodesLowLoad
}
return nodes, nil
}
func (s *SystemLoadSelector) SelectNode(nodes []*livekit.Node) (*livekit.Node, error) {
nodes, err := s.filterNodes(nodes)
if err != nil {
return nil, err
}
return SelectSortedNode(nodes, s.SortBy)
}
+102
View File
@@ -0,0 +1,102 @@
// Copyright 2023 LiveKit, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package selector_test
import (
"testing"
"time"
"github.com/stretchr/testify/require"
"github.com/livekit/protocol/livekit"
"github.com/livekit/livekit-server/pkg/routing/selector"
)
var (
nodeLoadLow = &livekit.Node{
State: livekit.NodeState_SERVING,
Stats: &livekit.NodeStats{
UpdatedAt: time.Now().Unix(),
NumCpus: 1,
CpuLoad: 0.1,
LoadAvgLast1Min: 0.0,
NumRooms: 1,
NumClients: 2,
NumTracksIn: 4,
NumTracksOut: 8,
BytesInPerSec: 1000,
BytesOutPerSec: 2000,
},
}
nodeLoadMedium = &livekit.Node{
State: livekit.NodeState_SERVING,
Stats: &livekit.NodeStats{
UpdatedAt: time.Now().Unix(),
NumCpus: 1,
CpuLoad: 0.5,
LoadAvgLast1Min: 0.5,
NumRooms: 5,
NumClients: 10,
NumTracksIn: 20,
NumTracksOut: 200,
BytesInPerSec: 5000,
BytesOutPerSec: 10000,
},
}
nodeLoadHigh = &livekit.Node{
State: livekit.NodeState_SERVING,
Stats: &livekit.NodeStats{
UpdatedAt: time.Now().Unix(),
NumCpus: 1,
CpuLoad: 0.99,
LoadAvgLast1Min: 2.0,
NumRooms: 10,
NumClients: 20,
NumTracksIn: 40,
NumTracksOut: 800,
BytesInPerSec: 10000,
BytesOutPerSec: 40000,
},
}
)
func TestSystemLoadSelector_SelectNode(t *testing.T) {
sel := selector.SystemLoadSelector{SysloadLimit: 1.0, SortBy: "random"}
var nodes []*livekit.Node
_, err := sel.SelectNode(nodes)
require.Error(t, err, "should error no available nodes")
// Select a node with high load when no nodes with low load are available
nodes = []*livekit.Node{nodeLoadHigh}
if _, err := sel.SelectNode(nodes); err != nil {
t.Error(err)
}
// Select a node with low load when available
nodes = []*livekit.Node{nodeLoadLow, nodeLoadHigh}
for i := 0; i < 5; i++ {
node, err := sel.SelectNode(nodes)
if err != nil {
t.Error(err)
}
if node != nodeLoadLow {
t.Error("selected the wrong node")
}
}
}
+115
View File
@@ -0,0 +1,115 @@
// Copyright 2023 LiveKit, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package selector
import (
"sort"
"time"
"github.com/thoas/go-funk"
"github.com/livekit/protocol/livekit"
"github.com/livekit/livekit-server/pkg/config"
)
const AvailableSeconds = 5
// checks if a node has been updated recently to be considered for selection
func IsAvailable(node *livekit.Node) bool {
if node.Stats == nil {
// available till stats are available
return true
}
delta := time.Now().Unix() - node.Stats.UpdatedAt
return int(delta) < AvailableSeconds
}
func GetAvailableNodes(nodes []*livekit.Node) []*livekit.Node {
return funk.Filter(nodes, func(node *livekit.Node) bool {
return IsAvailable(node) && node.State == livekit.NodeState_SERVING
}).([]*livekit.Node)
}
func GetNodeSysload(node *livekit.Node) float32 {
stats := node.Stats
numCpus := stats.NumCpus
if numCpus == 0 {
numCpus = 1
}
return stats.LoadAvgLast1Min / float32(numCpus)
}
// TODO: check remote node configured limit, instead of this node's config
func LimitsReached(limitConfig config.LimitConfig, nodeStats *livekit.NodeStats) bool {
if nodeStats == nil {
return false
}
if limitConfig.NumTracks > 0 && limitConfig.NumTracks <= nodeStats.NumTracksIn+nodeStats.NumTracksOut {
return true
}
if limitConfig.BytesPerSec > 0 && limitConfig.BytesPerSec <= nodeStats.BytesInPerSec+nodeStats.BytesOutPerSec {
return true
}
return false
}
func SelectSortedNode(nodes []*livekit.Node, sortBy string) (*livekit.Node, error) {
if sortBy == "" {
return nil, ErrSortByNotSet
}
// Return a node based on what it should be sorted by for priority
switch sortBy {
case "random":
idx := funk.RandomInt(0, len(nodes))
return nodes[idx], nil
case "sysload":
sort.Slice(nodes, func(i, j int) bool {
return GetNodeSysload(nodes[i]) < GetNodeSysload(nodes[j])
})
return nodes[0], nil
case "cpuload":
sort.Slice(nodes, func(i, j int) bool {
return nodes[i].Stats.CpuLoad < nodes[j].Stats.CpuLoad
})
return nodes[0], nil
case "rooms":
sort.Slice(nodes, func(i, j int) bool {
return nodes[i].Stats.NumRooms < nodes[j].Stats.NumRooms
})
return nodes[0], nil
case "clients":
sort.Slice(nodes, func(i, j int) bool {
return nodes[i].Stats.NumClients < nodes[j].Stats.NumClients
})
return nodes[0], nil
case "tracks":
sort.Slice(nodes, func(i, j int) bool {
return nodes[i].Stats.NumTracksIn+nodes[i].Stats.NumTracksOut < nodes[j].Stats.NumTracksIn+nodes[j].Stats.NumTracksOut
})
return nodes[0], nil
case "bytespersec":
sort.Slice(nodes, func(i, j int) bool {
return nodes[i].Stats.BytesInPerSec+nodes[i].Stats.BytesOutPerSec < nodes[j].Stats.BytesInPerSec+nodes[j].Stats.BytesOutPerSec
})
return nodes[0], nil
default:
return nil, ErrSortByUnknown
}
}
+46
View File
@@ -0,0 +1,46 @@
// Copyright 2023 LiveKit, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package selector_test
import (
"testing"
"time"
"github.com/stretchr/testify/require"
"github.com/livekit/protocol/livekit"
"github.com/livekit/livekit-server/pkg/routing/selector"
)
func TestIsAvailable(t *testing.T) {
t.Run("still available", func(t *testing.T) {
n := &livekit.Node{
Stats: &livekit.NodeStats{
UpdatedAt: time.Now().Unix() - 3,
},
}
require.True(t, selector.IsAvailable(n))
})
t.Run("expired", func(t *testing.T) {
n := &livekit.Node{
Stats: &livekit.NodeStats{
UpdatedAt: time.Now().Unix() - 20,
},
}
require.False(t, selector.IsAvailable(n))
})
}
+368
View File
@@ -0,0 +1,368 @@
// Copyright 2023 LiveKit, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package routing
import (
"context"
"errors"
"sync"
"time"
"go.uber.org/atomic"
"google.golang.org/protobuf/proto"
"github.com/livekit/livekit-server/pkg/config"
"github.com/livekit/livekit-server/pkg/telemetry/prometheus"
"github.com/livekit/protocol/livekit"
"github.com/livekit/protocol/logger"
"github.com/livekit/protocol/rpc"
"github.com/livekit/protocol/utils/guid"
"github.com/livekit/psrpc"
"github.com/livekit/psrpc/pkg/middleware"
)
var ErrSignalWriteFailed = errors.New("signal write failed")
var ErrSignalMessageDropped = errors.New("signal message dropped")
//counterfeiter:generate . SignalClient
type SignalClient interface {
ActiveCount() int
StartParticipantSignal(ctx context.Context, roomName livekit.RoomName, pi ParticipantInit, nodeID livekit.NodeID) (connectionID livekit.ConnectionID, reqSink MessageSink, resSource MessageSource, err error)
}
type signalClient struct {
nodeID livekit.NodeID
config config.SignalRelayConfig
client rpc.TypedSignalClient
active atomic.Int32
}
func NewSignalClient(nodeID livekit.NodeID, bus psrpc.MessageBus, config config.SignalRelayConfig) (SignalClient, error) {
c, err := rpc.NewTypedSignalClient(
nodeID,
bus,
middleware.WithClientMetrics(rpc.PSRPCMetricsObserver{}),
psrpc.WithClientChannelSize(config.StreamBufferSize),
)
if err != nil {
return nil, err
}
return &signalClient{
nodeID: nodeID,
config: config,
client: c,
}, nil
}
func (r *signalClient) ActiveCount() int {
return int(r.active.Load())
}
func (r *signalClient) StartParticipantSignal(
ctx context.Context,
roomName livekit.RoomName,
pi ParticipantInit,
nodeID livekit.NodeID,
) (
connectionID livekit.ConnectionID,
reqSink MessageSink,
resSource MessageSource,
err error,
) {
connectionID = livekit.ConnectionID(guid.New("CO_"))
ss, err := pi.ToStartSession(roomName, connectionID)
if err != nil {
return
}
l := logger.GetLogger().WithValues(
"room", roomName,
"reqNodeID", nodeID,
"participant", pi.Identity,
"connID", connectionID,
)
l.Debugw("starting signal connection")
stream, err := r.client.RelaySignal(ctx, nodeID)
if err != nil {
prometheus.MessageCounter.WithLabelValues("signal", "failure").Add(1)
return
}
err = stream.Send(&rpc.RelaySignalRequest{StartSession: ss})
if err != nil {
stream.Close(err)
prometheus.MessageCounter.WithLabelValues("signal", "failure").Add(1)
return
}
sink := NewSignalMessageSink(SignalSinkParams[*rpc.RelaySignalRequest, *rpc.RelaySignalResponse]{
Logger: l,
Stream: stream,
Config: r.config,
Writer: signalRequestMessageWriter{},
CloseOnFailure: true,
BlockOnClose: true,
ConnectionID: connectionID,
})
resChan := NewDefaultMessageChannel(connectionID)
go func() {
r.active.Inc()
defer r.active.Dec()
err := CopySignalStreamToMessageChannel[*rpc.RelaySignalRequest, *rpc.RelaySignalResponse](
stream,
resChan,
signalResponseMessageReader{},
r.config,
)
l.Debugw("signal stream closed", "error", err)
resChan.Close()
}()
return connectionID, sink, resChan, nil
}
type signalRequestMessageWriter struct{}
func (e signalRequestMessageWriter) Write(seq uint64, close bool, msgs []proto.Message) *rpc.RelaySignalRequest {
r := &rpc.RelaySignalRequest{
Seq: seq,
Requests: make([]*livekit.SignalRequest, 0, len(msgs)),
Close: close,
}
for _, m := range msgs {
r.Requests = append(r.Requests, m.(*livekit.SignalRequest))
}
return r
}
type signalResponseMessageReader struct{}
func (e signalResponseMessageReader) Read(rm *rpc.RelaySignalResponse) ([]proto.Message, error) {
msgs := make([]proto.Message, 0, len(rm.Responses))
for _, m := range rm.Responses {
msgs = append(msgs, m)
}
return msgs, nil
}
type RelaySignalMessage interface {
proto.Message
GetSeq() uint64
GetClose() bool
}
type SignalMessageWriter[SendType RelaySignalMessage] interface {
Write(seq uint64, close bool, msgs []proto.Message) SendType
}
type SignalMessageReader[RecvType RelaySignalMessage] interface {
Read(msg RecvType) ([]proto.Message, error)
}
func CopySignalStreamToMessageChannel[SendType, RecvType RelaySignalMessage](
stream psrpc.Stream[SendType, RecvType],
ch *MessageChannel,
reader SignalMessageReader[RecvType],
config config.SignalRelayConfig,
) error {
r := &signalMessageReader[SendType, RecvType]{
reader: reader,
config: config,
}
for msg := range stream.Channel() {
res, err := r.Read(msg)
if err != nil {
prometheus.MessageCounter.WithLabelValues("signal", "failure").Add(1)
return err
}
for _, r := range res {
if err = ch.WriteMessage(r); err != nil {
prometheus.MessageCounter.WithLabelValues("signal", "failure").Add(1)
return err
}
prometheus.MessageCounter.WithLabelValues("signal", "success").Add(1)
}
if msg.GetClose() {
return stream.Close(nil)
}
}
return stream.Err()
}
type signalMessageReader[SendType, RecvType RelaySignalMessage] struct {
seq uint64
reader SignalMessageReader[RecvType]
config config.SignalRelayConfig
}
func (r *signalMessageReader[SendType, RecvType]) Read(msg RecvType) ([]proto.Message, error) {
res, err := r.reader.Read(msg)
if err != nil {
return nil, err
}
if r.seq < msg.GetSeq() {
return nil, ErrSignalMessageDropped
}
if r.seq > msg.GetSeq() {
n := int(r.seq - msg.GetSeq())
if n > len(res) {
n = len(res)
}
res = res[n:]
}
r.seq += uint64(len(res))
return res, nil
}
type SignalSinkParams[SendType, RecvType RelaySignalMessage] struct {
Stream psrpc.Stream[SendType, RecvType]
Logger logger.Logger
Config config.SignalRelayConfig
Writer SignalMessageWriter[SendType]
CloseOnFailure bool
BlockOnClose bool
ConnectionID livekit.ConnectionID
}
func NewSignalMessageSink[SendType, RecvType RelaySignalMessage](params SignalSinkParams[SendType, RecvType]) MessageSink {
return &signalMessageSink[SendType, RecvType]{
SignalSinkParams: params,
}
}
type signalMessageSink[SendType, RecvType RelaySignalMessage] struct {
SignalSinkParams[SendType, RecvType]
mu sync.Mutex
seq uint64
queue []proto.Message
writing bool
draining bool
}
func (s *signalMessageSink[SendType, RecvType]) Close() {
s.mu.Lock()
s.draining = true
if !s.writing {
s.writing = true
go s.write()
}
s.mu.Unlock()
// conditionally block while closing to wait for outgoing messages to drain
//
// on media the signal sink shares a goroutine with other signal connection
// attempts from the same participant so blocking delays establishing new
// sessions during reconnect.
//
// on controller closing without waiting for the outstanding messages to
// drain causes leave messages to be dropped from the write queue. when
// this happens other participants in the room aren't notified about the
// departure until the participant times out.
if s.BlockOnClose {
<-s.Stream.Context().Done()
}
}
func (s *signalMessageSink[SendType, RecvType]) IsClosed() bool {
return s.Stream.Err() != nil
}
func (s *signalMessageSink[SendType, RecvType]) write() {
interval := s.Config.MinRetryInterval
deadline := time.Now().Add(s.Config.RetryTimeout)
var err error
s.mu.Lock()
for {
close := s.draining
if (!close && len(s.queue) == 0) || s.IsClosed() {
break
}
msg, n := s.Writer.Write(s.seq, close, s.queue), len(s.queue)
s.mu.Unlock()
err = s.Stream.Send(msg, psrpc.WithTimeout(interval))
if err != nil {
if time.Now().After(deadline) {
s.Logger.Warnw("could not send signal message", err)
s.mu.Lock()
s.seq += uint64(len(s.queue))
s.queue = nil
break
}
interval *= 2
if interval > s.Config.MaxRetryInterval {
interval = s.Config.MaxRetryInterval
}
}
s.mu.Lock()
if err == nil {
interval = s.Config.MinRetryInterval
deadline = time.Now().Add(s.Config.RetryTimeout)
s.seq += uint64(n)
s.queue = s.queue[n:]
if close {
break
}
}
}
s.writing = false
if s.draining {
s.Stream.Close(nil)
}
if err != nil && s.CloseOnFailure {
s.Stream.Close(ErrSignalWriteFailed)
}
s.mu.Unlock()
}
func (s *signalMessageSink[SendType, RecvType]) WriteMessage(msg proto.Message) error {
s.mu.Lock()
defer s.mu.Unlock()
if err := s.Stream.Err(); err != nil {
return err
} else if s.draining {
return psrpc.ErrStreamClosed
}
s.queue = append(s.queue, msg)
if !s.writing {
s.writing = true
go s.write()
}
return nil
}
func (s *signalMessageSink[SendType, RecvType]) ConnectionID() livekit.ConnectionID {
return s.SignalSinkParams.ConnectionID
}