Merge commit '0da97ebd2149ec2a0412a273b17270f540431d81' as 'livekit-server'
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
// 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 service
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/livekit/livekit-server/pkg/routing"
|
||||
"github.com/livekit/protocol/livekit"
|
||||
"github.com/livekit/protocol/rpc"
|
||||
"github.com/livekit/protocol/utils/guid"
|
||||
)
|
||||
|
||||
type AgentDispatchService struct {
|
||||
agentDispatchClient rpc.TypedAgentDispatchInternalClient
|
||||
topicFormatter rpc.TopicFormatter
|
||||
roomAllocator RoomAllocator
|
||||
router routing.MessageRouter
|
||||
}
|
||||
|
||||
func NewAgentDispatchService(
|
||||
agentDispatchClient rpc.TypedAgentDispatchInternalClient,
|
||||
topicFormatter rpc.TopicFormatter,
|
||||
roomAllocator RoomAllocator,
|
||||
router routing.MessageRouter,
|
||||
) *AgentDispatchService {
|
||||
return &AgentDispatchService{
|
||||
agentDispatchClient: agentDispatchClient,
|
||||
topicFormatter: topicFormatter,
|
||||
roomAllocator: roomAllocator,
|
||||
router: router,
|
||||
}
|
||||
}
|
||||
|
||||
func (ag *AgentDispatchService) CreateDispatch(ctx context.Context, req *livekit.CreateAgentDispatchRequest) (*livekit.AgentDispatch, error) {
|
||||
err := EnsureAdminPermission(ctx, livekit.RoomName(req.Room))
|
||||
if err != nil {
|
||||
return nil, twirpAuthError(err)
|
||||
}
|
||||
|
||||
if ag.roomAllocator.AutoCreateEnabled(ctx) {
|
||||
err := ag.roomAllocator.SelectRoomNode(ctx, livekit.RoomName(req.Room), "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = ag.router.CreateRoom(ctx, &livekit.CreateRoomRequest{Name: req.Room})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
dispatch := &livekit.AgentDispatch{
|
||||
Id: guid.New(guid.AgentDispatchPrefix),
|
||||
AgentName: req.AgentName,
|
||||
Room: req.Room,
|
||||
Metadata: req.Metadata,
|
||||
}
|
||||
return ag.agentDispatchClient.CreateDispatch(ctx, ag.topicFormatter.RoomTopic(ctx, livekit.RoomName(req.Room)), dispatch)
|
||||
}
|
||||
|
||||
func (ag *AgentDispatchService) DeleteDispatch(ctx context.Context, req *livekit.DeleteAgentDispatchRequest) (*livekit.AgentDispatch, error) {
|
||||
err := EnsureAdminPermission(ctx, livekit.RoomName(req.Room))
|
||||
if err != nil {
|
||||
return nil, twirpAuthError(err)
|
||||
}
|
||||
|
||||
return ag.agentDispatchClient.DeleteDispatch(ctx, ag.topicFormatter.RoomTopic(ctx, livekit.RoomName(req.Room)), req)
|
||||
}
|
||||
|
||||
func (ag *AgentDispatchService) ListDispatch(ctx context.Context, req *livekit.ListAgentDispatchRequest) (*livekit.ListAgentDispatchResponse, error) {
|
||||
err := EnsureAdminPermission(ctx, livekit.RoomName(req.Room))
|
||||
if err != nil {
|
||||
return nil, twirpAuthError(err)
|
||||
}
|
||||
|
||||
return ag.agentDispatchClient.ListDispatch(ctx, ag.topicFormatter.RoomTopic(ctx, livekit.RoomName(req.Room)), req)
|
||||
}
|
||||
@@ -0,0 +1,540 @@
|
||||
// Copyright 2024 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 service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"slices"
|
||||
"sort"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"google.golang.org/protobuf/types/known/emptypb"
|
||||
|
||||
"github.com/livekit/livekit-server/pkg/agent"
|
||||
"github.com/livekit/livekit-server/pkg/config"
|
||||
"github.com/livekit/livekit-server/pkg/routing"
|
||||
"github.com/livekit/livekit-server/pkg/rtc"
|
||||
"github.com/livekit/livekit-server/pkg/rtc/types"
|
||||
"github.com/livekit/livekit-server/version"
|
||||
"github.com/livekit/protocol/auth"
|
||||
"github.com/livekit/protocol/livekit"
|
||||
"github.com/livekit/protocol/logger"
|
||||
"github.com/livekit/protocol/rpc"
|
||||
"github.com/livekit/protocol/utils"
|
||||
"github.com/livekit/psrpc"
|
||||
)
|
||||
|
||||
const agentWorkerLoadTarget = 0.65
|
||||
|
||||
type AgentSocketUpgrader struct {
|
||||
websocket.Upgrader
|
||||
}
|
||||
|
||||
func (u AgentSocketUpgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeader http.Header) (*websocket.Conn, agent.WorkerProtocolVersion, bool) {
|
||||
if u.CheckOrigin == nil {
|
||||
// allow connections from any origin, since script may be hosted anywhere
|
||||
// security is enforced by access tokens
|
||||
u.CheckOrigin = func(r *http.Request) bool {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// reject non websocket requests
|
||||
if !websocket.IsWebSocketUpgrade(r) {
|
||||
w.WriteHeader(404)
|
||||
return nil, 0, false
|
||||
}
|
||||
|
||||
// require a claim
|
||||
claims := GetGrants(r.Context())
|
||||
if claims == nil || claims.Video == nil || !claims.Video.Agent {
|
||||
handleError(w, r, http.StatusUnauthorized, rtc.ErrPermissionDenied)
|
||||
return nil, 0, false
|
||||
}
|
||||
|
||||
// upgrade
|
||||
conn, err := u.Upgrader.Upgrade(w, r, responseHeader)
|
||||
if err != nil {
|
||||
handleError(w, r, http.StatusInternalServerError, err)
|
||||
return nil, 0, false
|
||||
}
|
||||
|
||||
var protocol agent.WorkerProtocolVersion = agent.CurrentProtocol
|
||||
if pv, err := strconv.Atoi(r.FormValue("protocol")); err == nil {
|
||||
protocol = agent.WorkerProtocolVersion(pv)
|
||||
}
|
||||
|
||||
return conn, protocol, true
|
||||
}
|
||||
|
||||
func DispatchAgentWorkerSignal(c agent.SignalConn, h agent.WorkerSignalHandler, l logger.Logger) bool {
|
||||
req, _, err := c.ReadWorkerMessage()
|
||||
if err != nil {
|
||||
if IsWebSocketCloseError(err) {
|
||||
l.Debugw("worker closed WS connection", "wsError", err)
|
||||
} else {
|
||||
l.Errorw("error reading from websocket", err)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
if err := agent.DispatchWorkerSignal(req, h); err != nil {
|
||||
l.Warnw("unable to handle worker signal", err, "req", logger.Proto(req))
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func HandshakeAgentWorker(c agent.SignalConn, serverInfo *livekit.ServerInfo, protocol agent.WorkerProtocolVersion, l logger.Logger) (r agent.WorkerRegistration, ok bool) {
|
||||
wr := agent.NewWorkerRegisterer(c, serverInfo, protocol)
|
||||
if err := c.SetReadDeadline(wr.Deadline()); err != nil {
|
||||
return
|
||||
}
|
||||
for !wr.Registered() {
|
||||
if ok = DispatchAgentWorkerSignal(c, wr, l); !ok {
|
||||
return
|
||||
}
|
||||
}
|
||||
if err := c.SetReadDeadline(time.Time{}); err != nil {
|
||||
return
|
||||
}
|
||||
return wr.Registration(), true
|
||||
}
|
||||
|
||||
type AgentService struct {
|
||||
upgrader AgentSocketUpgrader
|
||||
|
||||
*AgentHandler
|
||||
}
|
||||
|
||||
type AgentHandler struct {
|
||||
agentServer rpc.AgentInternalServer
|
||||
mu sync.Mutex
|
||||
logger logger.Logger
|
||||
|
||||
serverInfo *livekit.ServerInfo
|
||||
workers map[string]*agent.Worker
|
||||
jobToWorker map[livekit.JobID]*agent.Worker
|
||||
keyProvider auth.KeyProvider
|
||||
|
||||
namespaceWorkers map[workerKey][]*agent.Worker
|
||||
roomKeyCount int
|
||||
publisherKeyCount int
|
||||
participantKeyCount int
|
||||
namespaces []string // namespaces deprecated
|
||||
agentNames []string
|
||||
|
||||
roomTopic string
|
||||
publisherTopic string
|
||||
participantTopic string
|
||||
}
|
||||
|
||||
type workerKey struct {
|
||||
agentName string
|
||||
namespace string
|
||||
jobType livekit.JobType
|
||||
}
|
||||
|
||||
func NewAgentService(conf *config.Config,
|
||||
currentNode routing.LocalNode,
|
||||
bus psrpc.MessageBus,
|
||||
keyProvider auth.KeyProvider,
|
||||
) (*AgentService, error) {
|
||||
s := &AgentService{}
|
||||
|
||||
serverInfo := &livekit.ServerInfo{
|
||||
Edition: livekit.ServerInfo_Standard,
|
||||
Version: version.Version,
|
||||
Protocol: types.CurrentProtocol,
|
||||
AgentProtocol: agent.CurrentProtocol,
|
||||
Region: conf.Region,
|
||||
NodeId: string(currentNode.NodeID()),
|
||||
}
|
||||
|
||||
agentServer, err := rpc.NewAgentInternalServer(s, bus)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.AgentHandler = NewAgentHandler(
|
||||
agentServer,
|
||||
keyProvider,
|
||||
logger.GetLogger(),
|
||||
serverInfo,
|
||||
agent.RoomAgentTopic,
|
||||
agent.PublisherAgentTopic,
|
||||
agent.ParticipantAgentTopic,
|
||||
)
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (s *AgentService) ServeHTTP(writer http.ResponseWriter, r *http.Request) {
|
||||
if conn, protocol, ok := s.upgrader.Upgrade(writer, r, nil); ok {
|
||||
s.HandleConnection(r.Context(), NewWSSignalConnection(conn), protocol)
|
||||
conn.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func NewAgentHandler(
|
||||
agentServer rpc.AgentInternalServer,
|
||||
keyProvider auth.KeyProvider,
|
||||
logger logger.Logger,
|
||||
serverInfo *livekit.ServerInfo,
|
||||
roomTopic string,
|
||||
publisherTopic string,
|
||||
participantTopic string,
|
||||
) *AgentHandler {
|
||||
return &AgentHandler{
|
||||
agentServer: agentServer,
|
||||
logger: logger.WithComponent("agents"),
|
||||
workers: make(map[string]*agent.Worker),
|
||||
jobToWorker: make(map[livekit.JobID]*agent.Worker),
|
||||
namespaceWorkers: make(map[workerKey][]*agent.Worker),
|
||||
serverInfo: serverInfo,
|
||||
keyProvider: keyProvider,
|
||||
roomTopic: roomTopic,
|
||||
publisherTopic: publisherTopic,
|
||||
participantTopic: participantTopic,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *AgentHandler) HandleConnection(ctx context.Context, conn agent.SignalConn, protocol agent.WorkerProtocolVersion) {
|
||||
registration, ok := HandshakeAgentWorker(conn, h.serverInfo, protocol, h.logger)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
apiKey := GetAPIKey(ctx)
|
||||
apiSecret := h.keyProvider.GetSecret(apiKey)
|
||||
|
||||
worker := agent.NewWorker(registration, apiKey, apiSecret, conn, h.logger)
|
||||
h.registerWorker(worker)
|
||||
|
||||
handlerWorker := &agentHandlerWorker{h, worker}
|
||||
for ok := true; ok; {
|
||||
ok = DispatchAgentWorkerSignal(conn, handlerWorker, worker.Logger())
|
||||
}
|
||||
|
||||
h.deregisterWorker(worker)
|
||||
worker.Close()
|
||||
}
|
||||
|
||||
func (h *AgentHandler) registerWorker(w *agent.Worker) {
|
||||
h.mu.Lock()
|
||||
|
||||
h.workers[w.ID] = w
|
||||
|
||||
key := workerKey{w.AgentName, w.Namespace, w.JobType}
|
||||
|
||||
workers := h.namespaceWorkers[key]
|
||||
created := len(workers) == 0
|
||||
|
||||
if created {
|
||||
nameTopic := agent.GetAgentTopic(w.AgentName, w.Namespace)
|
||||
var typeTopic string
|
||||
switch w.JobType {
|
||||
case livekit.JobType_JT_ROOM:
|
||||
typeTopic = h.roomTopic
|
||||
case livekit.JobType_JT_PUBLISHER:
|
||||
typeTopic = h.publisherTopic
|
||||
case livekit.JobType_JT_PARTICIPANT:
|
||||
typeTopic = h.participantTopic
|
||||
}
|
||||
|
||||
fmt.Println(">>> register worker", typeTopic)
|
||||
|
||||
err := h.agentServer.RegisterJobRequestTopic(nameTopic, typeTopic)
|
||||
if err != nil {
|
||||
h.mu.Unlock()
|
||||
|
||||
w.Logger().Errorw("failed to register job request topic", err)
|
||||
w.Close()
|
||||
return
|
||||
}
|
||||
|
||||
switch w.JobType {
|
||||
case livekit.JobType_JT_ROOM:
|
||||
h.roomKeyCount++
|
||||
case livekit.JobType_JT_PUBLISHER:
|
||||
h.publisherKeyCount++
|
||||
case livekit.JobType_JT_PARTICIPANT:
|
||||
h.participantKeyCount++
|
||||
}
|
||||
|
||||
h.namespaces = append(h.namespaces, w.Namespace)
|
||||
sort.Strings(h.namespaces)
|
||||
h.agentNames = append(h.agentNames, w.AgentName)
|
||||
sort.Strings(h.agentNames)
|
||||
}
|
||||
|
||||
h.namespaceWorkers[key] = append(workers, w)
|
||||
h.mu.Unlock()
|
||||
|
||||
h.logger.Infow("worker registered",
|
||||
"namespace", w.Namespace,
|
||||
"jobType", w.JobType,
|
||||
"agentName", w.AgentName,
|
||||
"workerID", w.ID,
|
||||
)
|
||||
if created {
|
||||
err := h.agentServer.PublishWorkerRegistered(context.Background(), agent.DefaultHandlerNamespace, &emptypb.Empty{})
|
||||
// TODO: when this happens, should we disconnect the worker so it'll retry?
|
||||
if err != nil {
|
||||
w.Logger().Errorw("failed to publish worker registered", err, "namespace", w.Namespace, "jobType", w.JobType, "agentName", w.AgentName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (h *AgentHandler) deregisterWorker(w *agent.Worker) {
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
|
||||
delete(h.workers, w.ID)
|
||||
|
||||
key := workerKey{w.AgentName, w.Namespace, w.JobType}
|
||||
|
||||
workers, ok := h.namespaceWorkers[key]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
index := slices.Index(workers, w)
|
||||
if index == -1 {
|
||||
return
|
||||
}
|
||||
|
||||
if len(workers) > 1 {
|
||||
h.namespaceWorkers[key] = slices.Delete(workers, index, index+1)
|
||||
} else {
|
||||
h.logger.Infow("last worker deregistered",
|
||||
"namespace", w.Namespace,
|
||||
"jobType", w.JobType,
|
||||
"agentName", w.AgentName,
|
||||
"workerID", w.ID,
|
||||
)
|
||||
delete(h.namespaceWorkers, key)
|
||||
|
||||
topic := agent.GetAgentTopic(w.AgentName, w.Namespace)
|
||||
|
||||
switch w.JobType {
|
||||
case livekit.JobType_JT_ROOM:
|
||||
h.roomKeyCount--
|
||||
h.agentServer.DeregisterJobRequestTopic(topic, h.roomTopic)
|
||||
case livekit.JobType_JT_PUBLISHER:
|
||||
h.publisherKeyCount--
|
||||
h.agentServer.DeregisterJobRequestTopic(topic, h.publisherTopic)
|
||||
case livekit.JobType_JT_PARTICIPANT:
|
||||
h.participantKeyCount--
|
||||
h.agentServer.DeregisterJobRequestTopic(topic, h.participantTopic)
|
||||
}
|
||||
|
||||
// agentNames and namespaces contains repeated entries for each agentNames/namespaces combinations
|
||||
if i := slices.Index(h.namespaces, w.Namespace); i != -1 {
|
||||
h.namespaces = slices.Delete(h.namespaces, i, i+1)
|
||||
}
|
||||
if i := slices.Index(h.agentNames, w.AgentName); i != -1 {
|
||||
h.agentNames = slices.Delete(h.agentNames, i, i+1)
|
||||
}
|
||||
}
|
||||
|
||||
jobs := w.RunningJobs()
|
||||
for jobID := range jobs {
|
||||
h.deregisterJob(jobID)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *AgentHandler) deregisterJob(jobID livekit.JobID) {
|
||||
h.agentServer.DeregisterJobTerminateTopic(string(jobID))
|
||||
|
||||
delete(h.jobToWorker, jobID)
|
||||
|
||||
// TODO update dispatch state
|
||||
}
|
||||
|
||||
func (h *AgentHandler) JobRequest(ctx context.Context, job *livekit.Job) (*rpc.JobRequestResponse, error) {
|
||||
logger := h.logger.WithUnlikelyValues(
|
||||
"jobID", job.Id,
|
||||
"namespace", job.Namespace,
|
||||
"agentName", job.AgentName,
|
||||
"jobType", job.Type.String(),
|
||||
)
|
||||
if job.Room != nil {
|
||||
logger = logger.WithValues("room", job.Room.Name, "roomID", job.Room.Sid)
|
||||
}
|
||||
if job.Participant != nil {
|
||||
logger = logger.WithValues("participant", job.Participant.Identity)
|
||||
}
|
||||
|
||||
key := workerKey{job.AgentName, job.Namespace, job.Type}
|
||||
attempted := make(map[*agent.Worker]struct{})
|
||||
for {
|
||||
selected, err := h.selectWorkerWeightedByLoad(key, attempted)
|
||||
if err != nil {
|
||||
logger.Warnw("no worker available to handle job", err)
|
||||
return nil, psrpc.NewError(psrpc.ResourceExhausted, err)
|
||||
}
|
||||
|
||||
logger := logger.WithValues("workerID", selected.ID)
|
||||
attempted[selected] = struct{}{}
|
||||
|
||||
state, err := selected.AssignJob(ctx, job)
|
||||
if err != nil {
|
||||
retry := utils.ErrorIsOneOf(err, agent.ErrWorkerNotAvailable, agent.ErrWorkerClosed)
|
||||
logger.Warnw("failed to assign job to worker", err, "retry", retry)
|
||||
if retry {
|
||||
continue // Try another worker
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
logger.Infow("assigned job to worker")
|
||||
h.mu.Lock()
|
||||
h.jobToWorker[livekit.JobID(job.Id)] = selected
|
||||
h.mu.Unlock()
|
||||
|
||||
err = h.agentServer.RegisterJobTerminateTopic(job.Id)
|
||||
if err != nil {
|
||||
logger.Errorw("failed to register JobTerminate handler", err)
|
||||
}
|
||||
|
||||
return &rpc.JobRequestResponse{
|
||||
State: state,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (h *AgentHandler) JobRequestAffinity(ctx context.Context, job *livekit.Job) float32 {
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
|
||||
var affinity float32
|
||||
for _, w := range h.workers {
|
||||
if w.AgentName != job.AgentName || w.Namespace != job.Namespace || w.JobType != job.Type {
|
||||
continue
|
||||
}
|
||||
|
||||
if w.Status() == livekit.WorkerStatus_WS_AVAILABLE {
|
||||
affinity += max(0, agentWorkerLoadTarget-w.Load())
|
||||
}
|
||||
}
|
||||
|
||||
return affinity
|
||||
}
|
||||
|
||||
func (h *AgentHandler) JobTerminate(ctx context.Context, req *rpc.JobTerminateRequest) (*rpc.JobTerminateResponse, error) {
|
||||
h.mu.Lock()
|
||||
w := h.jobToWorker[livekit.JobID(req.JobId)]
|
||||
h.mu.Unlock()
|
||||
|
||||
if w == nil {
|
||||
return nil, psrpc.NewErrorf(psrpc.NotFound, "no worker for jobID")
|
||||
}
|
||||
|
||||
state, err := w.TerminateJob(livekit.JobID(req.JobId), req.Reason)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &rpc.JobTerminateResponse{
|
||||
State: state,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (h *AgentHandler) CheckEnabled(ctx context.Context, req *rpc.CheckEnabledRequest) (*rpc.CheckEnabledResponse, error) {
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
|
||||
// This doesn't return the full agentName -> namespace mapping, which can cause some unnecessary RPC.
|
||||
// namespaces are however deprecated.
|
||||
return &rpc.CheckEnabledResponse{
|
||||
Namespaces: slices.Compact(slices.Clone(h.namespaces)),
|
||||
AgentNames: slices.Compact(slices.Clone(h.agentNames)),
|
||||
RoomEnabled: h.roomKeyCount != 0,
|
||||
PublisherEnabled: h.publisherKeyCount != 0,
|
||||
ParticipantEnabled: h.participantKeyCount != 0,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (h *AgentHandler) DrainConnections(interval time.Duration) {
|
||||
// jitter drain start
|
||||
time.Sleep(time.Duration(rand.Int63n(int64(interval))))
|
||||
|
||||
t := time.NewTicker(interval)
|
||||
defer t.Stop()
|
||||
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
|
||||
for _, w := range h.workers {
|
||||
w.Close()
|
||||
<-t.C
|
||||
}
|
||||
}
|
||||
|
||||
func (h *AgentHandler) selectWorkerWeightedByLoad(key workerKey, ignore map[*agent.Worker]struct{}) (*agent.Worker, error) {
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
|
||||
workers, ok := h.namespaceWorkers[key]
|
||||
if !ok {
|
||||
return nil, errors.New("no workers available")
|
||||
}
|
||||
|
||||
normalizedLoads := make(map[*agent.Worker]float32)
|
||||
var availableSum float32
|
||||
for _, w := range workers {
|
||||
if _, ok := ignore[w]; !ok && w.Status() == livekit.WorkerStatus_WS_AVAILABLE {
|
||||
normalizedLoads[w] = max(0, 1-w.Load())
|
||||
availableSum += normalizedLoads[w]
|
||||
}
|
||||
}
|
||||
|
||||
if availableSum == 0 {
|
||||
return nil, errors.New("no workers with sufficient capacity")
|
||||
}
|
||||
|
||||
currentSum := rand.Float32() * availableSum
|
||||
for w, load := range normalizedLoads {
|
||||
if currentSum -= load; currentSum <= 0 {
|
||||
return w, nil
|
||||
}
|
||||
}
|
||||
return workers[0], nil
|
||||
}
|
||||
|
||||
var _ agent.WorkerSignalHandler = (*agentHandlerWorker)(nil)
|
||||
|
||||
type agentHandlerWorker struct {
|
||||
h *AgentHandler
|
||||
*agent.Worker
|
||||
}
|
||||
|
||||
func (w *agentHandlerWorker) HandleUpdateJob(update *livekit.UpdateJobStatus) error {
|
||||
if err := w.Worker.HandleUpdateJob(update); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if agent.JobStatusIsEnded(update.Status) {
|
||||
w.h.mu.Lock()
|
||||
w.h.deregisterJob(livekit.JobID(update.JobId))
|
||||
w.h.mu.Unlock()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,225 @@
|
||||
// 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 service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/twitchtv/twirp"
|
||||
|
||||
"github.com/livekit/protocol/auth"
|
||||
"github.com/livekit/protocol/livekit"
|
||||
)
|
||||
|
||||
const (
|
||||
authorizationHeader = "Authorization"
|
||||
bearerPrefix = "Bearer "
|
||||
accessTokenParam = "access_token"
|
||||
)
|
||||
|
||||
type grantsKey struct{}
|
||||
|
||||
type grantsValue struct {
|
||||
claims *auth.ClaimGrants
|
||||
apiKey string
|
||||
}
|
||||
|
||||
var (
|
||||
ErrPermissionDenied = errors.New("permissions denied")
|
||||
ErrMissingAuthorization = errors.New("invalid authorization header. Must start with " + bearerPrefix)
|
||||
ErrInvalidAuthorizationToken = errors.New("invalid authorization token")
|
||||
ErrInvalidAPIKey = errors.New("invalid API key")
|
||||
)
|
||||
|
||||
// authentication middleware
|
||||
type APIKeyAuthMiddleware struct {
|
||||
provider auth.KeyProvider
|
||||
}
|
||||
|
||||
func NewAPIKeyAuthMiddleware(provider auth.KeyProvider) *APIKeyAuthMiddleware {
|
||||
return &APIKeyAuthMiddleware{
|
||||
provider: provider,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *APIKeyAuthMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
|
||||
if r.URL != nil && r.URL.Path == "/rtc/validate" {
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
}
|
||||
|
||||
authHeader := r.Header.Get(authorizationHeader)
|
||||
var authToken string
|
||||
|
||||
if authHeader != "" {
|
||||
if !strings.HasPrefix(authHeader, bearerPrefix) {
|
||||
handleError(w, r, http.StatusUnauthorized, ErrMissingAuthorization)
|
||||
return
|
||||
}
|
||||
|
||||
authToken = authHeader[len(bearerPrefix):]
|
||||
} else {
|
||||
// attempt to find from request header
|
||||
authToken = r.FormValue(accessTokenParam)
|
||||
}
|
||||
|
||||
if authToken != "" {
|
||||
v, err := auth.ParseAPIToken(authToken)
|
||||
if err != nil {
|
||||
handleError(w, r, http.StatusUnauthorized, ErrInvalidAuthorizationToken)
|
||||
return
|
||||
}
|
||||
|
||||
secret := m.provider.GetSecret(v.APIKey())
|
||||
if secret == "" {
|
||||
handleError(w, r, http.StatusUnauthorized, errors.New("invalid API key: "+v.APIKey()))
|
||||
return
|
||||
}
|
||||
|
||||
grants, err := v.Verify(secret)
|
||||
if err != nil {
|
||||
handleError(w, r, http.StatusUnauthorized, errors.New("invalid token: "+authToken+", error: "+err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
// set grants in context
|
||||
ctx := r.Context()
|
||||
r = r.WithContext(context.WithValue(ctx, grantsKey{}, &grantsValue{
|
||||
claims: grants,
|
||||
apiKey: v.APIKey(),
|
||||
}))
|
||||
}
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
func WithAPIKey(ctx context.Context, grants *auth.ClaimGrants, apiKey string) context.Context {
|
||||
return context.WithValue(ctx, grantsKey{}, &grantsValue{
|
||||
claims: grants,
|
||||
apiKey: apiKey,
|
||||
})
|
||||
}
|
||||
|
||||
func GetGrants(ctx context.Context) *auth.ClaimGrants {
|
||||
val := ctx.Value(grantsKey{})
|
||||
v, ok := val.(*grantsValue)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return v.claims
|
||||
}
|
||||
|
||||
func GetAPIKey(ctx context.Context) string {
|
||||
val := ctx.Value(grantsKey{})
|
||||
v, ok := val.(*grantsValue)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return v.apiKey
|
||||
}
|
||||
|
||||
func WithGrants(ctx context.Context, grants *auth.ClaimGrants, apiKey string) context.Context {
|
||||
return context.WithValue(ctx, grantsKey{}, &grantsValue{
|
||||
claims: grants,
|
||||
apiKey: apiKey,
|
||||
})
|
||||
}
|
||||
|
||||
func SetAuthorizationToken(r *http.Request, token string) {
|
||||
r.Header.Set(authorizationHeader, bearerPrefix+token)
|
||||
}
|
||||
|
||||
func EnsureJoinPermission(ctx context.Context) (name livekit.RoomName, err error) {
|
||||
claims := GetGrants(ctx)
|
||||
if claims == nil || claims.Video == nil {
|
||||
err = ErrPermissionDenied
|
||||
return
|
||||
}
|
||||
|
||||
if claims.Video.RoomJoin {
|
||||
name = livekit.RoomName(claims.Video.Room)
|
||||
} else {
|
||||
err = ErrPermissionDenied
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func EnsureAdminPermission(ctx context.Context, room livekit.RoomName) error {
|
||||
claims := GetGrants(ctx)
|
||||
if claims == nil || claims.Video == nil {
|
||||
return ErrPermissionDenied
|
||||
}
|
||||
|
||||
if !claims.Video.RoomAdmin || room != livekit.RoomName(claims.Video.Room) {
|
||||
return ErrPermissionDenied
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func EnsureCreatePermission(ctx context.Context) error {
|
||||
claims := GetGrants(ctx)
|
||||
if claims == nil || claims.Video == nil || !claims.Video.RoomCreate {
|
||||
return ErrPermissionDenied
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func EnsureListPermission(ctx context.Context) error {
|
||||
claims := GetGrants(ctx)
|
||||
if claims == nil || claims.Video == nil || !claims.Video.RoomList {
|
||||
return ErrPermissionDenied
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func EnsureRecordPermission(ctx context.Context) error {
|
||||
claims := GetGrants(ctx)
|
||||
if claims == nil || claims.Video == nil || !claims.Video.RoomRecord {
|
||||
return ErrPermissionDenied
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func EnsureIngressAdminPermission(ctx context.Context) error {
|
||||
claims := GetGrants(ctx)
|
||||
if claims == nil || claims.Video == nil || !claims.Video.IngressAdmin {
|
||||
return ErrPermissionDenied
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func EnsureSIPAdminPermission(ctx context.Context) error {
|
||||
claims := GetGrants(ctx)
|
||||
if claims == nil || claims.SIP == nil || !claims.SIP.Admin {
|
||||
return ErrPermissionDenied
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func EnsureSIPCallPermission(ctx context.Context) error {
|
||||
claims := GetGrants(ctx)
|
||||
if claims == nil || claims.SIP == nil || !claims.SIP.Call {
|
||||
return ErrPermissionDenied
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// wraps authentication errors around Twirp
|
||||
func twirpAuthError(err error) error {
|
||||
return twirp.NewError(twirp.Unauthenticated, err.Error())
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
// 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 service_test
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/livekit/protocol/auth"
|
||||
"github.com/livekit/protocol/auth/authfakes"
|
||||
|
||||
"github.com/livekit/livekit-server/pkg/service"
|
||||
)
|
||||
|
||||
func TestAuthMiddleware(t *testing.T) {
|
||||
api := "APIabcdefg"
|
||||
secret := "somesecretencodedinbase62extendto32bytes"
|
||||
provider := &authfakes.FakeKeyProvider{}
|
||||
provider.GetSecretReturns(secret)
|
||||
|
||||
m := service.NewAPIKeyAuthMiddleware(provider)
|
||||
var grants *auth.ClaimGrants
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
grants = service.GetGrants(r.Context())
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
|
||||
orig := &auth.VideoGrant{Room: "abcdefg", RoomJoin: true}
|
||||
// ensure that the original claim could be retrieved
|
||||
at := auth.NewAccessToken(api, secret).
|
||||
AddGrant(orig)
|
||||
token, err := at.ToJWT()
|
||||
require.NoError(t, err)
|
||||
|
||||
r := &http.Request{Header: http.Header{}}
|
||||
w := httptest.NewRecorder()
|
||||
service.SetAuthorizationToken(r, token)
|
||||
m.ServeHTTP(w, r, handler)
|
||||
|
||||
require.NotNil(t, grants)
|
||||
require.EqualValues(t, orig, grants.Video)
|
||||
|
||||
// no authorization == no claims
|
||||
grants = nil
|
||||
w = httptest.NewRecorder()
|
||||
r = &http.Request{Header: http.Header{}}
|
||||
m.ServeHTTP(w, r, handler)
|
||||
require.Nil(t, grants)
|
||||
require.Equal(t, http.StatusOK, w.Code)
|
||||
|
||||
// incorrect authorization: error
|
||||
grants = nil
|
||||
w = httptest.NewRecorder()
|
||||
r = &http.Request{Header: http.Header{}}
|
||||
service.SetAuthorizationToken(r, "invalid token")
|
||||
m.ServeHTTP(w, r, handler)
|
||||
require.Nil(t, grants)
|
||||
require.Equal(t, http.StatusUnauthorized, w.Code)
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func GenBasicAuthMiddleware(username string, password string) (func(http.ResponseWriter, *http.Request, http.HandlerFunc) ) {
|
||||
return func(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
|
||||
given_username, given_password, ok := r.BasicAuth()
|
||||
unauthorized := func() {
|
||||
rw.Header().Set("WWW-Authenticate", "Basic realm=\"Protected Area\"")
|
||||
rw.WriteHeader(http.StatusUnauthorized)
|
||||
}
|
||||
if !ok {
|
||||
unauthorized()
|
||||
return
|
||||
}
|
||||
|
||||
if given_username != username {
|
||||
unauthorized()
|
||||
return
|
||||
}
|
||||
|
||||
if given_password != password {
|
||||
unauthorized()
|
||||
return
|
||||
}
|
||||
|
||||
next(rw, r)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
// 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 service
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"google.golang.org/protobuf/types/known/emptypb"
|
||||
|
||||
"github.com/livekit/livekit-server/pkg/rtc"
|
||||
"github.com/livekit/protocol/livekit"
|
||||
"github.com/livekit/protocol/logger"
|
||||
"github.com/livekit/protocol/rpc"
|
||||
"github.com/livekit/protocol/utils"
|
||||
"github.com/livekit/protocol/utils/guid"
|
||||
)
|
||||
|
||||
//counterfeiter:generate . IOClient
|
||||
type IOClient interface {
|
||||
CreateEgress(ctx context.Context, info *livekit.EgressInfo) (*emptypb.Empty, error)
|
||||
GetEgress(ctx context.Context, req *rpc.GetEgressRequest) (*livekit.EgressInfo, error)
|
||||
ListEgress(ctx context.Context, req *livekit.ListEgressRequest) (*livekit.ListEgressResponse, error)
|
||||
CreateIngress(ctx context.Context, req *livekit.IngressInfo) (*emptypb.Empty, error)
|
||||
UpdateIngressState(ctx context.Context, req *rpc.UpdateIngressStateRequest) (*emptypb.Empty, error)
|
||||
}
|
||||
|
||||
type egressLauncher struct {
|
||||
client rpc.EgressClient
|
||||
io IOClient
|
||||
}
|
||||
|
||||
func NewEgressLauncher(client rpc.EgressClient, io IOClient) rtc.EgressLauncher {
|
||||
if client == nil {
|
||||
return nil
|
||||
}
|
||||
return &egressLauncher{
|
||||
client: client,
|
||||
io: io,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *egressLauncher) StartEgress(ctx context.Context, req *rpc.StartEgressRequest) (*livekit.EgressInfo, error) {
|
||||
if s.client == nil {
|
||||
return nil, ErrEgressNotConnected
|
||||
}
|
||||
|
||||
// Ensure we have an Egress ID
|
||||
if req.EgressId == "" {
|
||||
req.EgressId = guid.New(utils.EgressPrefix)
|
||||
}
|
||||
|
||||
info, err := s.client.StartEgress(ctx, "", req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = s.io.CreateEgress(ctx, info)
|
||||
if err != nil {
|
||||
logger.Errorw("failed to create egress", err)
|
||||
}
|
||||
|
||||
return info, nil
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
// Copyright 2024 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 service_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"go.uber.org/atomic"
|
||||
|
||||
"github.com/ory/dockertest/v3"
|
||||
)
|
||||
|
||||
var Docker *dockertest.Pool
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
pool, err := dockertest.NewPool("")
|
||||
if err != nil {
|
||||
log.Fatalf("Could not construct pool: %s", err)
|
||||
}
|
||||
|
||||
// uses pool to try to connect to Docker
|
||||
err = pool.Client.Ping()
|
||||
if err != nil {
|
||||
log.Fatalf("Could not connect to Docker: %s", err)
|
||||
}
|
||||
Docker = pool
|
||||
|
||||
code := m.Run()
|
||||
os.Exit(code)
|
||||
}
|
||||
|
||||
func waitTCPPort(t testing.TB, addr string) {
|
||||
if err := Docker.Retry(func() error {
|
||||
conn, err := net.Dial("tcp", addr)
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
return err
|
||||
}
|
||||
_ = conn.Close()
|
||||
return nil
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
var redisLast atomic.Uint32
|
||||
|
||||
func runRedis(t testing.TB) string {
|
||||
c, err := Docker.RunWithOptions(&dockertest.RunOptions{
|
||||
Name: fmt.Sprintf("lktest-redis-%d", redisLast.Inc()),
|
||||
Repository: "redis", Tag: "latest",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
_ = Docker.Purge(c)
|
||||
})
|
||||
addr := c.GetHostPort("6379/tcp")
|
||||
waitTCPPort(t, addr)
|
||||
|
||||
t.Log("Redis running on", addr)
|
||||
return addr
|
||||
}
|
||||
@@ -0,0 +1,281 @@
|
||||
// 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 service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/twitchtv/twirp"
|
||||
|
||||
"github.com/livekit/livekit-server/pkg/rtc"
|
||||
"github.com/livekit/protocol/egress"
|
||||
"github.com/livekit/protocol/livekit"
|
||||
"github.com/livekit/protocol/rpc"
|
||||
)
|
||||
|
||||
type EgressService struct {
|
||||
launcher rtc.EgressLauncher
|
||||
client rpc.EgressClient
|
||||
io IOClient
|
||||
roomService livekit.RoomService
|
||||
store ServiceStore
|
||||
}
|
||||
|
||||
func NewEgressService(
|
||||
client rpc.EgressClient,
|
||||
launcher rtc.EgressLauncher,
|
||||
store ServiceStore,
|
||||
io IOClient,
|
||||
rs livekit.RoomService,
|
||||
) *EgressService {
|
||||
return &EgressService{
|
||||
client: client,
|
||||
store: store,
|
||||
io: io,
|
||||
roomService: rs,
|
||||
launcher: launcher,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *EgressService) StartRoomCompositeEgress(ctx context.Context, req *livekit.RoomCompositeEgressRequest) (*livekit.EgressInfo, error) {
|
||||
fields := []interface{}{
|
||||
"room", req.RoomName,
|
||||
"baseUrl", req.CustomBaseUrl,
|
||||
"outputType", egress.GetOutputType(req),
|
||||
}
|
||||
defer func() {
|
||||
AppendLogFields(ctx, fields...)
|
||||
}()
|
||||
ei, err := s.startEgress(ctx, livekit.RoomName(req.RoomName), &rpc.StartEgressRequest{
|
||||
Request: &rpc.StartEgressRequest_RoomComposite{
|
||||
RoomComposite: req,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fields = append(fields, "egressID", ei.EgressId)
|
||||
return ei, err
|
||||
}
|
||||
|
||||
func (s *EgressService) StartWebEgress(ctx context.Context, req *livekit.WebEgressRequest) (*livekit.EgressInfo, error) {
|
||||
fields := []interface{}{
|
||||
"url", req.Url,
|
||||
"outputType", egress.GetOutputType(req),
|
||||
}
|
||||
defer func() {
|
||||
AppendLogFields(ctx, fields...)
|
||||
}()
|
||||
ei, err := s.startEgress(ctx, "", &rpc.StartEgressRequest{
|
||||
Request: &rpc.StartEgressRequest_Web{
|
||||
Web: req,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fields = append(fields, "egressID", ei.EgressId)
|
||||
return ei, err
|
||||
}
|
||||
|
||||
func (s *EgressService) StartParticipantEgress(ctx context.Context, req *livekit.ParticipantEgressRequest) (*livekit.EgressInfo, error) {
|
||||
fields := []interface{}{
|
||||
"room", req.RoomName,
|
||||
"identity", req.Identity,
|
||||
"outputType", egress.GetOutputType(req),
|
||||
}
|
||||
defer func() {
|
||||
AppendLogFields(ctx, fields...)
|
||||
}()
|
||||
ei, err := s.startEgress(ctx, livekit.RoomName(req.RoomName), &rpc.StartEgressRequest{
|
||||
Request: &rpc.StartEgressRequest_Participant{
|
||||
Participant: req,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fields = append(fields, "egressID", ei.EgressId)
|
||||
return ei, err
|
||||
}
|
||||
|
||||
func (s *EgressService) StartTrackCompositeEgress(ctx context.Context, req *livekit.TrackCompositeEgressRequest) (*livekit.EgressInfo, error) {
|
||||
fields := []interface{}{
|
||||
"room", req.RoomName,
|
||||
"audioTrackID", req.AudioTrackId,
|
||||
"videoTrackID", req.VideoTrackId,
|
||||
"outputType", egress.GetOutputType(req),
|
||||
}
|
||||
defer func() {
|
||||
AppendLogFields(ctx, fields...)
|
||||
}()
|
||||
ei, err := s.startEgress(ctx, livekit.RoomName(req.RoomName), &rpc.StartEgressRequest{
|
||||
Request: &rpc.StartEgressRequest_TrackComposite{
|
||||
TrackComposite: req,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fields = append(fields, "egressID", ei.EgressId)
|
||||
return ei, err
|
||||
}
|
||||
|
||||
func (s *EgressService) StartTrackEgress(ctx context.Context, req *livekit.TrackEgressRequest) (*livekit.EgressInfo, error) {
|
||||
fields := []interface{}{"room", req.RoomName, "trackID", req.TrackId}
|
||||
if t := reflect.TypeOf(req.Output); t != nil {
|
||||
fields = append(fields, "outputType", t.String())
|
||||
}
|
||||
defer func() {
|
||||
AppendLogFields(ctx, fields...)
|
||||
}()
|
||||
ei, err := s.startEgress(ctx, livekit.RoomName(req.RoomName), &rpc.StartEgressRequest{
|
||||
Request: &rpc.StartEgressRequest_Track{
|
||||
Track: req,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fields = append(fields, "egressID", ei.EgressId)
|
||||
return ei, err
|
||||
}
|
||||
|
||||
func (s *EgressService) startEgress(ctx context.Context, roomName livekit.RoomName, req *rpc.StartEgressRequest) (*livekit.EgressInfo, error) {
|
||||
if err := EnsureRecordPermission(ctx); err != nil {
|
||||
return nil, twirpAuthError(err)
|
||||
} else if s.launcher == nil {
|
||||
return nil, ErrEgressNotConnected
|
||||
}
|
||||
if roomName != "" {
|
||||
room, _, err := s.store.LoadRoom(ctx, roomName, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.RoomId = room.Sid
|
||||
}
|
||||
return s.launcher.StartEgress(ctx, req)
|
||||
}
|
||||
|
||||
type LayoutMetadata struct {
|
||||
Layout string `json:"layout"`
|
||||
}
|
||||
|
||||
func (s *EgressService) UpdateLayout(ctx context.Context, req *livekit.UpdateLayoutRequest) (*livekit.EgressInfo, error) {
|
||||
AppendLogFields(ctx, "egressID", req.EgressId, "layout", req.Layout)
|
||||
if err := EnsureRecordPermission(ctx); err != nil {
|
||||
return nil, twirpAuthError(err)
|
||||
}
|
||||
|
||||
info, err := s.io.GetEgress(ctx, &rpc.GetEgressRequest{EgressId: req.EgressId})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
metadata, err := json.Marshal(&LayoutMetadata{Layout: req.Layout})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
grants := GetGrants(ctx)
|
||||
grants.Video.Room = info.RoomName
|
||||
grants.Video.RoomAdmin = true
|
||||
|
||||
_, err = s.roomService.UpdateParticipant(ctx, &livekit.UpdateParticipantRequest{
|
||||
Room: info.RoomName,
|
||||
Identity: info.EgressId,
|
||||
Metadata: string(metadata),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return info, nil
|
||||
}
|
||||
|
||||
func (s *EgressService) UpdateStream(ctx context.Context, req *livekit.UpdateStreamRequest) (*livekit.EgressInfo, error) {
|
||||
AppendLogFields(ctx, "egressID", req.EgressId, "addUrls", req.AddOutputUrls, "removeUrls", req.RemoveOutputUrls)
|
||||
if err := EnsureRecordPermission(ctx); err != nil {
|
||||
return nil, twirpAuthError(err)
|
||||
}
|
||||
|
||||
if s.client == nil {
|
||||
return nil, ErrEgressNotConnected
|
||||
}
|
||||
|
||||
info, err := s.client.UpdateStream(ctx, req.EgressId, req)
|
||||
if err != nil {
|
||||
var loadErr error
|
||||
info, loadErr = s.io.GetEgress(ctx, &rpc.GetEgressRequest{EgressId: req.EgressId})
|
||||
if loadErr != nil {
|
||||
return nil, loadErr
|
||||
}
|
||||
|
||||
switch info.Status {
|
||||
case livekit.EgressStatus_EGRESS_STARTING,
|
||||
livekit.EgressStatus_EGRESS_ACTIVE:
|
||||
return nil, err
|
||||
default:
|
||||
return nil, twirp.NewError(twirp.FailedPrecondition,
|
||||
fmt.Sprintf("egress with status %s cannot be updated", info.Status.String()))
|
||||
}
|
||||
}
|
||||
|
||||
return info, nil
|
||||
}
|
||||
|
||||
func (s *EgressService) ListEgress(ctx context.Context, req *livekit.ListEgressRequest) (*livekit.ListEgressResponse, error) {
|
||||
if req.RoomName != "" {
|
||||
AppendLogFields(ctx, "room", req.RoomName)
|
||||
}
|
||||
if err := EnsureRecordPermission(ctx); err != nil {
|
||||
return nil, twirpAuthError(err)
|
||||
}
|
||||
return s.io.ListEgress(ctx, req)
|
||||
}
|
||||
|
||||
func (s *EgressService) StopEgress(ctx context.Context, req *livekit.StopEgressRequest) (*livekit.EgressInfo, error) {
|
||||
AppendLogFields(ctx, "egressID", req.EgressId)
|
||||
if err := EnsureRecordPermission(ctx); err != nil {
|
||||
return nil, twirpAuthError(err)
|
||||
}
|
||||
|
||||
if s.client == nil {
|
||||
return nil, ErrEgressNotConnected
|
||||
}
|
||||
|
||||
info, err := s.client.StopEgress(ctx, req.EgressId, req)
|
||||
if err != nil {
|
||||
var loadErr error
|
||||
info, loadErr = s.io.GetEgress(ctx, &rpc.GetEgressRequest{EgressId: req.EgressId})
|
||||
if loadErr != nil {
|
||||
return nil, loadErr
|
||||
}
|
||||
|
||||
switch info.Status {
|
||||
case livekit.EgressStatus_EGRESS_STARTING,
|
||||
livekit.EgressStatus_EGRESS_ACTIVE:
|
||||
return nil, err
|
||||
default:
|
||||
return nil, twirp.NewError(twirp.FailedPrecondition,
|
||||
fmt.Sprintf("egress with status %s cannot be stopped", info.Status.String()))
|
||||
}
|
||||
}
|
||||
|
||||
return info, nil
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
// 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 service
|
||||
|
||||
import (
|
||||
"github.com/livekit/psrpc"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrEgressNotFound = psrpc.NewErrorf(psrpc.NotFound, "egress does not exist")
|
||||
ErrEgressNotConnected = psrpc.NewErrorf(psrpc.Internal, "egress not connected (redis required)")
|
||||
ErrIdentityEmpty = psrpc.NewErrorf(psrpc.InvalidArgument, "identity cannot be empty")
|
||||
ErrIngressNotConnected = psrpc.NewErrorf(psrpc.Internal, "ingress not connected (redis required)")
|
||||
ErrIngressNotFound = psrpc.NewErrorf(psrpc.NotFound, "ingress does not exist")
|
||||
ErrIngressNonReusable = psrpc.NewErrorf(psrpc.InvalidArgument, "ingress is not reusable and cannot be modified")
|
||||
ErrNameExceedsLimits = psrpc.NewErrorf(psrpc.InvalidArgument, "name length exceeds limits")
|
||||
ErrMetadataExceedsLimits = psrpc.NewErrorf(psrpc.InvalidArgument, "metadata size exceeds limits")
|
||||
ErrAttributeExceedsLimits = psrpc.NewErrorf(psrpc.InvalidArgument, "attribute size exceeds limits")
|
||||
ErrRoomNameExceedsLimits = psrpc.NewErrorf(psrpc.InvalidArgument, "room name length exceeds limits")
|
||||
ErrParticipantIdentityExceedsLimits = psrpc.NewErrorf(psrpc.InvalidArgument, "participant identity length exceeds limits")
|
||||
ErrOperationFailed = psrpc.NewErrorf(psrpc.Internal, "operation cannot be completed")
|
||||
ErrParticipantNotFound = psrpc.NewErrorf(psrpc.NotFound, "participant does not exist")
|
||||
ErrRoomNotFound = psrpc.NewErrorf(psrpc.NotFound, "requested room does not exist")
|
||||
ErrRoomLockFailed = psrpc.NewErrorf(psrpc.Internal, "could not lock room")
|
||||
ErrRoomUnlockFailed = psrpc.NewErrorf(psrpc.Internal, "could not unlock room, lock token does not match")
|
||||
ErrRemoteUnmuteNoteEnabled = psrpc.NewErrorf(psrpc.FailedPrecondition, "remote unmute not enabled")
|
||||
ErrTrackNotFound = psrpc.NewErrorf(psrpc.NotFound, "track is not found")
|
||||
ErrWebHookMissingAPIKey = psrpc.NewErrorf(psrpc.InvalidArgument, "api_key is required to use webhooks")
|
||||
ErrSIPNotConnected = psrpc.NewErrorf(psrpc.Internal, "sip not connected (redis required)")
|
||||
ErrSIPTrunkNotFound = psrpc.NewErrorf(psrpc.NotFound, "requested sip trunk does not exist")
|
||||
ErrSIPDispatchRuleNotFound = psrpc.NewErrorf(psrpc.NotFound, "requested sip dispatch rule does not exist")
|
||||
ErrSIPParticipantNotFound = psrpc.NewErrorf(psrpc.NotFound, "requested sip participant does not exist")
|
||||
)
|
||||
@@ -0,0 +1,409 @@
|
||||
// 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 service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"github.com/livekit/livekit-server/pkg/config"
|
||||
"github.com/livekit/livekit-server/pkg/telemetry"
|
||||
"github.com/livekit/protocol/ingress"
|
||||
"github.com/livekit/protocol/livekit"
|
||||
"github.com/livekit/protocol/logger"
|
||||
"github.com/livekit/protocol/rpc"
|
||||
"github.com/livekit/protocol/utils"
|
||||
"github.com/livekit/protocol/utils/guid"
|
||||
"github.com/livekit/psrpc"
|
||||
)
|
||||
|
||||
type IngressLauncher interface {
|
||||
LaunchPullIngress(ctx context.Context, info *livekit.IngressInfo) (*livekit.IngressInfo, error)
|
||||
}
|
||||
|
||||
type IngressService struct {
|
||||
conf *config.IngressConfig
|
||||
nodeID livekit.NodeID
|
||||
bus psrpc.MessageBus
|
||||
psrpcClient rpc.IngressClient
|
||||
store IngressStore
|
||||
io IOClient
|
||||
telemetry telemetry.TelemetryService
|
||||
launcher IngressLauncher
|
||||
}
|
||||
|
||||
func NewIngressServiceWithIngressLauncher(
|
||||
conf *config.IngressConfig,
|
||||
nodeID livekit.NodeID,
|
||||
bus psrpc.MessageBus,
|
||||
psrpcClient rpc.IngressClient,
|
||||
store IngressStore,
|
||||
io IOClient,
|
||||
ts telemetry.TelemetryService,
|
||||
launcher IngressLauncher,
|
||||
) *IngressService {
|
||||
|
||||
return &IngressService{
|
||||
conf: conf,
|
||||
nodeID: nodeID,
|
||||
bus: bus,
|
||||
psrpcClient: psrpcClient,
|
||||
store: store,
|
||||
io: io,
|
||||
telemetry: ts,
|
||||
launcher: launcher,
|
||||
}
|
||||
}
|
||||
|
||||
func NewIngressService(
|
||||
conf *config.IngressConfig,
|
||||
nodeID livekit.NodeID,
|
||||
bus psrpc.MessageBus,
|
||||
psrpcClient rpc.IngressClient,
|
||||
store IngressStore,
|
||||
io IOClient,
|
||||
ts telemetry.TelemetryService,
|
||||
) *IngressService {
|
||||
s := NewIngressServiceWithIngressLauncher(conf, nodeID, bus, psrpcClient, store, io, ts, nil)
|
||||
|
||||
s.launcher = s
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *IngressService) CreateIngress(ctx context.Context, req *livekit.CreateIngressRequest) (*livekit.IngressInfo, error) {
|
||||
fields := []interface{}{
|
||||
"inputType", req.InputType,
|
||||
"name", req.Name,
|
||||
}
|
||||
if req.RoomName != "" {
|
||||
fields = append(fields, "room", req.RoomName, "identity", req.ParticipantIdentity)
|
||||
}
|
||||
defer func() {
|
||||
AppendLogFields(ctx, fields...)
|
||||
}()
|
||||
|
||||
var url string
|
||||
switch req.InputType {
|
||||
case livekit.IngressInput_RTMP_INPUT:
|
||||
url = s.conf.RTMPBaseURL
|
||||
case livekit.IngressInput_WHIP_INPUT:
|
||||
url = s.conf.WHIPBaseURL
|
||||
case livekit.IngressInput_URL_INPUT:
|
||||
default:
|
||||
return nil, ingress.ErrInvalidIngressType
|
||||
}
|
||||
|
||||
ig, err := s.CreateIngressWithUrl(ctx, url, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fields = append(fields, "ingressID", ig.IngressId)
|
||||
|
||||
return ig, nil
|
||||
}
|
||||
|
||||
func (s *IngressService) CreateIngressWithUrl(ctx context.Context, urlStr string, req *livekit.CreateIngressRequest) (*livekit.IngressInfo, error) {
|
||||
err := EnsureIngressAdminPermission(ctx)
|
||||
if err != nil {
|
||||
return nil, twirpAuthError(err)
|
||||
}
|
||||
if s.store == nil {
|
||||
return nil, ErrIngressNotConnected
|
||||
}
|
||||
|
||||
if req.InputType == livekit.IngressInput_URL_INPUT {
|
||||
if req.Url == "" {
|
||||
return nil, ingress.ErrInvalidIngress("missing URL parameter")
|
||||
}
|
||||
urlObj, err := url.Parse(req.Url)
|
||||
if err != nil {
|
||||
return nil, psrpc.NewError(psrpc.InvalidArgument, err)
|
||||
}
|
||||
if urlObj.Scheme != "http" && urlObj.Scheme != "https" && urlObj.Scheme != "srt" {
|
||||
return nil, ingress.ErrInvalidIngress(fmt.Sprintf("invalid url scheme %s", urlObj.Scheme))
|
||||
}
|
||||
// Marshall the URL again for sanitization
|
||||
urlStr = urlObj.String()
|
||||
}
|
||||
|
||||
var sk string
|
||||
if req.InputType != livekit.IngressInput_URL_INPUT {
|
||||
sk = guid.New("")
|
||||
}
|
||||
|
||||
info := &livekit.IngressInfo{
|
||||
IngressId: guid.New(utils.IngressPrefix),
|
||||
Name: req.Name,
|
||||
StreamKey: sk,
|
||||
Url: urlStr,
|
||||
InputType: req.InputType,
|
||||
Audio: req.Audio,
|
||||
Video: req.Video,
|
||||
EnableTranscoding: req.EnableTranscoding,
|
||||
RoomName: req.RoomName,
|
||||
ParticipantIdentity: req.ParticipantIdentity,
|
||||
ParticipantName: req.ParticipantName,
|
||||
ParticipantMetadata: req.ParticipantMetadata,
|
||||
State: &livekit.IngressState{},
|
||||
Enabled: req.Enabled,
|
||||
}
|
||||
|
||||
switch req.InputType {
|
||||
case livekit.IngressInput_RTMP_INPUT,
|
||||
livekit.IngressInput_WHIP_INPUT:
|
||||
info.Reusable = true
|
||||
if err := ingress.ValidateForSerialization(info); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case livekit.IngressInput_URL_INPUT:
|
||||
if err := ingress.Validate(info); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
default:
|
||||
return nil, ingress.ErrInvalidIngressType
|
||||
}
|
||||
|
||||
updateEnableTranscoding(info)
|
||||
|
||||
if req.InputType == livekit.IngressInput_URL_INPUT {
|
||||
retInfo, err := s.launcher.LaunchPullIngress(ctx, info)
|
||||
if retInfo != nil {
|
||||
info = retInfo
|
||||
} else {
|
||||
info.State.Status = livekit.IngressState_ENDPOINT_ERROR
|
||||
info.State.Error = err.Error()
|
||||
}
|
||||
if err != nil {
|
||||
return info, err
|
||||
}
|
||||
}
|
||||
|
||||
// TODO Remove this store Ingress call for URL pull as it is redundant since
|
||||
// the ingress service sends a CreateIngress RPC
|
||||
_, err = s.io.CreateIngress(ctx, info)
|
||||
switch err {
|
||||
case nil:
|
||||
break
|
||||
case ingress.ErrIngressOutOfDate:
|
||||
// Error returned if the ingress was already created by the ingress service
|
||||
err = nil
|
||||
default:
|
||||
logger.Errorw("could not create ingress object", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return info, nil
|
||||
}
|
||||
|
||||
func (s *IngressService) LaunchPullIngress(ctx context.Context, info *livekit.IngressInfo) (*livekit.IngressInfo, error) {
|
||||
req := &rpc.StartIngressRequest{
|
||||
Info: info,
|
||||
}
|
||||
|
||||
return s.psrpcClient.StartIngress(ctx, req)
|
||||
}
|
||||
|
||||
func updateEnableTranscoding(info *livekit.IngressInfo) {
|
||||
// Set BypassTranscoding as well for backward compatiblity
|
||||
if info.EnableTranscoding != nil {
|
||||
info.BypassTranscoding = !*info.EnableTranscoding
|
||||
return
|
||||
}
|
||||
|
||||
switch info.InputType {
|
||||
case livekit.IngressInput_WHIP_INPUT:
|
||||
f := false
|
||||
info.EnableTranscoding = &f
|
||||
info.BypassTranscoding = true
|
||||
default:
|
||||
t := true
|
||||
info.EnableTranscoding = &t
|
||||
}
|
||||
}
|
||||
|
||||
func updateInfoUsingRequest(req *livekit.UpdateIngressRequest, info *livekit.IngressInfo) error {
|
||||
if req.Name != "" {
|
||||
info.Name = req.Name
|
||||
}
|
||||
if req.RoomName != "" {
|
||||
info.RoomName = req.RoomName
|
||||
}
|
||||
if req.ParticipantIdentity != "" {
|
||||
info.ParticipantIdentity = req.ParticipantIdentity
|
||||
}
|
||||
if req.ParticipantName != "" {
|
||||
info.ParticipantName = req.ParticipantName
|
||||
}
|
||||
if req.EnableTranscoding != nil {
|
||||
info.EnableTranscoding = req.EnableTranscoding
|
||||
}
|
||||
|
||||
if req.ParticipantMetadata != "" {
|
||||
info.ParticipantMetadata = req.ParticipantMetadata
|
||||
}
|
||||
if req.Audio != nil {
|
||||
info.Audio = req.Audio
|
||||
}
|
||||
if req.Video != nil {
|
||||
info.Video = req.Video
|
||||
}
|
||||
|
||||
if req.Enabled != nil {
|
||||
info.Enabled = req.Enabled
|
||||
}
|
||||
|
||||
if err := ingress.ValidateForSerialization(info); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
updateEnableTranscoding(info)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *IngressService) UpdateIngress(ctx context.Context, req *livekit.UpdateIngressRequest) (*livekit.IngressInfo, error) {
|
||||
fields := []interface{}{
|
||||
"ingress", req.IngressId,
|
||||
"name", req.Name,
|
||||
}
|
||||
if req.RoomName != "" {
|
||||
fields = append(fields, "room", req.RoomName, "identity", req.ParticipantIdentity)
|
||||
}
|
||||
AppendLogFields(ctx, fields...)
|
||||
err := EnsureIngressAdminPermission(ctx)
|
||||
if err != nil {
|
||||
return nil, twirpAuthError(err)
|
||||
}
|
||||
|
||||
if s.psrpcClient == nil {
|
||||
return nil, ErrIngressNotConnected
|
||||
}
|
||||
|
||||
info, err := s.store.LoadIngress(ctx, req.IngressId)
|
||||
if err != nil {
|
||||
logger.Errorw("could not load ingress info", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !info.Reusable {
|
||||
logger.Infow("ingress update attempted on non reusable ingress", "ingressID", info.IngressId)
|
||||
return info, ErrIngressNonReusable
|
||||
}
|
||||
|
||||
switch info.State.Status {
|
||||
case livekit.IngressState_ENDPOINT_ERROR:
|
||||
info.State.Status = livekit.IngressState_ENDPOINT_INACTIVE
|
||||
_, err = s.io.UpdateIngressState(ctx, &rpc.UpdateIngressStateRequest{
|
||||
IngressId: req.IngressId,
|
||||
State: info.State,
|
||||
})
|
||||
if err != nil {
|
||||
logger.Warnw("could not store ingress state", err)
|
||||
}
|
||||
fallthrough
|
||||
|
||||
case livekit.IngressState_ENDPOINT_INACTIVE:
|
||||
err = updateInfoUsingRequest(req, info)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
case livekit.IngressState_ENDPOINT_BUFFERING,
|
||||
livekit.IngressState_ENDPOINT_PUBLISHING:
|
||||
err := updateInfoUsingRequest(req, info)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Do not store the returned state as the ingress service will do it
|
||||
if _, err = s.psrpcClient.UpdateIngress(ctx, req.IngressId, req); err != nil {
|
||||
logger.Warnw("could not update active ingress", err)
|
||||
}
|
||||
}
|
||||
|
||||
err = s.store.UpdateIngress(ctx, info)
|
||||
if err != nil {
|
||||
logger.Errorw("could not update ingress info", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return info, nil
|
||||
}
|
||||
|
||||
func (s *IngressService) ListIngress(ctx context.Context, req *livekit.ListIngressRequest) (*livekit.ListIngressResponse, error) {
|
||||
AppendLogFields(ctx, "room", req.RoomName)
|
||||
err := EnsureIngressAdminPermission(ctx)
|
||||
if err != nil {
|
||||
return nil, twirpAuthError(err)
|
||||
}
|
||||
if s.store == nil {
|
||||
return nil, ErrIngressNotConnected
|
||||
}
|
||||
|
||||
var infos []*livekit.IngressInfo
|
||||
if req.IngressId != "" {
|
||||
info, err := s.store.LoadIngress(ctx, req.IngressId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
infos = []*livekit.IngressInfo{info}
|
||||
} else {
|
||||
infos, err = s.store.ListIngress(ctx, livekit.RoomName(req.RoomName))
|
||||
if err != nil {
|
||||
logger.Errorw("could not list ingress info", err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &livekit.ListIngressResponse{Items: infos}, nil
|
||||
}
|
||||
|
||||
func (s *IngressService) DeleteIngress(ctx context.Context, req *livekit.DeleteIngressRequest) (*livekit.IngressInfo, error) {
|
||||
AppendLogFields(ctx, "ingressID", req.IngressId)
|
||||
if err := EnsureIngressAdminPermission(ctx); err != nil {
|
||||
return nil, twirpAuthError(err)
|
||||
}
|
||||
|
||||
if s.psrpcClient == nil {
|
||||
return nil, ErrIngressNotConnected
|
||||
}
|
||||
|
||||
info, err := s.store.LoadIngress(ctx, req.IngressId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch info.State.Status {
|
||||
case livekit.IngressState_ENDPOINT_BUFFERING,
|
||||
livekit.IngressState_ENDPOINT_PUBLISHING:
|
||||
if _, err = s.psrpcClient.DeleteIngress(ctx, req.IngressId, req); err != nil {
|
||||
logger.Warnw("could not stop active ingress", err)
|
||||
}
|
||||
}
|
||||
|
||||
err = s.store.DeleteIngress(ctx, info)
|
||||
if err != nil {
|
||||
logger.Errorw("could not delete ingress info", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info.State.Status = livekit.IngressState_ENDPOINT_INACTIVE
|
||||
|
||||
s.telemetry.IngressDeleted(ctx, info)
|
||||
|
||||
return info, nil
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
// 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 service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/livekit/protocol/livekit"
|
||||
)
|
||||
|
||||
//go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 -generate
|
||||
|
||||
// encapsulates CRUD operations for room settings
|
||||
//
|
||||
//counterfeiter:generate . ObjectStore
|
||||
type ObjectStore interface {
|
||||
ServiceStore
|
||||
|
||||
// enable locking on a specific room to prevent race
|
||||
// returns a (lock uuid, error)
|
||||
LockRoom(ctx context.Context, roomName livekit.RoomName, duration time.Duration) (string, error)
|
||||
UnlockRoom(ctx context.Context, roomName livekit.RoomName, uid string) error
|
||||
|
||||
StoreRoom(ctx context.Context, room *livekit.Room, internal *livekit.RoomInternal) error
|
||||
|
||||
StoreParticipant(ctx context.Context, roomName livekit.RoomName, participant *livekit.ParticipantInfo) error
|
||||
DeleteParticipant(ctx context.Context, roomName livekit.RoomName, identity livekit.ParticipantIdentity) error
|
||||
}
|
||||
|
||||
//counterfeiter:generate . ServiceStore
|
||||
type ServiceStore interface {
|
||||
LoadRoom(ctx context.Context, roomName livekit.RoomName, includeInternal bool) (*livekit.Room, *livekit.RoomInternal, error)
|
||||
DeleteRoom(ctx context.Context, roomName livekit.RoomName) error
|
||||
|
||||
// ListRooms returns currently active rooms. if names is not nil, it'll filter and return
|
||||
// only rooms that match
|
||||
ListRooms(ctx context.Context, roomNames []livekit.RoomName) ([]*livekit.Room, error)
|
||||
LoadParticipant(ctx context.Context, roomName livekit.RoomName, identity livekit.ParticipantIdentity) (*livekit.ParticipantInfo, error)
|
||||
ListParticipants(ctx context.Context, roomName livekit.RoomName) ([]*livekit.ParticipantInfo, error)
|
||||
}
|
||||
|
||||
//counterfeiter:generate . EgressStore
|
||||
type EgressStore interface {
|
||||
StoreEgress(ctx context.Context, info *livekit.EgressInfo) error
|
||||
LoadEgress(ctx context.Context, egressID string) (*livekit.EgressInfo, error)
|
||||
ListEgress(ctx context.Context, roomName livekit.RoomName, active bool) ([]*livekit.EgressInfo, error)
|
||||
UpdateEgress(ctx context.Context, info *livekit.EgressInfo) error
|
||||
}
|
||||
|
||||
//counterfeiter:generate . IngressStore
|
||||
type IngressStore interface {
|
||||
StoreIngress(ctx context.Context, info *livekit.IngressInfo) error
|
||||
LoadIngress(ctx context.Context, ingressID string) (*livekit.IngressInfo, error)
|
||||
LoadIngressFromStreamKey(ctx context.Context, streamKey string) (*livekit.IngressInfo, error)
|
||||
ListIngress(ctx context.Context, roomName livekit.RoomName) ([]*livekit.IngressInfo, error)
|
||||
UpdateIngress(ctx context.Context, info *livekit.IngressInfo) error
|
||||
UpdateIngressState(ctx context.Context, ingressId string, state *livekit.IngressState) error
|
||||
DeleteIngress(ctx context.Context, info *livekit.IngressInfo) error
|
||||
}
|
||||
|
||||
//counterfeiter:generate . RoomAllocator
|
||||
type RoomAllocator interface {
|
||||
AutoCreateEnabled(ctx context.Context) bool
|
||||
SelectRoomNode(ctx context.Context, roomName livekit.RoomName, nodeID livekit.NodeID) error
|
||||
CreateRoom(ctx context.Context, req *livekit.CreateRoomRequest, isExplicit bool) (*livekit.Room, *livekit.RoomInternal, bool, error)
|
||||
ValidateCreateRoom(ctx context.Context, roomName livekit.RoomName) error
|
||||
}
|
||||
|
||||
//counterfeiter:generate . SIPStore
|
||||
type SIPStore interface {
|
||||
StoreSIPTrunk(ctx context.Context, info *livekit.SIPTrunkInfo) error
|
||||
StoreSIPInboundTrunk(ctx context.Context, info *livekit.SIPInboundTrunkInfo) error
|
||||
StoreSIPOutboundTrunk(ctx context.Context, info *livekit.SIPOutboundTrunkInfo) error
|
||||
LoadSIPTrunk(ctx context.Context, sipTrunkID string) (*livekit.SIPTrunkInfo, error)
|
||||
LoadSIPInboundTrunk(ctx context.Context, sipTrunkID string) (*livekit.SIPInboundTrunkInfo, error)
|
||||
LoadSIPOutboundTrunk(ctx context.Context, sipTrunkID string) (*livekit.SIPOutboundTrunkInfo, error)
|
||||
ListSIPTrunk(ctx context.Context, opts *livekit.ListSIPTrunkRequest) (*livekit.ListSIPTrunkResponse, error)
|
||||
ListSIPInboundTrunk(ctx context.Context, opts *livekit.ListSIPInboundTrunkRequest) (*livekit.ListSIPInboundTrunkResponse, error)
|
||||
ListSIPOutboundTrunk(ctx context.Context, opts *livekit.ListSIPOutboundTrunkRequest) (*livekit.ListSIPOutboundTrunkResponse, error)
|
||||
DeleteSIPTrunk(ctx context.Context, sipTrunkID string) error
|
||||
|
||||
StoreSIPDispatchRule(ctx context.Context, info *livekit.SIPDispatchRuleInfo) error
|
||||
LoadSIPDispatchRule(ctx context.Context, sipDispatchRuleID string) (*livekit.SIPDispatchRuleInfo, error)
|
||||
ListSIPDispatchRule(ctx context.Context, opts *livekit.ListSIPDispatchRuleRequest) (*livekit.ListSIPDispatchRuleResponse, error)
|
||||
DeleteSIPDispatchRule(ctx context.Context, sipDispatchRuleID string) error
|
||||
}
|
||||
|
||||
//counterfeiter:generate . AgentStore
|
||||
type AgentStore interface {
|
||||
StoreAgentDispatch(ctx context.Context, dispatch *livekit.AgentDispatch) error
|
||||
DeleteAgentDispatch(ctx context.Context, dispatch *livekit.AgentDispatch) error
|
||||
ListAgentDispatches(ctx context.Context, roomName livekit.RoomName) ([]*livekit.AgentDispatch, error)
|
||||
|
||||
StoreAgentJob(ctx context.Context, job *livekit.Job) error
|
||||
DeleteAgentJob(ctx context.Context, job *livekit.Job) error
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
// 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 service
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"google.golang.org/protobuf/types/known/emptypb"
|
||||
|
||||
"github.com/livekit/protocol/livekit"
|
||||
"github.com/livekit/protocol/logger"
|
||||
"github.com/livekit/protocol/rpc"
|
||||
"github.com/livekit/psrpc"
|
||||
|
||||
"github.com/livekit/livekit-server/pkg/telemetry"
|
||||
)
|
||||
|
||||
type IOInfoService struct {
|
||||
ioServer rpc.IOInfoServer
|
||||
|
||||
es EgressStore
|
||||
is IngressStore
|
||||
ss SIPStore
|
||||
telemetry telemetry.TelemetryService
|
||||
|
||||
shutdown chan struct{}
|
||||
}
|
||||
|
||||
func NewIOInfoService(
|
||||
bus psrpc.MessageBus,
|
||||
es EgressStore,
|
||||
is IngressStore,
|
||||
ss SIPStore,
|
||||
ts telemetry.TelemetryService,
|
||||
) (*IOInfoService, error) {
|
||||
s := &IOInfoService{
|
||||
es: es,
|
||||
is: is,
|
||||
ss: ss,
|
||||
telemetry: ts,
|
||||
shutdown: make(chan struct{}),
|
||||
}
|
||||
|
||||
if bus != nil {
|
||||
ioServer, err := rpc.NewIOInfoServer(s, bus)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.ioServer = ioServer
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (s *IOInfoService) Start() error {
|
||||
if s.es != nil {
|
||||
rs := s.es.(*RedisStore)
|
||||
err := rs.Start()
|
||||
if err != nil {
|
||||
logger.Errorw("failed to start redis egress worker", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *IOInfoService) Stop() {
|
||||
close(s.shutdown)
|
||||
|
||||
if s.ioServer != nil {
|
||||
s.ioServer.Shutdown()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *IOInfoService) CreateEgress(ctx context.Context, info *livekit.EgressInfo) (*emptypb.Empty, error) {
|
||||
// check if egress already exists to avoid duplicate EgressStarted event
|
||||
if _, err := s.es.LoadEgress(ctx, info.EgressId); err == nil {
|
||||
return &emptypb.Empty{}, nil
|
||||
}
|
||||
|
||||
err := s.es.StoreEgress(ctx, info)
|
||||
if err != nil {
|
||||
logger.Errorw("could not update egress", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.telemetry.EgressStarted(ctx, info)
|
||||
|
||||
return &emptypb.Empty{}, nil
|
||||
}
|
||||
|
||||
func (s *IOInfoService) UpdateEgress(ctx context.Context, info *livekit.EgressInfo) (*emptypb.Empty, error) {
|
||||
err := s.es.UpdateEgress(ctx, info)
|
||||
|
||||
switch info.Status {
|
||||
case livekit.EgressStatus_EGRESS_ACTIVE,
|
||||
livekit.EgressStatus_EGRESS_ENDING:
|
||||
s.telemetry.EgressUpdated(ctx, info)
|
||||
|
||||
case livekit.EgressStatus_EGRESS_COMPLETE,
|
||||
livekit.EgressStatus_EGRESS_FAILED,
|
||||
livekit.EgressStatus_EGRESS_ABORTED,
|
||||
livekit.EgressStatus_EGRESS_LIMIT_REACHED:
|
||||
s.telemetry.EgressEnded(ctx, info)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
logger.Errorw("could not update egress", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &emptypb.Empty{}, nil
|
||||
}
|
||||
|
||||
func (s *IOInfoService) GetEgress(ctx context.Context, req *rpc.GetEgressRequest) (*livekit.EgressInfo, error) {
|
||||
info, err := s.es.LoadEgress(ctx, req.EgressId)
|
||||
if err != nil {
|
||||
logger.Errorw("failed to load egress", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return info, nil
|
||||
}
|
||||
|
||||
func (s *IOInfoService) ListEgress(ctx context.Context, req *livekit.ListEgressRequest) (*livekit.ListEgressResponse, error) {
|
||||
if req.EgressId != "" {
|
||||
info, err := s.es.LoadEgress(ctx, req.EgressId)
|
||||
if err != nil {
|
||||
logger.Errorw("failed to load egress", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &livekit.ListEgressResponse{Items: []*livekit.EgressInfo{info}}, nil
|
||||
}
|
||||
|
||||
items, err := s.es.ListEgress(ctx, livekit.RoomName(req.RoomName), req.Active)
|
||||
if err != nil {
|
||||
logger.Errorw("failed to list egress", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &livekit.ListEgressResponse{Items: items}, nil
|
||||
}
|
||||
|
||||
func (s *IOInfoService) UpdateMetrics(ctx context.Context, req *rpc.UpdateMetricsRequest) (*emptypb.Empty, error) {
|
||||
logger.Infow("received egress metrics",
|
||||
"egressID", req.Info.EgressId,
|
||||
"avgCpu", req.AvgCpuUsage,
|
||||
"maxCpu", req.MaxCpuUsage,
|
||||
)
|
||||
return &emptypb.Empty{}, nil
|
||||
}
|
||||
|
||||
func (s *IOInfoService) UpdateSIPCallState(ctx context.Context, req *rpc.UpdateSIPCallStateRequest) (*emptypb.Empty, error) {
|
||||
// TODO: placeholder
|
||||
return &emptypb.Empty{}, nil
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
// Copyright 2024 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 service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/livekit/protocol/livekit"
|
||||
"github.com/livekit/protocol/logger"
|
||||
"github.com/livekit/protocol/rpc"
|
||||
"google.golang.org/protobuf/types/known/emptypb"
|
||||
)
|
||||
|
||||
func (s *IOInfoService) CreateIngress(ctx context.Context, info *livekit.IngressInfo) (*emptypb.Empty, error) {
|
||||
err := s.is.StoreIngress(ctx, info)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.telemetry.IngressCreated(ctx, info)
|
||||
|
||||
return &emptypb.Empty{}, nil
|
||||
}
|
||||
|
||||
func (s *IOInfoService) GetIngressInfo(ctx context.Context, req *rpc.GetIngressInfoRequest) (*rpc.GetIngressInfoResponse, error) {
|
||||
info, err := s.loadIngressFromInfoRequest(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &rpc.GetIngressInfoResponse{Info: info}, nil
|
||||
}
|
||||
|
||||
func (s *IOInfoService) loadIngressFromInfoRequest(req *rpc.GetIngressInfoRequest) (info *livekit.IngressInfo, err error) {
|
||||
if req.IngressId != "" {
|
||||
info, err = s.is.LoadIngress(context.Background(), req.IngressId)
|
||||
} else if req.StreamKey != "" {
|
||||
info, err = s.is.LoadIngressFromStreamKey(context.Background(), req.StreamKey)
|
||||
} else {
|
||||
err = errors.New("request needs to specify either IngressId or StreamKey")
|
||||
}
|
||||
return info, err
|
||||
}
|
||||
|
||||
func (s *IOInfoService) UpdateIngressState(ctx context.Context, req *rpc.UpdateIngressStateRequest) (*emptypb.Empty, error) {
|
||||
info, err := s.is.LoadIngress(ctx, req.IngressId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = s.is.UpdateIngressState(ctx, req.IngressId, req.State); err != nil {
|
||||
logger.Errorw("could not update ingress", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if info.State.Status != req.State.Status {
|
||||
info.State = req.State
|
||||
|
||||
switch req.State.Status {
|
||||
case livekit.IngressState_ENDPOINT_ERROR,
|
||||
livekit.IngressState_ENDPOINT_INACTIVE,
|
||||
livekit.IngressState_ENDPOINT_COMPLETE:
|
||||
s.telemetry.IngressEnded(ctx, info)
|
||||
|
||||
if req.State.Error != "" {
|
||||
logger.Infow("ingress failed", "error", req.State.Error, "ingressID", req.IngressId)
|
||||
} else {
|
||||
logger.Infow("ingress ended", "ingressID", req.IngressId)
|
||||
}
|
||||
|
||||
case livekit.IngressState_ENDPOINT_PUBLISHING:
|
||||
s.telemetry.IngressStarted(ctx, info)
|
||||
|
||||
logger.Infow("ingress started", "ingressID", req.IngressId)
|
||||
|
||||
case livekit.IngressState_ENDPOINT_BUFFERING:
|
||||
s.telemetry.IngressUpdated(ctx, info)
|
||||
|
||||
logger.Infow("ingress buffering", "ingressID", req.IngressId)
|
||||
}
|
||||
} else {
|
||||
// Status didn't change, send Updated event
|
||||
info.State = req.State
|
||||
|
||||
s.telemetry.IngressUpdated(ctx, info)
|
||||
|
||||
logger.Infow("ingress state updated", "ingressID", req.IngressId, "status", info.State.Status)
|
||||
}
|
||||
|
||||
return &emptypb.Empty{}, nil
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
// 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 service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/netip"
|
||||
|
||||
"github.com/dennwc/iters"
|
||||
"github.com/twitchtv/twirp"
|
||||
|
||||
"github.com/livekit/protocol/livekit"
|
||||
"github.com/livekit/protocol/logger"
|
||||
"github.com/livekit/protocol/rpc"
|
||||
"github.com/livekit/protocol/sip"
|
||||
)
|
||||
|
||||
// matchSIPTrunk finds a SIP Trunk definition matching the request.
|
||||
// Returns nil if no rules matched or an error if there are conflicting definitions.
|
||||
func (s *IOInfoService) matchSIPTrunk(ctx context.Context, trunkID, calling, called string, srcIP netip.Addr) (*livekit.SIPInboundTrunkInfo, error) {
|
||||
if s.ss == nil {
|
||||
return nil, ErrSIPNotConnected
|
||||
}
|
||||
if trunkID != "" {
|
||||
// This is a best-effort optimization. Fallthrough to listing trunks if it doesn't work.
|
||||
if tr, err := s.ss.LoadSIPInboundTrunk(ctx, trunkID); err == nil {
|
||||
tr, err = sip.MatchTrunkIter(iters.Slice([]*livekit.SIPInboundTrunkInfo{tr}), srcIP, calling, called)
|
||||
if err == nil {
|
||||
return tr, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
it := s.SelectSIPInboundTrunk(ctx, called)
|
||||
return sip.MatchTrunkIter(it, srcIP, calling, called)
|
||||
}
|
||||
|
||||
func (s *IOInfoService) SelectSIPInboundTrunk(ctx context.Context, called string) iters.Iter[*livekit.SIPInboundTrunkInfo] {
|
||||
it := livekit.ListPageIter(s.ss.ListSIPInboundTrunk, &livekit.ListSIPInboundTrunkRequest{
|
||||
Numbers: []string{called},
|
||||
})
|
||||
return iters.PagesAsIter(ctx, it)
|
||||
}
|
||||
|
||||
// matchSIPDispatchRule finds the best dispatch rule matching the request parameters. Returns an error if no rule matched.
|
||||
// Trunk parameter can be nil, in which case only wildcard dispatch rules will be effective (ones without Trunk IDs).
|
||||
func (s *IOInfoService) matchSIPDispatchRule(ctx context.Context, trunk *livekit.SIPInboundTrunkInfo, req *rpc.EvaluateSIPDispatchRulesRequest) (*livekit.SIPDispatchRuleInfo, error) {
|
||||
if s.ss == nil {
|
||||
return nil, ErrSIPNotConnected
|
||||
}
|
||||
var trunkID string
|
||||
if trunk != nil {
|
||||
trunkID = trunk.SipTrunkId
|
||||
}
|
||||
// Trunk can still be nil here in case none matched or were defined.
|
||||
// This is still fine, but only in case we'll match exactly one wildcard dispatch rule.
|
||||
it := s.SelectSIPDispatchRule(ctx, trunkID)
|
||||
return sip.MatchDispatchRuleIter(trunk, it, req)
|
||||
}
|
||||
|
||||
func (s *IOInfoService) SelectSIPDispatchRule(ctx context.Context, trunkID string) iters.Iter[*livekit.SIPDispatchRuleInfo] {
|
||||
var trunkIDs []string
|
||||
if trunkID != "" {
|
||||
trunkIDs = []string{trunkID}
|
||||
}
|
||||
it := livekit.ListPageIter(s.ss.ListSIPDispatchRule, &livekit.ListSIPDispatchRuleRequest{
|
||||
TrunkIds: trunkIDs,
|
||||
})
|
||||
return iters.PagesAsIter(ctx, it)
|
||||
}
|
||||
|
||||
func (s *IOInfoService) EvaluateSIPDispatchRules(ctx context.Context, req *rpc.EvaluateSIPDispatchRulesRequest) (*rpc.EvaluateSIPDispatchRulesResponse, error) {
|
||||
log := logger.GetLogger()
|
||||
log = log.WithValues("toUser", req.CalledNumber, "fromUser", req.CallingNumber, "src", req.SrcAddress)
|
||||
if req.SrcAddress == "" {
|
||||
log.Warnw("source address is not set", nil)
|
||||
// TODO: return error in the next release
|
||||
}
|
||||
srcIP, err := netip.ParseAddr(req.SrcAddress)
|
||||
if req.SrcAddress != "" && err != nil {
|
||||
log.Errorw("cannot parse source IP", err)
|
||||
return nil, twirp.WrapError(twirp.NewError(twirp.InvalidArgument, err.Error()), err)
|
||||
}
|
||||
trunk, err := s.matchSIPTrunk(ctx, req.SipTrunkId, req.CallingNumber, req.CalledNumber, srcIP)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
trunkID := ""
|
||||
if trunk != nil {
|
||||
trunkID = trunk.SipTrunkId
|
||||
}
|
||||
log = log.WithValues("sipTrunk", trunkID)
|
||||
if trunk != nil {
|
||||
log.Debugw("SIP trunk matched")
|
||||
} else {
|
||||
log.Debugw("No SIP trunk matched")
|
||||
}
|
||||
best, err := s.matchSIPDispatchRule(ctx, trunk, req)
|
||||
if err != nil {
|
||||
if e := (*sip.ErrNoDispatchMatched)(nil); errors.As(err, &e) {
|
||||
return &rpc.EvaluateSIPDispatchRulesResponse{
|
||||
SipTrunkId: trunkID,
|
||||
Result: rpc.SIPDispatchResult_DROP,
|
||||
}, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
log.Debugw("SIP dispatch rule matched", "sipRule", best.SipDispatchRuleId)
|
||||
resp, err := sip.EvaluateDispatchRule("", trunk, best, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp.SipTrunkId = trunkID
|
||||
return resp, err
|
||||
}
|
||||
|
||||
func (s *IOInfoService) GetSIPTrunkAuthentication(ctx context.Context, req *rpc.GetSIPTrunkAuthenticationRequest) (*rpc.GetSIPTrunkAuthenticationResponse, error) {
|
||||
log := logger.GetLogger()
|
||||
log = log.WithValues("toUser", req.To, "fromUser", req.From, "src", req.SrcAddress)
|
||||
if req.SrcAddress == "" {
|
||||
log.Warnw("source address is not set", nil)
|
||||
// TODO: return error in the next release
|
||||
}
|
||||
srcIP, err := netip.ParseAddr(req.SrcAddress)
|
||||
if req.SrcAddress != "" && err != nil {
|
||||
log.Errorw("cannot parse source IP", err)
|
||||
return nil, twirp.WrapError(twirp.NewError(twirp.InvalidArgument, err.Error()), err)
|
||||
}
|
||||
trunk, err := s.matchSIPTrunk(ctx, "", req.From, req.To, srcIP)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if trunk == nil {
|
||||
log.Debugw("No SIP trunk matched for auth", "sipTrunk", "")
|
||||
return &rpc.GetSIPTrunkAuthenticationResponse{}, nil
|
||||
}
|
||||
log.Debugw("SIP trunk matched for auth", "sipTrunk", trunk.SipTrunkId)
|
||||
return &rpc.GetSIPTrunkAuthenticationResponse{
|
||||
SipTrunkId: trunk.SipTrunkId,
|
||||
Username: trunk.AuthUsername,
|
||||
Password: trunk.AuthPassword,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
// Copyright 2024 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 service_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/dennwc/iters"
|
||||
"github.com/livekit/livekit-server/pkg/service"
|
||||
"github.com/livekit/psrpc"
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"github.com/livekit/protocol/livekit"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func ioStoreDocker(t testing.TB) (*service.IOInfoService, *service.RedisStore) {
|
||||
r := redisClientDocker(t)
|
||||
bus := psrpc.NewRedisMessageBus(r)
|
||||
rs := service.NewRedisStore(r)
|
||||
io, err := service.NewIOInfoService(bus, rs, rs, rs, nil)
|
||||
require.NoError(t, err)
|
||||
return io, rs
|
||||
}
|
||||
|
||||
func TestSIPTrunkSelect(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
s, rs := ioStoreDocker(t)
|
||||
|
||||
for _, tr := range []*livekit.SIPInboundTrunkInfo{
|
||||
{SipTrunkId: "any", Numbers: nil},
|
||||
{SipTrunkId: "B", Numbers: []string{"B1", "B2"}},
|
||||
{SipTrunkId: "BC", Numbers: []string{"B1", "C1"}},
|
||||
} {
|
||||
err := rs.StoreSIPInboundTrunk(ctx, tr)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
for _, tr := range []*livekit.SIPTrunkInfo{
|
||||
{SipTrunkId: "old-any", OutboundNumber: ""},
|
||||
{SipTrunkId: "old-A", OutboundNumber: "A"},
|
||||
} {
|
||||
err := rs.StoreSIPTrunk(ctx, tr)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
for _, c := range []struct {
|
||||
number string
|
||||
exp []string
|
||||
}{
|
||||
{"A", []string{"old-A", "old-any", "any"}},
|
||||
{"B1", []string{"B", "BC", "old-any", "any"}},
|
||||
{"B2", []string{"B", "old-any", "any"}},
|
||||
{"C1", []string{"BC", "old-any", "any"}},
|
||||
{"wrong", []string{"old-any", "any"}},
|
||||
} {
|
||||
t.Run(c.number, func(t *testing.T) {
|
||||
it := s.SelectSIPInboundTrunk(ctx, c.number)
|
||||
defer it.Close()
|
||||
list, err := iters.All(it)
|
||||
require.NoError(t, err)
|
||||
var ids []string
|
||||
for _, v := range list {
|
||||
ids = append(ids, v.SipTrunkId)
|
||||
}
|
||||
slices.Sort(c.exp)
|
||||
slices.Sort(ids)
|
||||
require.Equal(t, c.exp, ids)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSIPRuleSelect(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
s, rs := ioStoreDocker(t)
|
||||
|
||||
for _, r := range []*livekit.SIPDispatchRuleInfo{
|
||||
{SipDispatchRuleId: "any", TrunkIds: nil},
|
||||
{SipDispatchRuleId: "B", TrunkIds: []string{"B1", "B2"}},
|
||||
{SipDispatchRuleId: "BC", TrunkIds: []string{"B1", "C1"}},
|
||||
} {
|
||||
err := rs.StoreSIPDispatchRule(ctx, r)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
for _, c := range []struct {
|
||||
trunk string
|
||||
exp []string
|
||||
}{
|
||||
{"A", []string{"any"}},
|
||||
{"B1", []string{"B", "BC", "any"}},
|
||||
{"B2", []string{"B", "any"}},
|
||||
{"C1", []string{"BC", "any"}},
|
||||
{"wrong", []string{"any"}},
|
||||
} {
|
||||
t.Run(c.trunk, func(t *testing.T) {
|
||||
it := s.SelectSIPDispatchRule(ctx, c.trunk)
|
||||
defer it.Close()
|
||||
list, err := iters.All(it)
|
||||
require.NoError(t, err)
|
||||
var ids []string
|
||||
for _, v := range list {
|
||||
ids = append(ids, v.SipDispatchRuleId)
|
||||
}
|
||||
slices.Sort(c.exp)
|
||||
slices.Sort(ids)
|
||||
require.Equal(t, c.exp, ids)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,283 @@
|
||||
// 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 service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/thoas/go-funk"
|
||||
|
||||
"github.com/livekit/protocol/livekit"
|
||||
"github.com/livekit/protocol/utils"
|
||||
)
|
||||
|
||||
// encapsulates CRUD operations for room settings
|
||||
type LocalStore struct {
|
||||
// map of roomName => room
|
||||
rooms map[livekit.RoomName]*livekit.Room
|
||||
roomInternal map[livekit.RoomName]*livekit.RoomInternal
|
||||
// map of roomName => { identity: participant }
|
||||
participants map[livekit.RoomName]map[livekit.ParticipantIdentity]*livekit.ParticipantInfo
|
||||
|
||||
agentDispatches map[livekit.RoomName]map[string]*livekit.AgentDispatch
|
||||
agentJobs map[livekit.RoomName]map[string]*livekit.Job
|
||||
|
||||
lock sync.RWMutex
|
||||
globalLock sync.Mutex
|
||||
}
|
||||
|
||||
func NewLocalStore() *LocalStore {
|
||||
return &LocalStore{
|
||||
rooms: make(map[livekit.RoomName]*livekit.Room),
|
||||
roomInternal: make(map[livekit.RoomName]*livekit.RoomInternal),
|
||||
participants: make(map[livekit.RoomName]map[livekit.ParticipantIdentity]*livekit.ParticipantInfo),
|
||||
agentDispatches: make(map[livekit.RoomName]map[string]*livekit.AgentDispatch),
|
||||
agentJobs: make(map[livekit.RoomName]map[string]*livekit.Job),
|
||||
lock: sync.RWMutex{},
|
||||
}
|
||||
}
|
||||
|
||||
func (s *LocalStore) StoreRoom(_ context.Context, room *livekit.Room, internal *livekit.RoomInternal) error {
|
||||
if room.CreationTime == 0 {
|
||||
now := time.Now()
|
||||
room.CreationTime = now.Unix()
|
||||
room.CreationTimeMs = now.UnixMilli()
|
||||
}
|
||||
roomName := livekit.RoomName(room.Name)
|
||||
|
||||
s.lock.Lock()
|
||||
s.rooms[roomName] = room
|
||||
s.roomInternal[roomName] = internal
|
||||
s.lock.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *LocalStore) LoadRoom(_ context.Context, roomName livekit.RoomName, includeInternal bool) (*livekit.Room, *livekit.RoomInternal, error) {
|
||||
s.lock.RLock()
|
||||
defer s.lock.RUnlock()
|
||||
|
||||
room := s.rooms[roomName]
|
||||
if room == nil {
|
||||
return nil, nil, ErrRoomNotFound
|
||||
}
|
||||
|
||||
var internal *livekit.RoomInternal
|
||||
if includeInternal {
|
||||
internal = s.roomInternal[roomName]
|
||||
}
|
||||
|
||||
return room, internal, nil
|
||||
}
|
||||
|
||||
func (s *LocalStore) ListRooms(_ context.Context, roomNames []livekit.RoomName) ([]*livekit.Room, error) {
|
||||
s.lock.RLock()
|
||||
defer s.lock.RUnlock()
|
||||
rooms := make([]*livekit.Room, 0, len(s.rooms))
|
||||
for _, r := range s.rooms {
|
||||
if roomNames == nil || funk.Contains(roomNames, livekit.RoomName(r.Name)) {
|
||||
rooms = append(rooms, r)
|
||||
}
|
||||
}
|
||||
return rooms, nil
|
||||
}
|
||||
|
||||
func (s *LocalStore) DeleteRoom(ctx context.Context, roomName livekit.RoomName) error {
|
||||
room, _, err := s.LoadRoom(ctx, roomName, false)
|
||||
if err == ErrRoomNotFound {
|
||||
return nil
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
delete(s.participants, livekit.RoomName(room.Name))
|
||||
delete(s.rooms, livekit.RoomName(room.Name))
|
||||
delete(s.roomInternal, livekit.RoomName(room.Name))
|
||||
delete(s.agentDispatches, livekit.RoomName(room.Name))
|
||||
delete(s.agentJobs, livekit.RoomName(room.Name))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *LocalStore) LockRoom(_ context.Context, _ livekit.RoomName, _ time.Duration) (string, error) {
|
||||
// local rooms lock & unlock globally
|
||||
s.globalLock.Lock()
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (s *LocalStore) UnlockRoom(_ context.Context, _ livekit.RoomName, _ string) error {
|
||||
s.globalLock.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *LocalStore) StoreParticipant(_ context.Context, roomName livekit.RoomName, participant *livekit.ParticipantInfo) error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
roomParticipants := s.participants[roomName]
|
||||
if roomParticipants == nil {
|
||||
roomParticipants = make(map[livekit.ParticipantIdentity]*livekit.ParticipantInfo)
|
||||
s.participants[roomName] = roomParticipants
|
||||
}
|
||||
roomParticipants[livekit.ParticipantIdentity(participant.Identity)] = participant
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *LocalStore) LoadParticipant(_ context.Context, roomName livekit.RoomName, identity livekit.ParticipantIdentity) (*livekit.ParticipantInfo, error) {
|
||||
s.lock.RLock()
|
||||
defer s.lock.RUnlock()
|
||||
|
||||
roomParticipants := s.participants[roomName]
|
||||
if roomParticipants == nil {
|
||||
return nil, ErrParticipantNotFound
|
||||
}
|
||||
participant := roomParticipants[identity]
|
||||
if participant == nil {
|
||||
return nil, ErrParticipantNotFound
|
||||
}
|
||||
return participant, nil
|
||||
}
|
||||
|
||||
func (s *LocalStore) ListParticipants(_ context.Context, roomName livekit.RoomName) ([]*livekit.ParticipantInfo, error) {
|
||||
s.lock.RLock()
|
||||
defer s.lock.RUnlock()
|
||||
|
||||
roomParticipants := s.participants[roomName]
|
||||
if roomParticipants == nil {
|
||||
// empty array
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
items := make([]*livekit.ParticipantInfo, 0, len(roomParticipants))
|
||||
for _, p := range roomParticipants {
|
||||
items = append(items, p)
|
||||
}
|
||||
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func (s *LocalStore) DeleteParticipant(_ context.Context, roomName livekit.RoomName, identity livekit.ParticipantIdentity) error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
roomParticipants := s.participants[roomName]
|
||||
if roomParticipants != nil {
|
||||
delete(roomParticipants, identity)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *LocalStore) StoreAgentDispatch(ctx context.Context, dispatch *livekit.AgentDispatch) error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
clone := utils.CloneProto(dispatch)
|
||||
if clone.State != nil {
|
||||
clone.State.Jobs = nil
|
||||
}
|
||||
|
||||
roomDispatches := s.agentDispatches[livekit.RoomName(dispatch.Room)]
|
||||
if roomDispatches == nil {
|
||||
roomDispatches = make(map[string]*livekit.AgentDispatch)
|
||||
s.agentDispatches[livekit.RoomName(dispatch.Room)] = roomDispatches
|
||||
}
|
||||
|
||||
roomDispatches[clone.Id] = clone
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *LocalStore) DeleteAgentDispatch(ctx context.Context, dispatch *livekit.AgentDispatch) error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
roomDispatches := s.agentDispatches[livekit.RoomName(dispatch.Room)]
|
||||
if roomDispatches != nil {
|
||||
delete(roomDispatches, dispatch.Id)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *LocalStore) ListAgentDispatches(ctx context.Context, roomName livekit.RoomName) ([]*livekit.AgentDispatch, error) {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
agentDispatches := s.agentDispatches[roomName]
|
||||
if agentDispatches == nil {
|
||||
return nil, nil
|
||||
}
|
||||
agentJobs := s.agentJobs[roomName]
|
||||
|
||||
var js []*livekit.Job
|
||||
if agentJobs != nil {
|
||||
for _, j := range agentJobs {
|
||||
js = append(js, utils.CloneProto(j))
|
||||
}
|
||||
}
|
||||
var ds []*livekit.AgentDispatch
|
||||
|
||||
m := make(map[string]*livekit.AgentDispatch)
|
||||
for _, d := range agentDispatches {
|
||||
clone := utils.CloneProto(d)
|
||||
m[d.Id] = clone
|
||||
ds = append(ds, clone)
|
||||
}
|
||||
|
||||
for _, j := range js {
|
||||
d := m[j.DispatchId]
|
||||
if d != nil {
|
||||
d.State.Jobs = append(d.State.Jobs, utils.CloneProto(j))
|
||||
}
|
||||
}
|
||||
|
||||
return ds, nil
|
||||
}
|
||||
|
||||
func (s *LocalStore) StoreAgentJob(ctx context.Context, job *livekit.Job) error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
clone := utils.CloneProto(job)
|
||||
clone.Room = nil
|
||||
if clone.Participant != nil {
|
||||
clone.Participant = &livekit.ParticipantInfo{
|
||||
Identity: clone.Participant.Identity,
|
||||
}
|
||||
}
|
||||
|
||||
roomJobs := s.agentJobs[livekit.RoomName(job.Room.Name)]
|
||||
if roomJobs == nil {
|
||||
roomJobs = make(map[string]*livekit.Job)
|
||||
s.agentJobs[livekit.RoomName(job.Room.Name)] = roomJobs
|
||||
}
|
||||
roomJobs[clone.Id] = clone
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *LocalStore) DeleteAgentJob(ctx context.Context, job *livekit.Job) error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
roomJobs := s.agentJobs[livekit.RoomName(job.Room.Name)]
|
||||
if roomJobs != nil {
|
||||
delete(roomJobs, job.Id)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,247 @@
|
||||
// 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 service
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/livekit/protocol/livekit"
|
||||
)
|
||||
|
||||
const (
|
||||
SIPTrunkKey = "sip_trunk"
|
||||
SIPInboundTrunkKey = "sip_inbound_trunk"
|
||||
SIPOutboundTrunkKey = "sip_outbound_trunk"
|
||||
SIPDispatchRuleKey = "sip_dispatch_rule"
|
||||
)
|
||||
|
||||
func (s *RedisStore) StoreSIPTrunk(ctx context.Context, info *livekit.SIPTrunkInfo) error {
|
||||
return redisStoreOne(s.ctx, s, SIPTrunkKey, info.SipTrunkId, info)
|
||||
}
|
||||
|
||||
func (s *RedisStore) StoreSIPInboundTrunk(ctx context.Context, info *livekit.SIPInboundTrunkInfo) error {
|
||||
return redisStoreOne(s.ctx, s, SIPInboundTrunkKey, info.SipTrunkId, info)
|
||||
}
|
||||
|
||||
func (s *RedisStore) StoreSIPOutboundTrunk(ctx context.Context, info *livekit.SIPOutboundTrunkInfo) error {
|
||||
return redisStoreOne(s.ctx, s, SIPOutboundTrunkKey, info.SipTrunkId, info)
|
||||
}
|
||||
|
||||
func (s *RedisStore) loadSIPLegacyTrunk(ctx context.Context, id string) (*livekit.SIPTrunkInfo, error) {
|
||||
return redisLoadOne[livekit.SIPTrunkInfo](ctx, s, SIPTrunkKey, id, ErrSIPTrunkNotFound)
|
||||
}
|
||||
|
||||
func (s *RedisStore) loadSIPInboundTrunk(ctx context.Context, id string) (*livekit.SIPInboundTrunkInfo, error) {
|
||||
return redisLoadOne[livekit.SIPInboundTrunkInfo](ctx, s, SIPInboundTrunkKey, id, ErrSIPTrunkNotFound)
|
||||
}
|
||||
|
||||
func (s *RedisStore) loadSIPOutboundTrunk(ctx context.Context, id string) (*livekit.SIPOutboundTrunkInfo, error) {
|
||||
return redisLoadOne[livekit.SIPOutboundTrunkInfo](ctx, s, SIPOutboundTrunkKey, id, ErrSIPTrunkNotFound)
|
||||
}
|
||||
|
||||
func (s *RedisStore) LoadSIPTrunk(ctx context.Context, id string) (*livekit.SIPTrunkInfo, error) {
|
||||
tr, err := s.loadSIPLegacyTrunk(ctx, id)
|
||||
if err == nil {
|
||||
return tr, nil
|
||||
} else if err != ErrSIPTrunkNotFound {
|
||||
return nil, err
|
||||
}
|
||||
in, err := s.loadSIPInboundTrunk(ctx, id)
|
||||
if err == nil {
|
||||
return in.AsTrunkInfo(), nil
|
||||
} else if err != ErrSIPTrunkNotFound {
|
||||
return nil, err
|
||||
}
|
||||
out, err := s.loadSIPOutboundTrunk(ctx, id)
|
||||
if err == nil {
|
||||
return out.AsTrunkInfo(), nil
|
||||
} else if err != ErrSIPTrunkNotFound {
|
||||
return nil, err
|
||||
}
|
||||
return nil, ErrSIPTrunkNotFound
|
||||
}
|
||||
|
||||
func (s *RedisStore) LoadSIPInboundTrunk(ctx context.Context, id string) (*livekit.SIPInboundTrunkInfo, error) {
|
||||
in, err := s.loadSIPInboundTrunk(ctx, id)
|
||||
if err == nil {
|
||||
return in, nil
|
||||
} else if err != ErrSIPTrunkNotFound {
|
||||
return nil, err
|
||||
}
|
||||
tr, err := s.loadSIPLegacyTrunk(ctx, id)
|
||||
if err == nil {
|
||||
return tr.AsInbound(), nil
|
||||
} else if err != ErrSIPTrunkNotFound {
|
||||
return nil, err
|
||||
}
|
||||
return nil, ErrSIPTrunkNotFound
|
||||
}
|
||||
|
||||
func (s *RedisStore) LoadSIPOutboundTrunk(ctx context.Context, id string) (*livekit.SIPOutboundTrunkInfo, error) {
|
||||
in, err := s.loadSIPOutboundTrunk(ctx, id)
|
||||
if err == nil {
|
||||
return in, nil
|
||||
} else if err != ErrSIPTrunkNotFound {
|
||||
return nil, err
|
||||
}
|
||||
tr, err := s.loadSIPLegacyTrunk(ctx, id)
|
||||
if err == nil {
|
||||
return tr.AsOutbound(), nil
|
||||
} else if err != ErrSIPTrunkNotFound {
|
||||
return nil, err
|
||||
}
|
||||
return nil, ErrSIPTrunkNotFound
|
||||
}
|
||||
|
||||
func (s *RedisStore) DeleteSIPTrunk(ctx context.Context, id string) error {
|
||||
tx := s.rc.TxPipeline()
|
||||
tx.HDel(s.ctx, SIPTrunkKey, id)
|
||||
tx.HDel(s.ctx, SIPInboundTrunkKey, id)
|
||||
tx.HDel(s.ctx, SIPOutboundTrunkKey, id)
|
||||
_, err := tx.Exec(ctx)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *RedisStore) listSIPLegacyTrunk(ctx context.Context, page *livekit.Pagination) ([]*livekit.SIPTrunkInfo, error) {
|
||||
return redisIterPage[livekit.SIPTrunkInfo](ctx, s, SIPTrunkKey, page)
|
||||
}
|
||||
|
||||
func (s *RedisStore) listSIPInboundTrunk(ctx context.Context, page *livekit.Pagination) ([]*livekit.SIPInboundTrunkInfo, error) {
|
||||
return redisIterPage[livekit.SIPInboundTrunkInfo](ctx, s, SIPInboundTrunkKey, page)
|
||||
}
|
||||
|
||||
func (s *RedisStore) listSIPOutboundTrunk(ctx context.Context, page *livekit.Pagination) ([]*livekit.SIPOutboundTrunkInfo, error) {
|
||||
return redisIterPage[livekit.SIPOutboundTrunkInfo](ctx, s, SIPOutboundTrunkKey, page)
|
||||
}
|
||||
|
||||
func (s *RedisStore) listSIPDispatchRule(ctx context.Context, page *livekit.Pagination) ([]*livekit.SIPDispatchRuleInfo, error) {
|
||||
return redisIterPage[livekit.SIPDispatchRuleInfo](ctx, s, SIPDispatchRuleKey, page)
|
||||
}
|
||||
|
||||
func (s *RedisStore) ListSIPTrunk(ctx context.Context, req *livekit.ListSIPTrunkRequest) (*livekit.ListSIPTrunkResponse, error) {
|
||||
var items []*livekit.SIPTrunkInfo
|
||||
old, err := s.listSIPLegacyTrunk(ctx, req.Page)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, t := range old {
|
||||
v := t
|
||||
if req.Filter(v) && req.Page.Filter(v) {
|
||||
items = append(items, v)
|
||||
}
|
||||
}
|
||||
in, err := s.listSIPInboundTrunk(ctx, req.Page)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, t := range in {
|
||||
v := t.AsTrunkInfo()
|
||||
if req.Filter(v) && req.Page.Filter(v) {
|
||||
items = append(items, v)
|
||||
}
|
||||
}
|
||||
out, err := s.listSIPOutboundTrunk(ctx, req.Page)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, t := range out {
|
||||
v := t.AsTrunkInfo()
|
||||
if req.Filter(v) && req.Page.Filter(v) {
|
||||
items = append(items, v)
|
||||
}
|
||||
}
|
||||
items = sortPage(items, req.Page)
|
||||
return &livekit.ListSIPTrunkResponse{Items: items}, nil
|
||||
}
|
||||
|
||||
func (s *RedisStore) ListSIPInboundTrunk(ctx context.Context, req *livekit.ListSIPInboundTrunkRequest) (*livekit.ListSIPInboundTrunkResponse, error) {
|
||||
var items []*livekit.SIPInboundTrunkInfo
|
||||
in, err := s.listSIPInboundTrunk(ctx, req.Page)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, t := range in {
|
||||
v := t
|
||||
if req.Filter(v) && req.Page.Filter(v) {
|
||||
items = append(items, v)
|
||||
}
|
||||
}
|
||||
old, err := s.listSIPLegacyTrunk(ctx, req.Page)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, t := range old {
|
||||
v := t.AsInbound()
|
||||
if req.Filter(v) && req.Page.Filter(v) {
|
||||
items = append(items, v)
|
||||
}
|
||||
}
|
||||
items = sortPage(items, req.Page)
|
||||
return &livekit.ListSIPInboundTrunkResponse{Items: items}, nil
|
||||
}
|
||||
|
||||
func (s *RedisStore) ListSIPOutboundTrunk(ctx context.Context, req *livekit.ListSIPOutboundTrunkRequest) (*livekit.ListSIPOutboundTrunkResponse, error) {
|
||||
var items []*livekit.SIPOutboundTrunkInfo
|
||||
out, err := s.listSIPOutboundTrunk(ctx, req.Page)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, t := range out {
|
||||
v := t
|
||||
if req.Filter(v) && req.Page.Filter(v) {
|
||||
items = append(items, v)
|
||||
}
|
||||
}
|
||||
old, err := s.listSIPLegacyTrunk(ctx, req.Page)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, t := range old {
|
||||
v := t.AsOutbound()
|
||||
if req.Filter(v) && req.Page.Filter(v) {
|
||||
items = append(items, v)
|
||||
}
|
||||
}
|
||||
items = sortPage(items, req.Page)
|
||||
return &livekit.ListSIPOutboundTrunkResponse{Items: items}, nil
|
||||
}
|
||||
|
||||
func (s *RedisStore) StoreSIPDispatchRule(ctx context.Context, info *livekit.SIPDispatchRuleInfo) error {
|
||||
return redisStoreOne(ctx, s, SIPDispatchRuleKey, info.SipDispatchRuleId, info)
|
||||
}
|
||||
|
||||
func (s *RedisStore) LoadSIPDispatchRule(ctx context.Context, sipDispatchRuleId string) (*livekit.SIPDispatchRuleInfo, error) {
|
||||
return redisLoadOne[livekit.SIPDispatchRuleInfo](ctx, s, SIPDispatchRuleKey, sipDispatchRuleId, ErrSIPDispatchRuleNotFound)
|
||||
}
|
||||
|
||||
func (s *RedisStore) DeleteSIPDispatchRule(ctx context.Context, sipDispatchRuleId string) error {
|
||||
return s.rc.HDel(s.ctx, SIPDispatchRuleKey, sipDispatchRuleId).Err()
|
||||
}
|
||||
|
||||
func (s *RedisStore) ListSIPDispatchRule(ctx context.Context, req *livekit.ListSIPDispatchRuleRequest) (*livekit.ListSIPDispatchRuleResponse, error) {
|
||||
var items []*livekit.SIPDispatchRuleInfo
|
||||
out, err := s.listSIPDispatchRule(ctx, req.Page)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, t := range out {
|
||||
v := t
|
||||
if req.Filter(v) && req.Page.Filter(v) {
|
||||
items = append(items, v)
|
||||
}
|
||||
}
|
||||
items = sortPage(items, req.Page)
|
||||
return &livekit.ListSIPDispatchRuleResponse{Items: items}, nil
|
||||
}
|
||||
@@ -0,0 +1,357 @@
|
||||
// Copyright 2024 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 service_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/dennwc/iters"
|
||||
"github.com/livekit/protocol/livekit"
|
||||
"github.com/livekit/protocol/utils"
|
||||
"github.com/livekit/protocol/utils/guid"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"github.com/livekit/livekit-server/pkg/service"
|
||||
)
|
||||
|
||||
func TestSIPStoreDispatch(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
rs := redisStoreDocker(t)
|
||||
|
||||
id := guid.New(utils.SIPDispatchRulePrefix)
|
||||
|
||||
// No dispatch rules initially.
|
||||
list, err := rs.ListSIPDispatchRule(ctx, &livekit.ListSIPDispatchRuleRequest{})
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, list.Items)
|
||||
|
||||
// Loading non-existent dispatch should return proper not found error.
|
||||
got, err := rs.LoadSIPDispatchRule(ctx, id)
|
||||
require.Equal(t, service.ErrSIPDispatchRuleNotFound, err)
|
||||
require.Nil(t, got)
|
||||
|
||||
// Creation without ID should fail.
|
||||
rule := &livekit.SIPDispatchRuleInfo{
|
||||
TrunkIds: []string{"trunk"},
|
||||
Rule: &livekit.SIPDispatchRule{Rule: &livekit.SIPDispatchRule_DispatchRuleDirect{
|
||||
DispatchRuleDirect: &livekit.SIPDispatchRuleDirect{
|
||||
RoomName: "room",
|
||||
Pin: "1234",
|
||||
},
|
||||
}},
|
||||
}
|
||||
err = rs.StoreSIPDispatchRule(ctx, rule)
|
||||
require.Error(t, err)
|
||||
|
||||
// Creation
|
||||
rule.SipDispatchRuleId = id
|
||||
err = rs.StoreSIPDispatchRule(ctx, rule)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Loading
|
||||
got, err = rs.LoadSIPDispatchRule(ctx, id)
|
||||
require.NoError(t, err)
|
||||
require.True(t, proto.Equal(rule, got))
|
||||
|
||||
// Listing
|
||||
list, err = rs.ListSIPDispatchRule(ctx, &livekit.ListSIPDispatchRuleRequest{})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, list.Items, 1)
|
||||
require.True(t, proto.Equal(rule, list.Items[0]))
|
||||
|
||||
// Deletion. Should not return error if not exists.
|
||||
err = rs.DeleteSIPDispatchRule(ctx, id)
|
||||
require.NoError(t, err)
|
||||
err = rs.DeleteSIPDispatchRule(ctx, id)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Check that it's deleted.
|
||||
list, err = rs.ListSIPDispatchRule(ctx, &livekit.ListSIPDispatchRuleRequest{})
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, list.Items)
|
||||
|
||||
got, err = rs.LoadSIPDispatchRule(ctx, id)
|
||||
require.Equal(t, service.ErrSIPDispatchRuleNotFound, err)
|
||||
require.Nil(t, got)
|
||||
}
|
||||
|
||||
func TestSIPStoreTrunk(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
rs := redisStoreDocker(t)
|
||||
|
||||
oldID := guid.New(utils.SIPTrunkPrefix)
|
||||
inID := guid.New(utils.SIPTrunkPrefix)
|
||||
outID := guid.New(utils.SIPTrunkPrefix)
|
||||
|
||||
// No trunks initially. Check legacy, inbound, outbound.
|
||||
// Loading non-existent trunk should return proper not found error.
|
||||
oldList, err := rs.ListSIPTrunk(ctx, &livekit.ListSIPTrunkRequest{})
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, oldList.Items)
|
||||
|
||||
old, err := rs.LoadSIPTrunk(ctx, oldID)
|
||||
require.Equal(t, service.ErrSIPTrunkNotFound, err)
|
||||
require.Nil(t, old)
|
||||
|
||||
inList, err := rs.ListSIPInboundTrunk(ctx, &livekit.ListSIPInboundTrunkRequest{})
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, inList.Items)
|
||||
|
||||
in, err := rs.LoadSIPInboundTrunk(ctx, oldID)
|
||||
require.Equal(t, service.ErrSIPTrunkNotFound, err)
|
||||
require.Nil(t, in)
|
||||
|
||||
outList, err := rs.ListSIPOutboundTrunk(ctx, &livekit.ListSIPOutboundTrunkRequest{})
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, outList.Items)
|
||||
|
||||
out, err := rs.LoadSIPOutboundTrunk(ctx, oldID)
|
||||
require.Equal(t, service.ErrSIPTrunkNotFound, err)
|
||||
require.Nil(t, out)
|
||||
|
||||
// Creation without ID should fail.
|
||||
oldT := &livekit.SIPTrunkInfo{
|
||||
Name: "Legacy",
|
||||
}
|
||||
err = rs.StoreSIPTrunk(ctx, oldT)
|
||||
require.Error(t, err)
|
||||
|
||||
inT := &livekit.SIPInboundTrunkInfo{
|
||||
Name: "Inbound",
|
||||
}
|
||||
err = rs.StoreSIPInboundTrunk(ctx, inT)
|
||||
require.Error(t, err)
|
||||
|
||||
outT := &livekit.SIPOutboundTrunkInfo{
|
||||
Name: "Outbound",
|
||||
}
|
||||
err = rs.StoreSIPOutboundTrunk(ctx, outT)
|
||||
require.Error(t, err)
|
||||
|
||||
// Creation
|
||||
oldT.SipTrunkId = oldID
|
||||
err = rs.StoreSIPTrunk(ctx, oldT)
|
||||
require.NoError(t, err)
|
||||
|
||||
inT.SipTrunkId = inID
|
||||
err = rs.StoreSIPInboundTrunk(ctx, inT)
|
||||
require.NoError(t, err)
|
||||
|
||||
outT.SipTrunkId = outID
|
||||
err = rs.StoreSIPOutboundTrunk(ctx, outT)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Loading (with matching kind)
|
||||
oldT2, err := rs.LoadSIPTrunk(ctx, oldID)
|
||||
require.NoError(t, err)
|
||||
require.True(t, proto.Equal(oldT, oldT2))
|
||||
|
||||
inT2, err := rs.LoadSIPInboundTrunk(ctx, inID)
|
||||
require.NoError(t, err)
|
||||
require.True(t, proto.Equal(inT, inT2))
|
||||
|
||||
outT2, err := rs.LoadSIPOutboundTrunk(ctx, outID)
|
||||
require.NoError(t, err)
|
||||
require.True(t, proto.Equal(outT, outT2))
|
||||
|
||||
// Loading (compat)
|
||||
oldT2, err = rs.LoadSIPTrunk(ctx, inID)
|
||||
require.NoError(t, err)
|
||||
require.True(t, proto.Equal(inT.AsTrunkInfo(), oldT2))
|
||||
|
||||
oldT2, err = rs.LoadSIPTrunk(ctx, outID)
|
||||
require.NoError(t, err)
|
||||
require.True(t, proto.Equal(outT.AsTrunkInfo(), oldT2))
|
||||
|
||||
inT2, err = rs.LoadSIPInboundTrunk(ctx, oldID)
|
||||
require.NoError(t, err)
|
||||
require.True(t, proto.Equal(oldT.AsInbound(), inT2))
|
||||
|
||||
outT2, err = rs.LoadSIPOutboundTrunk(ctx, oldID)
|
||||
require.NoError(t, err)
|
||||
require.True(t, proto.Equal(oldT.AsOutbound(), outT2))
|
||||
|
||||
// Listing (always shows legacy + new)
|
||||
listOld, err := rs.ListSIPTrunk(ctx, &livekit.ListSIPTrunkRequest{})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, listOld.Items, 3)
|
||||
slices.SortFunc(listOld.Items, func(a, b *livekit.SIPTrunkInfo) int {
|
||||
return strings.Compare(a.Name, b.Name)
|
||||
})
|
||||
require.True(t, proto.Equal(inT.AsTrunkInfo(), listOld.Items[0]))
|
||||
require.True(t, proto.Equal(oldT, listOld.Items[1]))
|
||||
require.True(t, proto.Equal(outT.AsTrunkInfo(), listOld.Items[2]))
|
||||
|
||||
listIn, err := rs.ListSIPInboundTrunk(ctx, &livekit.ListSIPInboundTrunkRequest{})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, listIn.Items, 2)
|
||||
slices.SortFunc(listIn.Items, func(a, b *livekit.SIPInboundTrunkInfo) int {
|
||||
return strings.Compare(a.Name, b.Name)
|
||||
})
|
||||
require.True(t, proto.Equal(inT, listIn.Items[0]))
|
||||
require.True(t, proto.Equal(oldT.AsInbound(), listIn.Items[1]))
|
||||
|
||||
listOut, err := rs.ListSIPOutboundTrunk(ctx, &livekit.ListSIPOutboundTrunkRequest{})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, listOut.Items, 2)
|
||||
slices.SortFunc(listOut.Items, func(a, b *livekit.SIPOutboundTrunkInfo) int {
|
||||
return strings.Compare(a.Name, b.Name)
|
||||
})
|
||||
require.True(t, proto.Equal(oldT.AsOutbound(), listOut.Items[0]))
|
||||
require.True(t, proto.Equal(outT, listOut.Items[1]))
|
||||
|
||||
// Deletion. Should not return error if not exists.
|
||||
err = rs.DeleteSIPTrunk(ctx, oldID)
|
||||
require.NoError(t, err)
|
||||
err = rs.DeleteSIPTrunk(ctx, oldID)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Other objects are still there.
|
||||
inT2, err = rs.LoadSIPInboundTrunk(ctx, inID)
|
||||
require.NoError(t, err)
|
||||
require.True(t, proto.Equal(inT, inT2))
|
||||
|
||||
outT2, err = rs.LoadSIPOutboundTrunk(ctx, outID)
|
||||
require.NoError(t, err)
|
||||
require.True(t, proto.Equal(outT, outT2))
|
||||
|
||||
// Delete the rest
|
||||
err = rs.DeleteSIPTrunk(ctx, inID)
|
||||
require.NoError(t, err)
|
||||
err = rs.DeleteSIPTrunk(ctx, outID)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Check everything is deleted.
|
||||
oldList, err = rs.ListSIPTrunk(ctx, &livekit.ListSIPTrunkRequest{})
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, oldList.Items)
|
||||
|
||||
inList, err = rs.ListSIPInboundTrunk(ctx, &livekit.ListSIPInboundTrunkRequest{})
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, inList.Items)
|
||||
|
||||
outList, err = rs.ListSIPOutboundTrunk(ctx, &livekit.ListSIPOutboundTrunkRequest{})
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, outList.Items)
|
||||
|
||||
old, err = rs.LoadSIPTrunk(ctx, oldID)
|
||||
require.Equal(t, service.ErrSIPTrunkNotFound, err)
|
||||
require.Nil(t, old)
|
||||
|
||||
in, err = rs.LoadSIPInboundTrunk(ctx, oldID)
|
||||
require.Equal(t, service.ErrSIPTrunkNotFound, err)
|
||||
require.Nil(t, in)
|
||||
|
||||
out, err = rs.LoadSIPOutboundTrunk(ctx, oldID)
|
||||
require.Equal(t, service.ErrSIPTrunkNotFound, err)
|
||||
require.Nil(t, out)
|
||||
}
|
||||
|
||||
func TestSIPTrunkList(t *testing.T) {
|
||||
s := redisStoreDocker(t)
|
||||
|
||||
testIter(t, func(ctx context.Context, id string) error {
|
||||
if strings.HasSuffix(id, "0") {
|
||||
return s.StoreSIPTrunk(ctx, &livekit.SIPTrunkInfo{
|
||||
SipTrunkId: id,
|
||||
OutboundNumber: id,
|
||||
})
|
||||
}
|
||||
return s.StoreSIPInboundTrunk(ctx, &livekit.SIPInboundTrunkInfo{
|
||||
SipTrunkId: id,
|
||||
Numbers: []string{id},
|
||||
})
|
||||
}, func(ctx context.Context, page *livekit.Pagination, ids []string) iters.PageIter[*livekit.SIPInboundTrunkInfo] {
|
||||
return livekit.ListPageIter(s.ListSIPInboundTrunk, &livekit.ListSIPInboundTrunkRequest{
|
||||
TrunkIds: ids, Page: page,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestSIPRuleList(t *testing.T) {
|
||||
s := redisStoreDocker(t)
|
||||
|
||||
testIter(t, func(ctx context.Context, id string) error {
|
||||
return s.StoreSIPDispatchRule(ctx, &livekit.SIPDispatchRuleInfo{
|
||||
SipDispatchRuleId: id,
|
||||
TrunkIds: []string{id},
|
||||
})
|
||||
}, func(ctx context.Context, page *livekit.Pagination, ids []string) iters.PageIter[*livekit.SIPDispatchRuleInfo] {
|
||||
return livekit.ListPageIter(s.ListSIPDispatchRule, &livekit.ListSIPDispatchRuleRequest{
|
||||
DispatchRuleIds: ids, Page: page,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
type listItem interface {
|
||||
ID() string
|
||||
}
|
||||
|
||||
func allIDs[T listItem](t testing.TB, it iters.PageIter[T]) []string {
|
||||
defer it.Close()
|
||||
got, err := iters.AllPages(context.Background(), iters.MapPage(it, func(ctx context.Context, v T) (string, error) {
|
||||
return v.ID(), nil
|
||||
}))
|
||||
require.NoError(t, err)
|
||||
return got
|
||||
}
|
||||
|
||||
func testIter[T listItem](
|
||||
t *testing.T,
|
||||
create func(ctx context.Context, id string) error,
|
||||
list func(ctx context.Context, page *livekit.Pagination, ids []string) iters.PageIter[T],
|
||||
) {
|
||||
ctx := context.Background()
|
||||
var all []string
|
||||
for i := 0; i < 250; i++ {
|
||||
id := fmt.Sprintf("%05d", i)
|
||||
all = append(all, id)
|
||||
err := create(ctx, id)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// List everything with pagination disabled (legacy)
|
||||
it := list(ctx, nil, nil)
|
||||
got := allIDs(t, it)
|
||||
require.Equal(t, all, got)
|
||||
|
||||
// List with pagination enabled
|
||||
it = list(ctx, &livekit.Pagination{Limit: 10}, nil)
|
||||
got = allIDs(t, it)
|
||||
require.Equal(t, all, got)
|
||||
|
||||
// List with pagination enabled, custom ID
|
||||
it = list(ctx, &livekit.Pagination{Limit: 10, AfterId: all[55]}, nil)
|
||||
got = allIDs(t, it)
|
||||
require.Equal(t, all[56:], got)
|
||||
|
||||
// List fixed IDs
|
||||
it = list(ctx, &livekit.Pagination{Limit: 10, AfterId: all[5]}, []string{
|
||||
all[10],
|
||||
all[3],
|
||||
"invalid",
|
||||
all[8],
|
||||
})
|
||||
got = allIDs(t, it)
|
||||
require.Equal(t, []string{
|
||||
all[8],
|
||||
all[10],
|
||||
}, got)
|
||||
}
|
||||
@@ -0,0 +1,391 @@
|
||||
// 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 service_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/atomic"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"github.com/livekit/protocol/ingress"
|
||||
"github.com/livekit/protocol/livekit"
|
||||
"github.com/livekit/protocol/utils"
|
||||
"github.com/livekit/protocol/utils/guid"
|
||||
|
||||
"github.com/livekit/livekit-server/pkg/service"
|
||||
)
|
||||
|
||||
func redisStoreDocker(t testing.TB) *service.RedisStore {
|
||||
return service.NewRedisStore(redisClientDocker(t))
|
||||
}
|
||||
|
||||
func redisStore(t testing.TB) *service.RedisStore {
|
||||
return service.NewRedisStore(redisClient(t))
|
||||
}
|
||||
|
||||
func TestRoomInternal(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
rs := redisStore(t)
|
||||
|
||||
room := &livekit.Room{
|
||||
Sid: "123",
|
||||
Name: "test_room",
|
||||
}
|
||||
internal := &livekit.RoomInternal{
|
||||
TrackEgress: &livekit.AutoTrackEgress{Filepath: "egress"},
|
||||
}
|
||||
|
||||
require.NoError(t, rs.StoreRoom(ctx, room, internal))
|
||||
actualRoom, actualInternal, err := rs.LoadRoom(ctx, livekit.RoomName(room.Name), true)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, room.Sid, actualRoom.Sid)
|
||||
require.Equal(t, internal.TrackEgress.Filepath, actualInternal.TrackEgress.Filepath)
|
||||
|
||||
// remove internal
|
||||
require.NoError(t, rs.StoreRoom(ctx, room, nil))
|
||||
_, actualInternal, err = rs.LoadRoom(ctx, livekit.RoomName(room.Name), true)
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, actualInternal)
|
||||
|
||||
// clean up
|
||||
require.NoError(t, rs.DeleteRoom(ctx, "test_room"))
|
||||
}
|
||||
|
||||
func TestParticipantPersistence(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
rs := redisStore(t)
|
||||
|
||||
roomName := livekit.RoomName("room1")
|
||||
_ = rs.DeleteRoom(ctx, roomName)
|
||||
|
||||
p := &livekit.ParticipantInfo{
|
||||
Sid: "PA_test",
|
||||
Identity: "test",
|
||||
State: livekit.ParticipantInfo_ACTIVE,
|
||||
Tracks: []*livekit.TrackInfo{
|
||||
{
|
||||
Sid: "track1",
|
||||
Type: livekit.TrackType_AUDIO,
|
||||
Name: "audio",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// create the participant
|
||||
require.NoError(t, rs.StoreParticipant(ctx, roomName, p))
|
||||
|
||||
// result should match
|
||||
pGet, err := rs.LoadParticipant(ctx, roomName, livekit.ParticipantIdentity(p.Identity))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, p.Identity, pGet.Identity)
|
||||
require.Equal(t, len(p.Tracks), len(pGet.Tracks))
|
||||
require.Equal(t, p.Tracks[0].Sid, pGet.Tracks[0].Sid)
|
||||
|
||||
// list should return one participant
|
||||
participants, err := rs.ListParticipants(ctx, roomName)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, participants, 1)
|
||||
|
||||
// deleting participant should return to normal
|
||||
require.NoError(t, rs.DeleteParticipant(ctx, roomName, livekit.ParticipantIdentity(p.Identity)))
|
||||
|
||||
participants, err = rs.ListParticipants(ctx, roomName)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, participants, 0)
|
||||
|
||||
// shouldn't be able to get it
|
||||
_, err = rs.LoadParticipant(ctx, roomName, livekit.ParticipantIdentity(p.Identity))
|
||||
require.Equal(t, err, service.ErrParticipantNotFound)
|
||||
}
|
||||
|
||||
func TestRoomLock(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
rs := redisStore(t)
|
||||
lockInterval := 5 * time.Millisecond
|
||||
roomName := livekit.RoomName("myroom")
|
||||
|
||||
t.Run("normal locking", func(t *testing.T) {
|
||||
token, err := rs.LockRoom(ctx, roomName, lockInterval)
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, token)
|
||||
require.NoError(t, rs.UnlockRoom(ctx, roomName, token))
|
||||
})
|
||||
|
||||
t.Run("waits before acquiring lock", func(t *testing.T) {
|
||||
token, err := rs.LockRoom(ctx, roomName, lockInterval)
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, token)
|
||||
unlocked := atomic.NewUint32(0)
|
||||
wg := sync.WaitGroup{}
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
// attempt to lock again
|
||||
defer wg.Done()
|
||||
token2, err := rs.LockRoom(ctx, roomName, lockInterval)
|
||||
require.NoError(t, err)
|
||||
defer rs.UnlockRoom(ctx, roomName, token2)
|
||||
require.Equal(t, uint32(1), unlocked.Load())
|
||||
}()
|
||||
|
||||
// release after 2 ms
|
||||
time.Sleep(2 * time.Millisecond)
|
||||
unlocked.Store(1)
|
||||
_ = rs.UnlockRoom(ctx, roomName, token)
|
||||
|
||||
wg.Wait()
|
||||
})
|
||||
|
||||
t.Run("lock expires", func(t *testing.T) {
|
||||
token, err := rs.LockRoom(ctx, roomName, lockInterval)
|
||||
require.NoError(t, err)
|
||||
defer rs.UnlockRoom(ctx, roomName, token)
|
||||
|
||||
time.Sleep(lockInterval + time.Millisecond)
|
||||
token2, err := rs.LockRoom(ctx, roomName, lockInterval)
|
||||
require.NoError(t, err)
|
||||
_ = rs.UnlockRoom(ctx, roomName, token2)
|
||||
})
|
||||
}
|
||||
|
||||
func TestEgressStore(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
rs := redisStore(t)
|
||||
|
||||
roomName := "egress-test"
|
||||
|
||||
// store egress info
|
||||
info := &livekit.EgressInfo{
|
||||
EgressId: guid.New(utils.EgressPrefix),
|
||||
RoomId: guid.New(utils.RoomPrefix),
|
||||
RoomName: roomName,
|
||||
Status: livekit.EgressStatus_EGRESS_STARTING,
|
||||
Request: &livekit.EgressInfo_RoomComposite{
|
||||
RoomComposite: &livekit.RoomCompositeEgressRequest{
|
||||
RoomName: roomName,
|
||||
Layout: "speaker-dark",
|
||||
},
|
||||
},
|
||||
}
|
||||
require.NoError(t, rs.StoreEgress(ctx, info))
|
||||
|
||||
// load
|
||||
res, err := rs.LoadEgress(ctx, info.EgressId)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, res.EgressId, info.EgressId)
|
||||
|
||||
// store another
|
||||
info2 := &livekit.EgressInfo{
|
||||
EgressId: guid.New(utils.EgressPrefix),
|
||||
RoomId: guid.New(utils.RoomPrefix),
|
||||
RoomName: "another-egress-test",
|
||||
Status: livekit.EgressStatus_EGRESS_STARTING,
|
||||
Request: &livekit.EgressInfo_RoomComposite{
|
||||
RoomComposite: &livekit.RoomCompositeEgressRequest{
|
||||
RoomName: "another-egress-test",
|
||||
Layout: "speaker-dark",
|
||||
},
|
||||
},
|
||||
}
|
||||
require.NoError(t, rs.StoreEgress(ctx, info2))
|
||||
|
||||
// update
|
||||
info2.Status = livekit.EgressStatus_EGRESS_COMPLETE
|
||||
info2.EndedAt = time.Now().Add(-24 * time.Hour).UnixNano()
|
||||
require.NoError(t, rs.UpdateEgress(ctx, info))
|
||||
|
||||
// list
|
||||
list, err := rs.ListEgress(ctx, "", false)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, list, 2)
|
||||
|
||||
// list by room
|
||||
list, err = rs.ListEgress(ctx, livekit.RoomName(roomName), false)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, list, 1)
|
||||
|
||||
// update
|
||||
info.Status = livekit.EgressStatus_EGRESS_COMPLETE
|
||||
info.EndedAt = time.Now().Add(-24 * time.Hour).UnixNano()
|
||||
require.NoError(t, rs.UpdateEgress(ctx, info))
|
||||
|
||||
// clean
|
||||
require.NoError(t, rs.CleanEndedEgress())
|
||||
|
||||
// list
|
||||
list, err = rs.ListEgress(ctx, livekit.RoomName(roomName), false)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, list, 0)
|
||||
}
|
||||
|
||||
func TestIngressStore(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
rs := redisStore(t)
|
||||
|
||||
info := &livekit.IngressInfo{
|
||||
IngressId: "ingressId",
|
||||
StreamKey: "streamKey",
|
||||
State: &livekit.IngressState{
|
||||
StartedAt: 2,
|
||||
},
|
||||
}
|
||||
|
||||
err := rs.StoreIngress(ctx, info)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = rs.UpdateIngressState(ctx, info.IngressId, info.State)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Cleanup(func() {
|
||||
rs.DeleteIngress(ctx, info)
|
||||
})
|
||||
|
||||
pulledInfo, err := rs.LoadIngress(ctx, "ingressId")
|
||||
require.NoError(t, err)
|
||||
compareIngressInfo(t, pulledInfo, info)
|
||||
|
||||
infos, err := rs.ListIngress(ctx, "room")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 0, len(infos))
|
||||
|
||||
info.RoomName = "room"
|
||||
err = rs.UpdateIngress(ctx, info)
|
||||
require.NoError(t, err)
|
||||
|
||||
infos, err = rs.ListIngress(ctx, "room")
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(infos))
|
||||
compareIngressInfo(t, infos[0], info)
|
||||
|
||||
info.RoomName = ""
|
||||
err = rs.UpdateIngress(ctx, info)
|
||||
require.NoError(t, err)
|
||||
|
||||
infos, err = rs.ListIngress(ctx, "room")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 0, len(infos))
|
||||
|
||||
info.State.StartedAt = 1
|
||||
err = rs.UpdateIngressState(ctx, info.IngressId, info.State)
|
||||
require.Equal(t, ingress.ErrIngressOutOfDate, err)
|
||||
|
||||
info.State.StartedAt = 3
|
||||
err = rs.UpdateIngressState(ctx, info.IngressId, info.State)
|
||||
require.NoError(t, err)
|
||||
|
||||
infos, err = rs.ListIngress(ctx, "")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(infos))
|
||||
require.Equal(t, "", infos[0].RoomName)
|
||||
}
|
||||
|
||||
func TestAgentStore(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
rs := redisStore(t)
|
||||
|
||||
ad := &livekit.AgentDispatch{
|
||||
Id: "dispatch_id",
|
||||
AgentName: "agent_name",
|
||||
Metadata: "metadata",
|
||||
Room: "room_name",
|
||||
State: &livekit.AgentDispatchState{
|
||||
CreatedAt: 1,
|
||||
DeletedAt: 2,
|
||||
Jobs: []*livekit.Job{
|
||||
&livekit.Job{
|
||||
Id: "job_id",
|
||||
DispatchId: "dispatch_id",
|
||||
Type: livekit.JobType_JT_PUBLISHER,
|
||||
Room: &livekit.Room{
|
||||
Name: "room_name",
|
||||
},
|
||||
Participant: &livekit.ParticipantInfo{
|
||||
Identity: "identity",
|
||||
Name: "name",
|
||||
},
|
||||
Namespace: "ns",
|
||||
Metadata: "metadata",
|
||||
AgentName: "agent_name",
|
||||
State: &livekit.JobState{
|
||||
Status: livekit.JobStatus_JS_RUNNING,
|
||||
StartedAt: 3,
|
||||
EndedAt: 4,
|
||||
Error: "error",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
err := rs.StoreAgentDispatch(ctx, ad)
|
||||
require.NoError(t, err)
|
||||
|
||||
rd, err := rs.ListAgentDispatches(ctx, "not_a_room")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 0, len(rd))
|
||||
|
||||
rd, err = rs.ListAgentDispatches(ctx, "room_name")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(rd))
|
||||
|
||||
expected := utils.CloneProto(ad)
|
||||
expected.State.Jobs = nil
|
||||
require.True(t, proto.Equal(expected, rd[0]))
|
||||
|
||||
err = rs.StoreAgentJob(ctx, ad.State.Jobs[0])
|
||||
require.NoError(t, err)
|
||||
|
||||
rd, err = rs.ListAgentDispatches(ctx, "room_name")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(rd))
|
||||
|
||||
expected = utils.CloneProto(ad)
|
||||
expected.State.Jobs[0].Room = nil
|
||||
expected.State.Jobs[0].Participant = &livekit.ParticipantInfo{
|
||||
Identity: "identity",
|
||||
}
|
||||
require.True(t, proto.Equal(expected, rd[0]))
|
||||
|
||||
err = rs.DeleteAgentJob(ctx, ad.State.Jobs[0])
|
||||
require.NoError(t, err)
|
||||
|
||||
rd, err = rs.ListAgentDispatches(ctx, "room_name")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(rd))
|
||||
|
||||
expected = utils.CloneProto(ad)
|
||||
expected.State.Jobs = nil
|
||||
require.True(t, proto.Equal(expected, rd[0]))
|
||||
|
||||
err = rs.DeleteAgentDispatch(ctx, ad)
|
||||
require.NoError(t, err)
|
||||
|
||||
rd, err = rs.ListAgentDispatches(ctx, "room_name")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 0, len(rd))
|
||||
}
|
||||
|
||||
func compareIngressInfo(t *testing.T, expected, v *livekit.IngressInfo) {
|
||||
require.Equal(t, expected.IngressId, v.IngressId)
|
||||
require.Equal(t, expected.StreamKey, v.StreamKey)
|
||||
require.Equal(t, expected.RoomName, v.RoomName)
|
||||
}
|
||||
@@ -0,0 +1,246 @@
|
||||
// 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 service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"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/psrpc"
|
||||
|
||||
"github.com/livekit/livekit-server/pkg/config"
|
||||
"github.com/livekit/livekit-server/pkg/routing"
|
||||
"github.com/livekit/livekit-server/pkg/routing/selector"
|
||||
)
|
||||
|
||||
type StandardRoomAllocator struct {
|
||||
config *config.Config
|
||||
router routing.Router
|
||||
selector selector.NodeSelector
|
||||
roomStore ObjectStore
|
||||
}
|
||||
|
||||
func NewRoomAllocator(conf *config.Config, router routing.Router, rs ObjectStore) (RoomAllocator, error) {
|
||||
ns, err := selector.CreateNodeSelector(conf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &StandardRoomAllocator{
|
||||
config: conf,
|
||||
router: router,
|
||||
selector: ns,
|
||||
roomStore: rs,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *StandardRoomAllocator) AutoCreateEnabled(context.Context) bool {
|
||||
return r.config.Room.AutoCreate
|
||||
}
|
||||
|
||||
// CreateRoom creates a new room from a request and allocates it to a node to handle
|
||||
// it'll also monitor its state, and cleans it up when appropriate
|
||||
func (r *StandardRoomAllocator) CreateRoom(ctx context.Context, req *livekit.CreateRoomRequest, isExplicit bool) (*livekit.Room, *livekit.RoomInternal, bool, error) {
|
||||
token, err := r.roomStore.LockRoom(ctx, livekit.RoomName(req.Name), 5*time.Second)
|
||||
if err != nil {
|
||||
return nil, nil, false, err
|
||||
}
|
||||
defer func() {
|
||||
_ = r.roomStore.UnlockRoom(ctx, livekit.RoomName(req.Name), token)
|
||||
}()
|
||||
|
||||
// find existing room and update it
|
||||
var created bool
|
||||
rm, internal, err := r.roomStore.LoadRoom(ctx, livekit.RoomName(req.Name), true)
|
||||
if errors.Is(err, ErrRoomNotFound) {
|
||||
created = true
|
||||
now := time.Now()
|
||||
rm = &livekit.Room{
|
||||
Sid: guid.New(utils.RoomPrefix),
|
||||
Name: req.Name,
|
||||
CreationTime: now.Unix(),
|
||||
CreationTimeMs: now.UnixMilli(),
|
||||
TurnPassword: utils.RandomSecret(),
|
||||
}
|
||||
internal = &livekit.RoomInternal{}
|
||||
applyDefaultRoomConfig(rm, internal, &r.config.Room)
|
||||
} else if err != nil {
|
||||
return nil, nil, false, err
|
||||
}
|
||||
|
||||
req, err = r.applyNamedRoomConfiguration(req)
|
||||
if err != nil {
|
||||
return nil, nil, false, err
|
||||
}
|
||||
|
||||
if req.EmptyTimeout > 0 {
|
||||
rm.EmptyTimeout = req.EmptyTimeout
|
||||
}
|
||||
if req.DepartureTimeout > 0 {
|
||||
rm.DepartureTimeout = req.DepartureTimeout
|
||||
}
|
||||
if req.MaxParticipants > 0 {
|
||||
rm.MaxParticipants = req.MaxParticipants
|
||||
}
|
||||
if req.Metadata != "" {
|
||||
rm.Metadata = req.Metadata
|
||||
}
|
||||
if req.Egress != nil {
|
||||
if req.Egress.Participant != nil {
|
||||
internal.ParticipantEgress = req.Egress.Participant
|
||||
}
|
||||
if req.Egress.Tracks != nil {
|
||||
internal.TrackEgress = req.Egress.Tracks
|
||||
}
|
||||
}
|
||||
if req.Agents != nil {
|
||||
internal.AgentDispatches = req.Agents
|
||||
}
|
||||
if req.MinPlayoutDelay > 0 || req.MaxPlayoutDelay > 0 {
|
||||
internal.PlayoutDelay = &livekit.PlayoutDelay{
|
||||
Enabled: true,
|
||||
Min: req.MinPlayoutDelay,
|
||||
Max: req.MaxPlayoutDelay,
|
||||
}
|
||||
}
|
||||
if req.SyncStreams {
|
||||
internal.SyncStreams = true
|
||||
}
|
||||
|
||||
if err = r.roomStore.StoreRoom(ctx, rm, internal); err != nil {
|
||||
return nil, nil, false, err
|
||||
}
|
||||
|
||||
return rm, internal, created, nil
|
||||
}
|
||||
|
||||
func (r *StandardRoomAllocator) SelectRoomNode(ctx context.Context, roomName livekit.RoomName, nodeID livekit.NodeID) error {
|
||||
// check if room already assigned
|
||||
existing, err := r.router.GetNodeForRoom(ctx, roomName)
|
||||
if !errors.Is(err, routing.ErrNotFound) && err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// if already assigned and still available, keep it on that node
|
||||
if err == nil && selector.IsAvailable(existing) {
|
||||
// if node hosting the room is full, deny entry
|
||||
if selector.LimitsReached(r.config.Limit, existing.Stats) {
|
||||
return routing.ErrNodeLimitReached
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// select a new node
|
||||
if nodeID == "" {
|
||||
nodes, err := r.router.ListNodes()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
node, err := r.selector.SelectNode(nodes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
nodeID = livekit.NodeID(node.Id)
|
||||
}
|
||||
|
||||
logger.Infow("selected node for room", "room", roomName, "selectedNodeID", nodeID)
|
||||
err = r.router.SetNodeForRoom(ctx, roomName, nodeID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *StandardRoomAllocator) ValidateCreateRoom(ctx context.Context, roomName livekit.RoomName) error {
|
||||
// when auto create is disabled, we'll check to ensure it's already created
|
||||
if !r.config.Room.AutoCreate {
|
||||
_, _, err := r.roomStore.LoadRoom(ctx, roomName, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func applyDefaultRoomConfig(room *livekit.Room, internal *livekit.RoomInternal, conf *config.RoomConfig) {
|
||||
room.EmptyTimeout = conf.EmptyTimeout
|
||||
room.DepartureTimeout = conf.DepartureTimeout
|
||||
room.MaxParticipants = conf.MaxParticipants
|
||||
for _, codec := range conf.EnabledCodecs {
|
||||
room.EnabledCodecs = append(room.EnabledCodecs, &livekit.Codec{
|
||||
Mime: codec.Mime,
|
||||
FmtpLine: codec.FmtpLine,
|
||||
})
|
||||
}
|
||||
internal.PlayoutDelay = &livekit.PlayoutDelay{
|
||||
Enabled: conf.PlayoutDelay.Enabled,
|
||||
Min: uint32(conf.PlayoutDelay.Min),
|
||||
Max: uint32(conf.PlayoutDelay.Max),
|
||||
}
|
||||
internal.SyncStreams = conf.SyncStreams
|
||||
}
|
||||
|
||||
func (r *StandardRoomAllocator) applyNamedRoomConfiguration(req *livekit.CreateRoomRequest) (*livekit.CreateRoomRequest, error) {
|
||||
if req.RoomPreset == "" {
|
||||
return req, nil
|
||||
}
|
||||
|
||||
conf, ok := r.config.Room.RoomConfigurations[req.RoomPreset]
|
||||
if !ok {
|
||||
return req, psrpc.NewErrorf(psrpc.InvalidArgument, "unknown room confguration in create room request")
|
||||
}
|
||||
|
||||
clone := utils.CloneProto(req)
|
||||
|
||||
// Request overwrites conf
|
||||
if clone.EmptyTimeout == 0 {
|
||||
clone.EmptyTimeout = conf.EmptyTimeout
|
||||
}
|
||||
if clone.DepartureTimeout == 0 {
|
||||
clone.DepartureTimeout = req.DepartureTimeout
|
||||
}
|
||||
if clone.MaxParticipants == 0 {
|
||||
clone.MaxParticipants = conf.MaxParticipants
|
||||
}
|
||||
if clone.Egress == nil {
|
||||
clone.Egress = utils.CloneProto(conf.Egress)
|
||||
}
|
||||
if clone.Agents == nil {
|
||||
clone.Agents = make([]*livekit.RoomAgentDispatch, 0, len(conf.Agents))
|
||||
for _, agent := range conf.Agents {
|
||||
clone.Agents = append(clone.Agents, utils.CloneProto(agent))
|
||||
}
|
||||
}
|
||||
if clone.MinPlayoutDelay == 0 {
|
||||
clone.MinPlayoutDelay = conf.MinPlayoutDelay
|
||||
}
|
||||
if clone.MaxPlayoutDelay == 0 {
|
||||
clone.MaxPlayoutDelay = conf.MaxPlayoutDelay
|
||||
}
|
||||
if !clone.SyncStreams {
|
||||
clone.SyncStreams = conf.SyncStreams
|
||||
}
|
||||
|
||||
return clone, nil
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
// 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 service_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/livekit/protocol/livekit"
|
||||
|
||||
"github.com/livekit/livekit-server/pkg/config"
|
||||
"github.com/livekit/livekit-server/pkg/routing"
|
||||
"github.com/livekit/livekit-server/pkg/routing/routingfakes"
|
||||
"github.com/livekit/livekit-server/pkg/service"
|
||||
"github.com/livekit/livekit-server/pkg/service/servicefakes"
|
||||
)
|
||||
|
||||
func TestCreateRoom(t *testing.T) {
|
||||
t.Run("ensure default room settings are applied", func(t *testing.T) {
|
||||
conf, err := config.NewConfig("", true, nil, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
node, err := routing.NewLocalNode(conf)
|
||||
require.NoError(t, err)
|
||||
|
||||
ra, conf := newTestRoomAllocator(t, conf, node.Clone())
|
||||
|
||||
room, _, _, err := ra.CreateRoom(context.Background(), &livekit.CreateRoomRequest{Name: "myroom"}, true)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, conf.Room.EmptyTimeout, room.EmptyTimeout)
|
||||
require.Equal(t, conf.Room.DepartureTimeout, room.DepartureTimeout)
|
||||
require.NotEmpty(t, room.EnabledCodecs)
|
||||
})
|
||||
}
|
||||
|
||||
func SelectRoomNode(t *testing.T) {
|
||||
t.Run("reject new participants when track limit has been reached", func(t *testing.T) {
|
||||
conf, err := config.NewConfig("", true, nil, nil)
|
||||
require.NoError(t, err)
|
||||
conf.Limit.NumTracks = 10
|
||||
|
||||
node, err := routing.NewLocalNode(conf)
|
||||
require.NoError(t, err)
|
||||
node.SetStats(&livekit.NodeStats{
|
||||
NumTracksIn: 100,
|
||||
NumTracksOut: 100,
|
||||
})
|
||||
|
||||
ra, _ := newTestRoomAllocator(t, conf, node.Clone())
|
||||
|
||||
err = ra.SelectRoomNode(context.Background(), "low-limit-room", "")
|
||||
require.ErrorIs(t, err, routing.ErrNodeLimitReached)
|
||||
})
|
||||
|
||||
t.Run("reject new participants when bandwidth limit has been reached", func(t *testing.T) {
|
||||
conf, err := config.NewConfig("", true, nil, nil)
|
||||
require.NoError(t, err)
|
||||
conf.Limit.BytesPerSec = 100
|
||||
|
||||
node, err := routing.NewLocalNode(conf)
|
||||
require.NoError(t, err)
|
||||
node.SetStats(&livekit.NodeStats{
|
||||
BytesInPerSec: 1000,
|
||||
BytesOutPerSec: 1000,
|
||||
})
|
||||
|
||||
ra, _ := newTestRoomAllocator(t, conf, node.Clone())
|
||||
|
||||
err = ra.SelectRoomNode(context.Background(), "low-limit-room", "")
|
||||
require.ErrorIs(t, err, routing.ErrNodeLimitReached)
|
||||
})
|
||||
}
|
||||
|
||||
func newTestRoomAllocator(t *testing.T, conf *config.Config, node *livekit.Node) (service.RoomAllocator, *config.Config) {
|
||||
store := &servicefakes.FakeObjectStore{}
|
||||
store.LoadRoomReturns(nil, nil, service.ErrRoomNotFound)
|
||||
router := &routingfakes.FakeRouter{}
|
||||
|
||||
router.GetNodeForRoomReturns(node, nil)
|
||||
|
||||
ra, err := service.NewRoomAllocator(conf, router, store)
|
||||
require.NoError(t, err)
|
||||
return ra, conf
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,392 @@
|
||||
// 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 service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/twitchtv/twirp"
|
||||
|
||||
"github.com/livekit/livekit-server/pkg/config"
|
||||
"github.com/livekit/livekit-server/pkg/routing"
|
||||
"github.com/livekit/livekit-server/pkg/rtc"
|
||||
"github.com/livekit/protocol/egress"
|
||||
"github.com/livekit/protocol/livekit"
|
||||
"github.com/livekit/protocol/logger"
|
||||
"github.com/livekit/protocol/rpc"
|
||||
"github.com/livekit/protocol/utils"
|
||||
)
|
||||
|
||||
type RoomService struct {
|
||||
limitConf config.LimitConfig
|
||||
apiConf config.APIConfig
|
||||
router routing.MessageRouter
|
||||
roomAllocator RoomAllocator
|
||||
roomStore ServiceStore
|
||||
egressLauncher rtc.EgressLauncher
|
||||
topicFormatter rpc.TopicFormatter
|
||||
roomClient rpc.TypedRoomClient
|
||||
participantClient rpc.TypedParticipantClient
|
||||
}
|
||||
|
||||
func NewRoomService(
|
||||
limitConf config.LimitConfig,
|
||||
apiConf config.APIConfig,
|
||||
router routing.MessageRouter,
|
||||
roomAllocator RoomAllocator,
|
||||
serviceStore ServiceStore,
|
||||
egressLauncher rtc.EgressLauncher,
|
||||
topicFormatter rpc.TopicFormatter,
|
||||
roomClient rpc.TypedRoomClient,
|
||||
participantClient rpc.TypedParticipantClient,
|
||||
) (svc *RoomService, err error) {
|
||||
svc = &RoomService{
|
||||
limitConf: limitConf,
|
||||
apiConf: apiConf,
|
||||
router: router,
|
||||
roomAllocator: roomAllocator,
|
||||
roomStore: serviceStore,
|
||||
egressLauncher: egressLauncher,
|
||||
topicFormatter: topicFormatter,
|
||||
roomClient: roomClient,
|
||||
participantClient: participantClient,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *RoomService) CreateRoom(ctx context.Context, req *livekit.CreateRoomRequest) (*livekit.Room, error) {
|
||||
redactedReq := redactCreateRoomRequest(req)
|
||||
RecordRequest(ctx, redactedReq)
|
||||
|
||||
AppendLogFields(ctx, "room", req.Name, "request", logger.Proto(redactedReq))
|
||||
if err := EnsureCreatePermission(ctx); err != nil {
|
||||
return nil, twirpAuthError(err)
|
||||
} else if req.Egress != nil && s.egressLauncher == nil {
|
||||
return nil, ErrEgressNotConnected
|
||||
}
|
||||
|
||||
if !s.limitConf.CheckRoomNameLength(req.Name) {
|
||||
return nil, fmt.Errorf("%w: max length %d", ErrRoomNameExceedsLimits, s.limitConf.MaxRoomNameLength)
|
||||
}
|
||||
|
||||
err := s.roomAllocator.SelectRoomNode(ctx, livekit.RoomName(req.Name), livekit.NodeID(req.NodeId))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
room, err := s.router.CreateRoom(ctx, req)
|
||||
RecordResponse(ctx, room)
|
||||
return room, err
|
||||
}
|
||||
|
||||
func (s *RoomService) ListRooms(ctx context.Context, req *livekit.ListRoomsRequest) (*livekit.ListRoomsResponse, error) {
|
||||
RecordRequest(ctx, req)
|
||||
|
||||
AppendLogFields(ctx, "room", req.Names)
|
||||
err := EnsureListPermission(ctx)
|
||||
if err != nil {
|
||||
return nil, twirpAuthError(err)
|
||||
}
|
||||
|
||||
var names []livekit.RoomName
|
||||
if len(req.Names) > 0 {
|
||||
names = livekit.StringsAsIDs[livekit.RoomName](req.Names)
|
||||
}
|
||||
rooms, err := s.roomStore.ListRooms(ctx, names)
|
||||
if err != nil {
|
||||
// TODO: translate error codes to Twirp
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res := &livekit.ListRoomsResponse{
|
||||
Rooms: rooms,
|
||||
}
|
||||
RecordResponse(ctx, res)
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *RoomService) DeleteRoom(ctx context.Context, req *livekit.DeleteRoomRequest) (*livekit.DeleteRoomResponse, error) {
|
||||
RecordRequest(ctx, req)
|
||||
|
||||
AppendLogFields(ctx, "room", req.Room)
|
||||
if err := EnsureCreatePermission(ctx); err != nil {
|
||||
return nil, twirpAuthError(err)
|
||||
}
|
||||
|
||||
_, _, err := s.roomStore.LoadRoom(ctx, livekit.RoomName(req.Room), false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// ensure at least one node is available to handle the request
|
||||
_, err = s.router.CreateRoom(ctx, &livekit.CreateRoomRequest{Name: req.Room})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = s.roomClient.DeleteRoom(ctx, s.topicFormatter.RoomTopic(ctx, livekit.RoomName(req.Room)), req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = s.roomStore.DeleteRoom(ctx, livekit.RoomName(req.Room))
|
||||
res := &livekit.DeleteRoomResponse{}
|
||||
RecordResponse(ctx, res)
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (s *RoomService) ListParticipants(ctx context.Context, req *livekit.ListParticipantsRequest) (*livekit.ListParticipantsResponse, error) {
|
||||
RecordRequest(ctx, req)
|
||||
|
||||
AppendLogFields(ctx, "room", req.Room)
|
||||
if err := EnsureAdminPermission(ctx, livekit.RoomName(req.Room)); err != nil {
|
||||
return nil, twirpAuthError(err)
|
||||
}
|
||||
|
||||
participants, err := s.roomStore.ListParticipants(ctx, livekit.RoomName(req.Room))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res := &livekit.ListParticipantsResponse{
|
||||
Participants: participants,
|
||||
}
|
||||
RecordResponse(ctx, res)
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *RoomService) GetParticipant(ctx context.Context, req *livekit.RoomParticipantIdentity) (*livekit.ParticipantInfo, error) {
|
||||
RecordRequest(ctx, req)
|
||||
|
||||
AppendLogFields(ctx, "room", req.Room, "participant", req.Identity)
|
||||
if err := EnsureAdminPermission(ctx, livekit.RoomName(req.Room)); err != nil {
|
||||
return nil, twirpAuthError(err)
|
||||
}
|
||||
|
||||
participant, err := s.roomStore.LoadParticipant(ctx, livekit.RoomName(req.Room), livekit.ParticipantIdentity(req.Identity))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
RecordResponse(ctx, participant)
|
||||
return participant, nil
|
||||
}
|
||||
|
||||
func (s *RoomService) RemoveParticipant(ctx context.Context, req *livekit.RoomParticipantIdentity) (*livekit.RemoveParticipantResponse, error) {
|
||||
RecordRequest(ctx, req)
|
||||
|
||||
AppendLogFields(ctx, "room", req.Room, "participant", req.Identity)
|
||||
|
||||
if err := EnsureAdminPermission(ctx, livekit.RoomName(req.Room)); err != nil {
|
||||
return nil, twirpAuthError(err)
|
||||
}
|
||||
|
||||
if _, err := s.roomStore.LoadParticipant(ctx, livekit.RoomName(req.Room), livekit.ParticipantIdentity(req.Identity)); err == ErrParticipantNotFound {
|
||||
return nil, twirp.NotFoundError("participant not found")
|
||||
}
|
||||
|
||||
res, err := s.participantClient.RemoveParticipant(ctx, s.topicFormatter.ParticipantTopic(ctx, livekit.RoomName(req.Room), livekit.ParticipantIdentity(req.Identity)), req)
|
||||
RecordResponse(ctx, res)
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (s *RoomService) MutePublishedTrack(ctx context.Context, req *livekit.MuteRoomTrackRequest) (*livekit.MuteRoomTrackResponse, error) {
|
||||
RecordRequest(ctx, req)
|
||||
|
||||
AppendLogFields(ctx, "room", req.Room, "participant", req.Identity, "trackID", req.TrackSid, "muted", req.Muted)
|
||||
if err := EnsureAdminPermission(ctx, livekit.RoomName(req.Room)); err != nil {
|
||||
return nil, twirpAuthError(err)
|
||||
}
|
||||
|
||||
res, err := s.participantClient.MutePublishedTrack(ctx, s.topicFormatter.ParticipantTopic(ctx, livekit.RoomName(req.Room), livekit.ParticipantIdentity(req.Identity)), req)
|
||||
RecordResponse(ctx, res)
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (s *RoomService) UpdateParticipant(ctx context.Context, req *livekit.UpdateParticipantRequest) (*livekit.ParticipantInfo, error) {
|
||||
RecordRequest(ctx, redactUpdateParticipantRequest(req))
|
||||
|
||||
AppendLogFields(ctx, "room", req.Room, "participant", req.Identity)
|
||||
|
||||
if !s.limitConf.CheckParticipantNameLength(req.Name) {
|
||||
return nil, twirp.InvalidArgumentError(ErrNameExceedsLimits.Error(), strconv.Itoa(s.limitConf.MaxParticipantNameLength))
|
||||
}
|
||||
|
||||
if !s.limitConf.CheckMetadataSize(req.Metadata) {
|
||||
return nil, twirp.InvalidArgumentError(ErrMetadataExceedsLimits.Error(), strconv.Itoa(int(s.limitConf.MaxMetadataSize)))
|
||||
}
|
||||
|
||||
if !s.limitConf.CheckAttributesSize(req.Attributes) {
|
||||
return nil, twirp.InvalidArgumentError(ErrAttributeExceedsLimits.Error(), strconv.Itoa(int(s.limitConf.MaxAttributesSize)))
|
||||
}
|
||||
|
||||
if err := EnsureAdminPermission(ctx, livekit.RoomName(req.Room)); err != nil {
|
||||
return nil, twirpAuthError(err)
|
||||
}
|
||||
|
||||
res, err := s.participantClient.UpdateParticipant(ctx, s.topicFormatter.ParticipantTopic(ctx, livekit.RoomName(req.Room), livekit.ParticipantIdentity(req.Identity)), req)
|
||||
RecordResponse(ctx, res)
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (s *RoomService) UpdateSubscriptions(ctx context.Context, req *livekit.UpdateSubscriptionsRequest) (*livekit.UpdateSubscriptionsResponse, error) {
|
||||
RecordRequest(ctx, req)
|
||||
|
||||
trackSIDs := append(make([]string, 0), req.TrackSids...)
|
||||
for _, pt := range req.ParticipantTracks {
|
||||
trackSIDs = append(trackSIDs, pt.TrackSids...)
|
||||
}
|
||||
AppendLogFields(ctx, "room", req.Room, "participant", req.Identity, "trackID", trackSIDs)
|
||||
|
||||
if err := EnsureAdminPermission(ctx, livekit.RoomName(req.Room)); err != nil {
|
||||
return nil, twirpAuthError(err)
|
||||
}
|
||||
|
||||
res, err := s.participantClient.UpdateSubscriptions(ctx, s.topicFormatter.ParticipantTopic(ctx, livekit.RoomName(req.Room), livekit.ParticipantIdentity(req.Identity)), req)
|
||||
RecordResponse(ctx, res)
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (s *RoomService) SendData(ctx context.Context, req *livekit.SendDataRequest) (*livekit.SendDataResponse, error) {
|
||||
RecordRequest(ctx, redactSendDataRequest(req))
|
||||
|
||||
roomName := livekit.RoomName(req.Room)
|
||||
AppendLogFields(ctx, "room", roomName, "size", len(req.Data))
|
||||
if err := EnsureAdminPermission(ctx, roomName); err != nil {
|
||||
return nil, twirpAuthError(err)
|
||||
}
|
||||
|
||||
// nonce is either absent or 128-bit UUID
|
||||
if len(req.Nonce) != 0 && len(req.Nonce) != 16 {
|
||||
return nil, twirp.NewError(twirp.InvalidArgument, fmt.Sprintf("nonce should be 16-bytes or not present, got: %d bytes", len(req.Nonce)))
|
||||
}
|
||||
|
||||
res, err := s.roomClient.SendData(ctx, s.topicFormatter.RoomTopic(ctx, livekit.RoomName(req.Room)), req)
|
||||
RecordResponse(ctx, res)
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (s *RoomService) UpdateRoomMetadata(ctx context.Context, req *livekit.UpdateRoomMetadataRequest) (*livekit.Room, error) {
|
||||
RecordRequest(ctx, redactUpdateRoomMetadataRequest(req))
|
||||
|
||||
AppendLogFields(ctx, "room", req.Room, "size", len(req.Metadata))
|
||||
maxMetadataSize := int(s.limitConf.MaxMetadataSize)
|
||||
if maxMetadataSize > 0 && len(req.Metadata) > maxMetadataSize {
|
||||
return nil, twirp.InvalidArgumentError(ErrMetadataExceedsLimits.Error(), strconv.Itoa(maxMetadataSize))
|
||||
}
|
||||
|
||||
if err := EnsureAdminPermission(ctx, livekit.RoomName(req.Room)); err != nil {
|
||||
return nil, twirpAuthError(err)
|
||||
}
|
||||
|
||||
_, _, err := s.roomStore.LoadRoom(ctx, livekit.RoomName(req.Room), false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
room, err := s.roomClient.UpdateRoomMetadata(ctx, s.topicFormatter.RoomTopic(ctx, livekit.RoomName(req.Room)), req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
RecordResponse(ctx, room)
|
||||
return room, nil
|
||||
}
|
||||
|
||||
func redactCreateRoomRequest(req *livekit.CreateRoomRequest) *livekit.CreateRoomRequest {
|
||||
if req.Egress == nil && req.Metadata == "" {
|
||||
// nothing to redact
|
||||
return req
|
||||
}
|
||||
|
||||
clone := utils.CloneProto(req)
|
||||
|
||||
if clone.Egress != nil {
|
||||
if clone.Egress.Room != nil {
|
||||
egress.RedactEncodedOutputs(clone.Egress.Room)
|
||||
}
|
||||
if clone.Egress.Participant != nil {
|
||||
egress.RedactAutoEncodedOutput(clone.Egress.Participant)
|
||||
}
|
||||
if clone.Egress.Tracks != nil {
|
||||
egress.RedactUpload(clone.Egress.Tracks)
|
||||
}
|
||||
}
|
||||
|
||||
// replace with size of metadata to provide visibility on request size
|
||||
if clone.Metadata != "" {
|
||||
clone.Metadata = fmt.Sprintf("__size: %d", len(clone.Metadata))
|
||||
}
|
||||
|
||||
return clone
|
||||
}
|
||||
|
||||
func redactUpdateParticipantRequest(req *livekit.UpdateParticipantRequest) *livekit.UpdateParticipantRequest {
|
||||
if req.Metadata == "" && len(req.Attributes) == 0 {
|
||||
return req
|
||||
}
|
||||
|
||||
clone := utils.CloneProto(req)
|
||||
|
||||
// replace with size of metadata/attributes to provide visibility on request size
|
||||
if clone.Metadata != "" {
|
||||
clone.Metadata = fmt.Sprintf("__size: %d", len(clone.Metadata))
|
||||
}
|
||||
|
||||
if len(clone.Attributes) != 0 {
|
||||
keysSize := 0
|
||||
valuesSize := 0
|
||||
for k, v := range clone.Attributes {
|
||||
keysSize += len(k)
|
||||
valuesSize += len(v)
|
||||
}
|
||||
|
||||
clone.Attributes = map[string]string{
|
||||
"__num_elements": fmt.Sprintf("%d", len(clone.Attributes)),
|
||||
"__keys_size": fmt.Sprintf("%d", keysSize),
|
||||
"__values_size": fmt.Sprintf("%d", valuesSize),
|
||||
}
|
||||
}
|
||||
|
||||
return clone
|
||||
}
|
||||
|
||||
func redactSendDataRequest(req *livekit.SendDataRequest) *livekit.SendDataRequest {
|
||||
if len(req.Data) == 0 {
|
||||
return req
|
||||
}
|
||||
|
||||
clone := utils.CloneProto(req)
|
||||
|
||||
// replace with size of data to provide visibility on request size
|
||||
clone.Data = []byte(fmt.Sprintf("__size: %d", len(clone.Data)))
|
||||
|
||||
return clone
|
||||
}
|
||||
|
||||
func redactUpdateRoomMetadataRequest(req *livekit.UpdateRoomMetadataRequest) *livekit.UpdateRoomMetadataRequest {
|
||||
if req.Metadata == "" {
|
||||
return req
|
||||
}
|
||||
|
||||
clone := utils.CloneProto(req)
|
||||
|
||||
// replace with size of metadata to provide visibility on request size
|
||||
clone.Metadata = fmt.Sprintf("__size: %d", len(clone.Metadata))
|
||||
|
||||
return clone
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
// 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 service_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/twitchtv/twirp"
|
||||
|
||||
"github.com/livekit/protocol/auth"
|
||||
"github.com/livekit/protocol/livekit"
|
||||
"github.com/livekit/protocol/rpc"
|
||||
"github.com/livekit/protocol/rpc/rpcfakes"
|
||||
|
||||
"github.com/livekit/livekit-server/pkg/config"
|
||||
"github.com/livekit/livekit-server/pkg/routing/routingfakes"
|
||||
"github.com/livekit/livekit-server/pkg/service"
|
||||
"github.com/livekit/livekit-server/pkg/service/servicefakes"
|
||||
)
|
||||
|
||||
func TestDeleteRoom(t *testing.T) {
|
||||
t.Run("missing permissions", func(t *testing.T) {
|
||||
svc := newTestRoomService(config.LimitConfig{})
|
||||
grant := &auth.ClaimGrants{
|
||||
Video: &auth.VideoGrant{},
|
||||
}
|
||||
ctx := service.WithGrants(context.Background(), grant, "")
|
||||
_, err := svc.DeleteRoom(ctx, &livekit.DeleteRoomRequest{
|
||||
Room: "testroom",
|
||||
})
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestMetaDataLimits(t *testing.T) {
|
||||
t.Run("metadata exceed limits", func(t *testing.T) {
|
||||
svc := newTestRoomService(config.LimitConfig{MaxMetadataSize: 5})
|
||||
grant := &auth.ClaimGrants{
|
||||
Video: &auth.VideoGrant{},
|
||||
}
|
||||
ctx := service.WithGrants(context.Background(), grant, "")
|
||||
_, err := svc.UpdateParticipant(ctx, &livekit.UpdateParticipantRequest{
|
||||
Room: "testroom",
|
||||
Identity: "123",
|
||||
Metadata: "abcdefg",
|
||||
})
|
||||
terr, ok := err.(twirp.Error)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, twirp.InvalidArgument, terr.Code())
|
||||
|
||||
_, err = svc.UpdateRoomMetadata(ctx, &livekit.UpdateRoomMetadataRequest{
|
||||
Room: "testroom",
|
||||
Metadata: "abcdefg",
|
||||
})
|
||||
terr, ok = err.(twirp.Error)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, twirp.InvalidArgument, terr.Code())
|
||||
})
|
||||
|
||||
notExceedsLimitsSvc := map[string]*TestRoomService{
|
||||
"metadata exceeds limits": newTestRoomService(config.LimitConfig{MaxMetadataSize: 5}),
|
||||
"metadata no limits": newTestRoomService(config.LimitConfig{}), // no limits
|
||||
}
|
||||
|
||||
for n, s := range notExceedsLimitsSvc {
|
||||
svc := s
|
||||
t.Run(n, func(t *testing.T) {
|
||||
grant := &auth.ClaimGrants{
|
||||
Video: &auth.VideoGrant{},
|
||||
}
|
||||
ctx := service.WithGrants(context.Background(), grant, "")
|
||||
_, err := svc.UpdateParticipant(ctx, &livekit.UpdateParticipantRequest{
|
||||
Room: "testroom",
|
||||
Identity: "123",
|
||||
Metadata: "abc",
|
||||
})
|
||||
terr, ok := err.(twirp.Error)
|
||||
require.True(t, ok)
|
||||
require.NotEqual(t, twirp.InvalidArgument, terr.Code())
|
||||
|
||||
_, err = svc.UpdateRoomMetadata(ctx, &livekit.UpdateRoomMetadataRequest{
|
||||
Room: "testroom",
|
||||
Metadata: "abc",
|
||||
})
|
||||
terr, ok = err.(twirp.Error)
|
||||
require.True(t, ok)
|
||||
require.NotEqual(t, twirp.InvalidArgument, terr.Code())
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func newTestRoomService(limitConf config.LimitConfig) *TestRoomService {
|
||||
router := &routingfakes.FakeRouter{}
|
||||
allocator := &servicefakes.FakeRoomAllocator{}
|
||||
store := &servicefakes.FakeServiceStore{}
|
||||
svc, err := service.NewRoomService(
|
||||
limitConf,
|
||||
config.APIConfig{ExecutionTimeout: 2},
|
||||
router,
|
||||
allocator,
|
||||
store,
|
||||
nil,
|
||||
rpc.NewTopicFormatter(),
|
||||
&rpcfakes.FakeTypedRoomClient{},
|
||||
&rpcfakes.FakeTypedParticipantClient{},
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return &TestRoomService{
|
||||
RoomService: *svc,
|
||||
router: router,
|
||||
allocator: allocator,
|
||||
store: store,
|
||||
}
|
||||
}
|
||||
|
||||
type TestRoomService struct {
|
||||
service.RoomService
|
||||
router *routingfakes.FakeRouter
|
||||
allocator *servicefakes.FakeRoomAllocator
|
||||
store *servicefakes.FakeServiceStore
|
||||
}
|
||||
@@ -0,0 +1,584 @@
|
||||
// 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 service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/ua-parser/uap-go/uaparser"
|
||||
"go.uber.org/atomic"
|
||||
"golang.org/x/exp/maps"
|
||||
|
||||
"github.com/livekit/livekit-server/pkg/config"
|
||||
"github.com/livekit/livekit-server/pkg/routing"
|
||||
"github.com/livekit/livekit-server/pkg/routing/selector"
|
||||
"github.com/livekit/livekit-server/pkg/rtc"
|
||||
"github.com/livekit/livekit-server/pkg/telemetry"
|
||||
"github.com/livekit/livekit-server/pkg/telemetry/prometheus"
|
||||
"github.com/livekit/livekit-server/pkg/utils"
|
||||
"github.com/livekit/protocol/livekit"
|
||||
"github.com/livekit/psrpc"
|
||||
)
|
||||
|
||||
type RTCService struct {
|
||||
router routing.MessageRouter
|
||||
roomAllocator RoomAllocator
|
||||
store ServiceStore
|
||||
upgrader websocket.Upgrader
|
||||
currentNode routing.LocalNode
|
||||
config *config.Config
|
||||
isDev bool
|
||||
limits config.LimitConfig
|
||||
parser *uaparser.Parser
|
||||
telemetry telemetry.TelemetryService
|
||||
|
||||
mu sync.Mutex
|
||||
connections map[*websocket.Conn]struct{}
|
||||
}
|
||||
|
||||
func NewRTCService(
|
||||
conf *config.Config,
|
||||
ra RoomAllocator,
|
||||
store ServiceStore,
|
||||
router routing.MessageRouter,
|
||||
currentNode routing.LocalNode,
|
||||
telemetry telemetry.TelemetryService,
|
||||
) *RTCService {
|
||||
s := &RTCService{
|
||||
router: router,
|
||||
roomAllocator: ra,
|
||||
store: store,
|
||||
currentNode: currentNode,
|
||||
config: conf,
|
||||
isDev: conf.Development,
|
||||
limits: conf.Limit,
|
||||
parser: uaparser.NewFromSaved(),
|
||||
telemetry: telemetry,
|
||||
connections: map[*websocket.Conn]struct{}{},
|
||||
}
|
||||
|
||||
s.upgrader = websocket.Upgrader{
|
||||
EnableCompression: true,
|
||||
|
||||
// allow connections from any origin, since script may be hosted anywhere
|
||||
// security is enforced by access tokens
|
||||
CheckOrigin: func(r *http.Request) bool {
|
||||
return true
|
||||
},
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *RTCService) SetupRoutes(mux *http.ServeMux) {
|
||||
mux.HandleFunc("/rtc/validate", s.validate)
|
||||
}
|
||||
|
||||
func (s *RTCService) validate(w http.ResponseWriter, r *http.Request) {
|
||||
_, _, code, err := s.validateInternal(r)
|
||||
if err != nil {
|
||||
handleError(w, r, code, err)
|
||||
return
|
||||
}
|
||||
_, _ = w.Write([]byte("success"))
|
||||
}
|
||||
|
||||
func (s *RTCService) validateInternal(r *http.Request) (livekit.RoomName, routing.ParticipantInit, int, error) {
|
||||
claims := GetGrants(r.Context())
|
||||
var pi routing.ParticipantInit
|
||||
|
||||
// require a claim
|
||||
if claims == nil || claims.Video == nil {
|
||||
return "", pi, http.StatusUnauthorized, rtc.ErrPermissionDenied
|
||||
}
|
||||
|
||||
onlyName, err := EnsureJoinPermission(r.Context())
|
||||
if err != nil {
|
||||
return "", pi, http.StatusUnauthorized, err
|
||||
}
|
||||
|
||||
if claims.Identity == "" {
|
||||
return "", pi, http.StatusBadRequest, ErrIdentityEmpty
|
||||
}
|
||||
if limit := s.config.Limit.MaxParticipantIdentityLength; limit > 0 && len(claims.Identity) > limit {
|
||||
return "", pi, http.StatusBadRequest, fmt.Errorf("%w: max length %d", ErrParticipantIdentityExceedsLimits, limit)
|
||||
}
|
||||
|
||||
roomName := livekit.RoomName(r.FormValue("room"))
|
||||
reconnectParam := r.FormValue("reconnect")
|
||||
reconnectReason, _ := strconv.Atoi(r.FormValue("reconnect_reason")) // 0 means unknown reason
|
||||
autoSubParam := r.FormValue("auto_subscribe")
|
||||
publishParam := r.FormValue("publish")
|
||||
adaptiveStreamParam := r.FormValue("adaptive_stream")
|
||||
participantID := r.FormValue("sid")
|
||||
subscriberAllowPauseParam := r.FormValue("subscriber_allow_pause")
|
||||
disableICELite := r.FormValue("disable_ice_lite")
|
||||
|
||||
if onlyName != "" {
|
||||
roomName = onlyName
|
||||
}
|
||||
if limit := s.config.Limit.MaxRoomNameLength; limit > 0 && len(roomName) > limit {
|
||||
return "", pi, http.StatusBadRequest, fmt.Errorf("%w: max length %d", ErrRoomNameExceedsLimits, limit)
|
||||
}
|
||||
|
||||
// this is new connection for existing participant - with publish only permissions
|
||||
if publishParam != "" {
|
||||
// Make sure grant has GetCanPublish set,
|
||||
if !claims.Video.GetCanPublish() {
|
||||
return "", routing.ParticipantInit{}, http.StatusUnauthorized, rtc.ErrPermissionDenied
|
||||
}
|
||||
// Make sure by default subscribe is off
|
||||
claims.Video.SetCanSubscribe(false)
|
||||
claims.Identity += "#" + publishParam
|
||||
}
|
||||
|
||||
// room allocator validations
|
||||
err = s.roomAllocator.ValidateCreateRoom(r.Context(), roomName)
|
||||
if err != nil {
|
||||
if errors.Is(err, ErrRoomNotFound) {
|
||||
return "", pi, http.StatusNotFound, err
|
||||
} else {
|
||||
return "", pi, http.StatusInternalServerError, err
|
||||
}
|
||||
}
|
||||
|
||||
region := ""
|
||||
if router, ok := s.router.(routing.Router); ok {
|
||||
region = router.GetRegion()
|
||||
if foundNode, err := router.GetNodeForRoom(r.Context(), roomName); err == nil {
|
||||
if selector.LimitsReached(s.limits, foundNode.Stats) {
|
||||
return "", pi, http.StatusServiceUnavailable, rtc.ErrLimitExceeded
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
createRequest := &livekit.CreateRoomRequest{
|
||||
Name: string(roomName),
|
||||
RoomPreset: claims.RoomPreset,
|
||||
}
|
||||
SetRoomConfiguration(createRequest, claims.GetRoomConfiguration())
|
||||
|
||||
pi = routing.ParticipantInit{
|
||||
Reconnect: boolValue(reconnectParam),
|
||||
ReconnectReason: livekit.ReconnectReason(reconnectReason),
|
||||
Identity: livekit.ParticipantIdentity(claims.Identity),
|
||||
Name: livekit.ParticipantName(claims.Name),
|
||||
AutoSubscribe: true,
|
||||
Client: s.ParseClientInfo(r),
|
||||
Grants: claims,
|
||||
Region: region,
|
||||
CreateRoom: createRequest,
|
||||
}
|
||||
if pi.Reconnect {
|
||||
pi.ID = livekit.ParticipantID(participantID)
|
||||
}
|
||||
|
||||
if autoSubParam != "" {
|
||||
pi.AutoSubscribe = boolValue(autoSubParam)
|
||||
}
|
||||
if adaptiveStreamParam != "" {
|
||||
pi.AdaptiveStream = boolValue(adaptiveStreamParam)
|
||||
}
|
||||
if subscriberAllowPauseParam != "" {
|
||||
subscriberAllowPause := boolValue(subscriberAllowPauseParam)
|
||||
pi.SubscriberAllowPause = &subscriberAllowPause
|
||||
}
|
||||
if disableICELite != "" {
|
||||
pi.DisableICELite = boolValue(disableICELite)
|
||||
}
|
||||
|
||||
return roomName, pi, http.StatusOK, nil
|
||||
}
|
||||
|
||||
func (s *RTCService) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
// reject non websocket requests
|
||||
if !websocket.IsWebSocketUpgrade(r) {
|
||||
w.WriteHeader(404)
|
||||
return
|
||||
}
|
||||
|
||||
roomName, pi, code, err := s.validateInternal(r)
|
||||
if err != nil {
|
||||
handleError(w, r, code, err)
|
||||
return
|
||||
}
|
||||
|
||||
loggerFields := []any{
|
||||
"participant", pi.Identity,
|
||||
"pID", pi.ID,
|
||||
"room", roomName,
|
||||
"remote", false,
|
||||
}
|
||||
pLogger := utils.GetLogger(r.Context()).WithValues(loggerFields...)
|
||||
|
||||
// give it a few attempts to start session
|
||||
var cr connectionResult
|
||||
var initialResponse *livekit.SignalResponse
|
||||
for attempt := 0; attempt < s.config.SignalRelay.ConnectAttempts; attempt++ {
|
||||
connectionTimeout := 3 * time.Second * time.Duration(attempt+1)
|
||||
ctx := utils.ContextWithAttempt(r.Context(), attempt)
|
||||
cr, initialResponse, err = s.startConnection(ctx, roomName, pi, connectionTimeout)
|
||||
if err == nil || errors.Is(err, context.Canceled) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
prometheus.IncrementParticipantJoinFail(1)
|
||||
status := http.StatusInternalServerError
|
||||
var psrpcErr psrpc.Error
|
||||
if errors.As(err, &psrpcErr) {
|
||||
status = psrpcErr.ToHttp()
|
||||
}
|
||||
handleError(w, r, status, err, loggerFields...)
|
||||
return
|
||||
}
|
||||
|
||||
prometheus.IncrementParticipantJoin(1)
|
||||
|
||||
if !pi.Reconnect && initialResponse.GetJoin() != nil {
|
||||
pi.ID = livekit.ParticipantID(initialResponse.GetJoin().GetParticipant().GetSid())
|
||||
}
|
||||
|
||||
signalStats := telemetry.NewBytesSignalStats(r.Context(), s.telemetry)
|
||||
if join := initialResponse.GetJoin(); join != nil {
|
||||
signalStats.ResolveRoom(join.GetRoom())
|
||||
signalStats.ResolveParticipant(join.GetParticipant())
|
||||
}
|
||||
if pi.Reconnect && pi.ID != "" {
|
||||
signalStats.ResolveParticipant(&livekit.ParticipantInfo{
|
||||
Sid: string(pi.ID),
|
||||
Identity: string(pi.Identity),
|
||||
})
|
||||
}
|
||||
|
||||
closedByClient := atomic.NewBool(false)
|
||||
done := make(chan struct{})
|
||||
// function exits when websocket terminates, it'll close the event reading off of request sink and response source as well
|
||||
defer func() {
|
||||
pLogger.Debugw("finishing WS connection",
|
||||
"connID", cr.ConnectionID,
|
||||
"closedByClient", closedByClient.Load(),
|
||||
)
|
||||
cr.ResponseSource.Close()
|
||||
cr.RequestSink.Close()
|
||||
close(done)
|
||||
|
||||
signalStats.Stop()
|
||||
}()
|
||||
|
||||
// upgrade only once the basics are good to go
|
||||
conn, err := s.upgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
handleError(w, r, http.StatusInternalServerError, err, loggerFields...)
|
||||
return
|
||||
}
|
||||
|
||||
s.mu.Lock()
|
||||
s.connections[conn] = struct{}{}
|
||||
s.mu.Unlock()
|
||||
|
||||
defer func() {
|
||||
s.mu.Lock()
|
||||
delete(s.connections, conn)
|
||||
s.mu.Unlock()
|
||||
}()
|
||||
|
||||
// websocket established
|
||||
sigConn := NewWSSignalConnection(conn)
|
||||
count, err := sigConn.WriteResponse(initialResponse)
|
||||
if err != nil {
|
||||
pLogger.Warnw("could not write initial response", err)
|
||||
return
|
||||
}
|
||||
signalStats.AddBytes(uint64(count), true)
|
||||
|
||||
pLogger.Debugw("new client WS connected",
|
||||
"connID", cr.ConnectionID,
|
||||
"reconnect", pi.Reconnect,
|
||||
"reconnectReason", pi.ReconnectReason,
|
||||
"adaptiveStream", pi.AdaptiveStream,
|
||||
"selectedNodeID", cr.NodeID,
|
||||
"nodeSelectionReason", cr.NodeSelectionReason,
|
||||
)
|
||||
|
||||
// handle responses
|
||||
go func() {
|
||||
defer func() {
|
||||
// when the source is terminated, this means Participant.Close had been called and RTC connection is done
|
||||
// we would terminate the signal connection as well
|
||||
closeMsg := websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")
|
||||
_ = conn.WriteControl(websocket.CloseMessage, closeMsg, time.Now().Add(time.Second))
|
||||
_ = conn.Close()
|
||||
}()
|
||||
defer func() {
|
||||
if r := rtc.Recover(pLogger); r != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
}()
|
||||
for {
|
||||
select {
|
||||
case <-done:
|
||||
return
|
||||
case msg := <-cr.ResponseSource.ReadChan():
|
||||
if msg == nil {
|
||||
pLogger.Debugw("nothing to read from response source", "connID", cr.ConnectionID)
|
||||
return
|
||||
}
|
||||
res, ok := msg.(*livekit.SignalResponse)
|
||||
if !ok {
|
||||
pLogger.Errorw(
|
||||
"unexpected message type", nil,
|
||||
"type", fmt.Sprintf("%T", msg),
|
||||
"connID", cr.ConnectionID,
|
||||
)
|
||||
continue
|
||||
}
|
||||
|
||||
switch m := res.Message.(type) {
|
||||
case *livekit.SignalResponse_Offer:
|
||||
pLogger.Debugw("sending offer", "offer", m)
|
||||
case *livekit.SignalResponse_Answer:
|
||||
pLogger.Debugw("sending answer", "answer", m)
|
||||
case *livekit.SignalResponse_Join:
|
||||
pLogger.Debugw("sending join", "join", m)
|
||||
signalStats.ResolveRoom(m.Join.GetRoom())
|
||||
signalStats.ResolveParticipant(m.Join.GetParticipant())
|
||||
case *livekit.SignalResponse_RoomUpdate:
|
||||
pLogger.Debugw("sending room update", "roomUpdate", m)
|
||||
signalStats.ResolveRoom(m.RoomUpdate.GetRoom())
|
||||
case *livekit.SignalResponse_Update:
|
||||
pLogger.Debugw("sending participant update", "participantUpdate", m)
|
||||
}
|
||||
|
||||
if count, err := sigConn.WriteResponse(res); err != nil {
|
||||
pLogger.Warnw("error writing to websocket", err)
|
||||
return
|
||||
} else {
|
||||
signalStats.AddBytes(uint64(count), true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// handle incoming requests from websocket
|
||||
for {
|
||||
req, count, err := sigConn.ReadRequest()
|
||||
if err != nil {
|
||||
if IsWebSocketCloseError(err) {
|
||||
closedByClient.Store(true)
|
||||
} else {
|
||||
pLogger.Errorw("error reading from websocket", err, "connID", cr.ConnectionID)
|
||||
}
|
||||
return
|
||||
}
|
||||
signalStats.AddBytes(uint64(count), false)
|
||||
|
||||
switch m := req.Message.(type) {
|
||||
case *livekit.SignalRequest_Ping:
|
||||
count, perr := sigConn.WriteResponse(&livekit.SignalResponse{
|
||||
Message: &livekit.SignalResponse_Pong{
|
||||
//
|
||||
// Although this field is int64, some clients (like JS) cause overflow if nanosecond granularity is used.
|
||||
// So. use UnixMillis().
|
||||
//
|
||||
Pong: time.Now().UnixMilli(),
|
||||
},
|
||||
})
|
||||
if perr == nil {
|
||||
signalStats.AddBytes(uint64(count), true)
|
||||
}
|
||||
case *livekit.SignalRequest_PingReq:
|
||||
count, perr := sigConn.WriteResponse(&livekit.SignalResponse{
|
||||
Message: &livekit.SignalResponse_PongResp{
|
||||
PongResp: &livekit.Pong{
|
||||
LastPingTimestamp: m.PingReq.Timestamp,
|
||||
Timestamp: time.Now().UnixMilli(),
|
||||
},
|
||||
},
|
||||
})
|
||||
if perr == nil {
|
||||
signalStats.AddBytes(uint64(count), true)
|
||||
}
|
||||
}
|
||||
|
||||
switch m := req.Message.(type) {
|
||||
case *livekit.SignalRequest_Offer:
|
||||
pLogger.Debugw("received offer", "offer", m)
|
||||
case *livekit.SignalRequest_Answer:
|
||||
pLogger.Debugw("received answer", "answer", m)
|
||||
}
|
||||
|
||||
if err := cr.RequestSink.WriteMessage(req); err != nil {
|
||||
pLogger.Warnw("error writing to request sink", err, "connID", cr.ConnectionID)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *RTCService) ParseClientInfo(r *http.Request) *livekit.ClientInfo {
|
||||
values := r.Form
|
||||
ci := &livekit.ClientInfo{}
|
||||
if pv, err := strconv.Atoi(values.Get("protocol")); err == nil {
|
||||
ci.Protocol = int32(pv)
|
||||
}
|
||||
sdkString := values.Get("sdk")
|
||||
switch sdkString {
|
||||
case "js":
|
||||
ci.Sdk = livekit.ClientInfo_JS
|
||||
case "ios", "swift":
|
||||
ci.Sdk = livekit.ClientInfo_SWIFT
|
||||
case "android":
|
||||
ci.Sdk = livekit.ClientInfo_ANDROID
|
||||
case "flutter":
|
||||
ci.Sdk = livekit.ClientInfo_FLUTTER
|
||||
case "go":
|
||||
ci.Sdk = livekit.ClientInfo_GO
|
||||
case "unity":
|
||||
ci.Sdk = livekit.ClientInfo_UNITY
|
||||
case "reactnative":
|
||||
ci.Sdk = livekit.ClientInfo_REACT_NATIVE
|
||||
case "rust":
|
||||
ci.Sdk = livekit.ClientInfo_RUST
|
||||
case "python":
|
||||
ci.Sdk = livekit.ClientInfo_PYTHON
|
||||
case "cpp":
|
||||
ci.Sdk = livekit.ClientInfo_CPP
|
||||
case "unityweb":
|
||||
ci.Sdk = livekit.ClientInfo_UNITY_WEB
|
||||
case "node":
|
||||
ci.Sdk = livekit.ClientInfo_NODE
|
||||
}
|
||||
|
||||
ci.Version = values.Get("version")
|
||||
ci.Os = values.Get("os")
|
||||
ci.OsVersion = values.Get("os_version")
|
||||
ci.Browser = values.Get("browser")
|
||||
ci.BrowserVersion = values.Get("browser_version")
|
||||
ci.DeviceModel = values.Get("device_model")
|
||||
ci.Network = values.Get("network")
|
||||
// get real address (forwarded http header) - check Cloudflare headers first, fall back to X-Forwarded-For
|
||||
ci.Address = GetClientIP(r)
|
||||
|
||||
// attempt to parse types for SDKs that support browser as a platform
|
||||
if ci.Sdk == livekit.ClientInfo_JS ||
|
||||
ci.Sdk == livekit.ClientInfo_REACT_NATIVE ||
|
||||
ci.Sdk == livekit.ClientInfo_FLUTTER ||
|
||||
ci.Sdk == livekit.ClientInfo_UNITY {
|
||||
client := s.parser.Parse(r.UserAgent())
|
||||
if ci.Browser == "" {
|
||||
ci.Browser = client.UserAgent.Family
|
||||
ci.BrowserVersion = client.UserAgent.ToVersionString()
|
||||
}
|
||||
if ci.Os == "" {
|
||||
ci.Os = client.Os.Family
|
||||
ci.OsVersion = client.Os.ToVersionString()
|
||||
}
|
||||
if ci.DeviceModel == "" {
|
||||
model := client.Device.Family
|
||||
if model != "" && client.Device.Model != "" && model != client.Device.Model {
|
||||
model += " " + client.Device.Model
|
||||
}
|
||||
|
||||
ci.DeviceModel = model
|
||||
}
|
||||
}
|
||||
|
||||
return ci
|
||||
}
|
||||
|
||||
func (s *RTCService) DrainConnections(interval time.Duration) {
|
||||
s.mu.Lock()
|
||||
conns := maps.Clone(s.connections)
|
||||
s.mu.Unlock()
|
||||
|
||||
// jitter drain start
|
||||
time.Sleep(time.Duration(rand.Int63n(int64(interval))))
|
||||
|
||||
t := time.NewTicker(interval)
|
||||
defer t.Stop()
|
||||
|
||||
for c := range conns {
|
||||
_ = c.Close()
|
||||
<-t.C
|
||||
}
|
||||
}
|
||||
|
||||
type connectionResult struct {
|
||||
routing.StartParticipantSignalResults
|
||||
Room *livekit.Room
|
||||
}
|
||||
|
||||
func (s *RTCService) startConnection(
|
||||
ctx context.Context,
|
||||
roomName livekit.RoomName,
|
||||
pi routing.ParticipantInit,
|
||||
timeout time.Duration,
|
||||
) (connectionResult, *livekit.SignalResponse, error) {
|
||||
var cr connectionResult
|
||||
var err error
|
||||
|
||||
if err := s.roomAllocator.SelectRoomNode(ctx, roomName, ""); err != nil {
|
||||
return cr, nil, err
|
||||
}
|
||||
|
||||
// this needs to be started first *before* using router functions on this node
|
||||
cr.StartParticipantSignalResults, err = s.router.StartParticipantSignal(ctx, roomName, pi)
|
||||
if err != nil {
|
||||
return cr, nil, err
|
||||
}
|
||||
|
||||
// wait for the first message before upgrading to websocket. If no one is
|
||||
// responding to our connection attempt, we should terminate the connection
|
||||
// instead of waiting forever on the WebSocket
|
||||
initialResponse, err := readInitialResponse(cr.ResponseSource, timeout)
|
||||
if err != nil {
|
||||
// close the connection to avoid leaking
|
||||
cr.RequestSink.Close()
|
||||
cr.ResponseSource.Close()
|
||||
return cr, nil, err
|
||||
}
|
||||
|
||||
return cr, initialResponse, nil
|
||||
}
|
||||
|
||||
func readInitialResponse(source routing.MessageSource, timeout time.Duration) (*livekit.SignalResponse, error) {
|
||||
responseTimer := time.NewTimer(timeout)
|
||||
defer responseTimer.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-responseTimer.C:
|
||||
return nil, errors.New("timed out while waiting for signal response")
|
||||
case msg := <-source.ReadChan():
|
||||
if msg == nil {
|
||||
return nil, errors.New("connection closed by media")
|
||||
}
|
||||
res, ok := msg.(*livekit.SignalResponse)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unexpected message type: %T", msg)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,404 @@
|
||||
// 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 service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
_ "net/http/pprof"
|
||||
"runtime"
|
||||
"runtime/pprof"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/pion/turn/v4"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
"github.com/rs/cors"
|
||||
"github.com/twitchtv/twirp"
|
||||
"github.com/urfave/negroni/v3"
|
||||
"go.uber.org/atomic"
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
"github.com/livekit/protocol/auth"
|
||||
"github.com/livekit/protocol/livekit"
|
||||
"github.com/livekit/protocol/logger"
|
||||
"github.com/livekit/protocol/utils/xtwirp"
|
||||
|
||||
"github.com/livekit/livekit-server/pkg/config"
|
||||
"github.com/livekit/livekit-server/pkg/routing"
|
||||
"github.com/livekit/livekit-server/version"
|
||||
)
|
||||
|
||||
type LivekitServer struct {
|
||||
config *config.Config
|
||||
ioService *IOInfoService
|
||||
rtcService *RTCService
|
||||
agentService *AgentService
|
||||
httpServer *http.Server
|
||||
promServer *http.Server
|
||||
router routing.Router
|
||||
roomManager *RoomManager
|
||||
signalServer *SignalServer
|
||||
turnServer *turn.Server
|
||||
currentNode routing.LocalNode
|
||||
running atomic.Bool
|
||||
doneChan chan struct{}
|
||||
closedChan chan struct{}
|
||||
}
|
||||
|
||||
func NewLivekitServer(conf *config.Config,
|
||||
roomService livekit.RoomService,
|
||||
agentDispatchService *AgentDispatchService,
|
||||
egressService *EgressService,
|
||||
ingressService *IngressService,
|
||||
sipService *SIPService,
|
||||
ioService *IOInfoService,
|
||||
rtcService *RTCService,
|
||||
agentService *AgentService,
|
||||
keyProvider auth.KeyProvider,
|
||||
router routing.Router,
|
||||
roomManager *RoomManager,
|
||||
signalServer *SignalServer,
|
||||
turnServer *turn.Server,
|
||||
currentNode routing.LocalNode,
|
||||
) (s *LivekitServer, err error) {
|
||||
s = &LivekitServer{
|
||||
config: conf,
|
||||
ioService: ioService,
|
||||
rtcService: rtcService,
|
||||
agentService: agentService,
|
||||
router: router,
|
||||
roomManager: roomManager,
|
||||
signalServer: signalServer,
|
||||
// turn server starts automatically
|
||||
turnServer: turnServer,
|
||||
currentNode: currentNode,
|
||||
closedChan: make(chan struct{}),
|
||||
}
|
||||
|
||||
middlewares := []negroni.Handler{
|
||||
// always first
|
||||
negroni.NewRecovery(),
|
||||
// CORS is allowed, we rely on token authentication to prevent improper use
|
||||
cors.New(cors.Options{
|
||||
AllowOriginFunc: func(origin string) bool {
|
||||
return true
|
||||
},
|
||||
AllowedHeaders: []string{"*"},
|
||||
// allow preflight to be cached for a day
|
||||
MaxAge: 86400,
|
||||
}),
|
||||
negroni.HandlerFunc(RemoveDoubleSlashes),
|
||||
}
|
||||
if keyProvider != nil {
|
||||
middlewares = append(middlewares, NewAPIKeyAuthMiddleware(keyProvider))
|
||||
}
|
||||
|
||||
serverOptions := []interface{}{
|
||||
twirp.WithServerHooks(twirp.ChainHooks(
|
||||
TwirpLogger(),
|
||||
TwirpRequestStatusReporter(),
|
||||
)),
|
||||
}
|
||||
for _, opt := range xtwirp.DefaultServerOptions() {
|
||||
serverOptions = append(serverOptions, opt)
|
||||
}
|
||||
roomServer := livekit.NewRoomServiceServer(roomService, serverOptions...)
|
||||
agentDispatchServer := livekit.NewAgentDispatchServiceServer(agentDispatchService, serverOptions...)
|
||||
egressServer := livekit.NewEgressServer(egressService, serverOptions...)
|
||||
ingressServer := livekit.NewIngressServer(ingressService, serverOptions...)
|
||||
sipServer := livekit.NewSIPServer(sipService, serverOptions...)
|
||||
|
||||
mux := http.NewServeMux()
|
||||
if conf.Development {
|
||||
// pprof handlers are registered onto DefaultServeMux
|
||||
mux = http.DefaultServeMux
|
||||
mux.HandleFunc("/debug/goroutine", s.debugGoroutines)
|
||||
mux.HandleFunc("/debug/rooms", s.debugInfo)
|
||||
}
|
||||
|
||||
xtwirp.RegisterServer(mux, roomServer)
|
||||
xtwirp.RegisterServer(mux, agentDispatchServer)
|
||||
xtwirp.RegisterServer(mux, egressServer)
|
||||
xtwirp.RegisterServer(mux, ingressServer)
|
||||
xtwirp.RegisterServer(mux, sipServer)
|
||||
mux.Handle("/rtc", rtcService)
|
||||
rtcService.SetupRoutes(mux)
|
||||
mux.Handle("/agent", agentService)
|
||||
mux.HandleFunc("/", s.defaultHandler)
|
||||
|
||||
s.httpServer = &http.Server{
|
||||
Handler: configureMiddlewares(mux, middlewares...),
|
||||
}
|
||||
|
||||
if conf.PrometheusPort > 0 {
|
||||
logger.Warnw("prometheus_port is deprecated, please switch prometheus.port instead", nil)
|
||||
conf.Prometheus.Port = conf.PrometheusPort
|
||||
}
|
||||
|
||||
if conf.Prometheus.Port > 0 {
|
||||
promHandler := promhttp.Handler()
|
||||
if conf.Prometheus.Username != "" && conf.Prometheus.Password != "" {
|
||||
protectedHandler := negroni.New()
|
||||
protectedHandler.Use(negroni.HandlerFunc(GenBasicAuthMiddleware(conf.Prometheus.Username, conf.Prometheus.Password)))
|
||||
protectedHandler.UseHandler(promHandler)
|
||||
promHandler = protectedHandler
|
||||
}
|
||||
s.promServer = &http.Server{
|
||||
Handler: promHandler,
|
||||
}
|
||||
}
|
||||
|
||||
if err = router.RemoveDeadNodes(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (s *LivekitServer) Node() *livekit.Node {
|
||||
return s.currentNode.Clone()
|
||||
}
|
||||
|
||||
func (s *LivekitServer) HTTPPort() int {
|
||||
return int(s.config.Port)
|
||||
}
|
||||
|
||||
func (s *LivekitServer) IsRunning() bool {
|
||||
return s.running.Load()
|
||||
}
|
||||
|
||||
func (s *LivekitServer) Start() error {
|
||||
if s.running.Load() {
|
||||
return errors.New("already running")
|
||||
}
|
||||
s.doneChan = make(chan struct{})
|
||||
|
||||
if err := s.router.RegisterNode(); err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if err := s.router.UnregisterNode(); err != nil {
|
||||
logger.Errorw("could not unregister node", err)
|
||||
}
|
||||
}()
|
||||
|
||||
if err := s.router.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := s.ioService.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
addresses := s.config.BindAddresses
|
||||
if addresses == nil {
|
||||
addresses = []string{""}
|
||||
}
|
||||
|
||||
// ensure we could listen
|
||||
listeners := make([]net.Listener, 0)
|
||||
promListeners := make([]net.Listener, 0)
|
||||
for _, addr := range addresses {
|
||||
ln, err := net.Listen("tcp", net.JoinHostPort(addr, strconv.Itoa(int(s.config.Port))))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
listeners = append(listeners, ln)
|
||||
|
||||
if s.promServer != nil {
|
||||
ln, err = net.Listen("tcp", net.JoinHostPort(addr, strconv.Itoa(int(s.config.Prometheus.Port))))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
promListeners = append(promListeners, ln)
|
||||
}
|
||||
}
|
||||
|
||||
values := []interface{}{
|
||||
"portHttp", s.config.Port,
|
||||
"nodeID", s.currentNode.NodeID(),
|
||||
"nodeIP", s.currentNode.NodeIP(),
|
||||
"version", version.Version,
|
||||
}
|
||||
if s.config.BindAddresses != nil {
|
||||
values = append(values, "bindAddresses", s.config.BindAddresses)
|
||||
}
|
||||
if s.config.RTC.TCPPort != 0 {
|
||||
values = append(values, "rtc.portTCP", s.config.RTC.TCPPort)
|
||||
}
|
||||
if !s.config.RTC.ForceTCP && s.config.RTC.UDPPort.Valid() {
|
||||
values = append(values, "rtc.portUDP", s.config.RTC.UDPPort)
|
||||
} else {
|
||||
values = append(values,
|
||||
"rtc.portICERange", []uint32{s.config.RTC.ICEPortRangeStart, s.config.RTC.ICEPortRangeEnd},
|
||||
)
|
||||
}
|
||||
if s.config.Prometheus.Port != 0 {
|
||||
values = append(values, "portPrometheus", s.config.Prometheus.Port)
|
||||
}
|
||||
if s.config.Region != "" {
|
||||
values = append(values, "region", s.config.Region)
|
||||
}
|
||||
logger.Infow("starting LiveKit server", values...)
|
||||
if runtime.GOOS == "windows" {
|
||||
logger.Infow("Windows detected, capacity management is unavailable")
|
||||
}
|
||||
|
||||
for _, promLn := range promListeners {
|
||||
go s.promServer.Serve(promLn)
|
||||
}
|
||||
|
||||
if err := s.signalServer.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
httpGroup := &errgroup.Group{}
|
||||
for _, ln := range listeners {
|
||||
l := ln
|
||||
httpGroup.Go(func() error {
|
||||
return s.httpServer.Serve(l)
|
||||
})
|
||||
}
|
||||
go func() {
|
||||
if err := httpGroup.Wait(); err != http.ErrServerClosed {
|
||||
logger.Errorw("could not start server", err)
|
||||
s.Stop(true)
|
||||
}
|
||||
}()
|
||||
|
||||
go s.backgroundWorker()
|
||||
|
||||
// give time for Serve goroutine to start
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
s.running.Store(true)
|
||||
|
||||
<-s.doneChan
|
||||
|
||||
// wait for shutdown
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||
defer cancel()
|
||||
_ = s.httpServer.Shutdown(ctx)
|
||||
|
||||
if s.turnServer != nil {
|
||||
_ = s.turnServer.Close()
|
||||
}
|
||||
|
||||
s.roomManager.Stop()
|
||||
s.signalServer.Stop()
|
||||
s.ioService.Stop()
|
||||
|
||||
close(s.closedChan)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *LivekitServer) Stop(force bool) {
|
||||
// wait for all participants to exit
|
||||
s.router.Drain()
|
||||
partTicker := time.NewTicker(5 * time.Second)
|
||||
waitingForParticipants := !force && s.roomManager.HasParticipants()
|
||||
for waitingForParticipants {
|
||||
<-partTicker.C
|
||||
logger.Infow("waiting for participants to exit")
|
||||
waitingForParticipants = s.roomManager.HasParticipants()
|
||||
}
|
||||
partTicker.Stop()
|
||||
|
||||
if !s.running.Swap(false) {
|
||||
return
|
||||
}
|
||||
|
||||
s.router.Stop()
|
||||
close(s.doneChan)
|
||||
|
||||
// wait for fully closed
|
||||
<-s.closedChan
|
||||
}
|
||||
|
||||
func (s *LivekitServer) RoomManager() *RoomManager {
|
||||
return s.roomManager
|
||||
}
|
||||
|
||||
func (s *LivekitServer) debugGoroutines(w http.ResponseWriter, _ *http.Request) {
|
||||
_ = pprof.Lookup("goroutine").WriteTo(w, 2)
|
||||
}
|
||||
|
||||
func (s *LivekitServer) debugInfo(w http.ResponseWriter, _ *http.Request) {
|
||||
s.roomManager.lock.RLock()
|
||||
info := make([]map[string]interface{}, 0, len(s.roomManager.rooms))
|
||||
for _, room := range s.roomManager.rooms {
|
||||
info = append(info, room.DebugInfo())
|
||||
}
|
||||
s.roomManager.lock.RUnlock()
|
||||
|
||||
b, err := json.Marshal(info)
|
||||
if err != nil {
|
||||
w.WriteHeader(400)
|
||||
_, _ = w.Write([]byte(err.Error()))
|
||||
} else {
|
||||
_, _ = w.Write(b)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *LivekitServer) defaultHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == "/" {
|
||||
s.healthCheck(w, r)
|
||||
} else {
|
||||
http.NotFound(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *LivekitServer) healthCheck(w http.ResponseWriter, _ *http.Request) {
|
||||
var updatedAt time.Time
|
||||
if s.Node().Stats != nil {
|
||||
updatedAt = time.Unix(s.Node().Stats.UpdatedAt, 0)
|
||||
}
|
||||
if time.Since(updatedAt) > 4*time.Second {
|
||||
w.WriteHeader(http.StatusNotAcceptable)
|
||||
_, _ = w.Write([]byte(fmt.Sprintf("Not Ready\nNode Updated At %s", updatedAt)))
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = w.Write([]byte("OK"))
|
||||
}
|
||||
|
||||
// worker to perform periodic tasks per node
|
||||
func (s *LivekitServer) backgroundWorker() {
|
||||
roomTicker := time.NewTicker(1 * time.Second)
|
||||
defer roomTicker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-s.doneChan:
|
||||
return
|
||||
case <-roomTicker.C:
|
||||
s.roomManager.CloseIdleRooms()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func configureMiddlewares(handler http.Handler, middlewares ...negroni.Handler) *negroni.Negroni {
|
||||
n := negroni.New()
|
||||
for _, m := range middlewares {
|
||||
n.Use(m)
|
||||
}
|
||||
n.UseHandler(handler)
|
||||
return n
|
||||
}
|
||||
@@ -0,0 +1,424 @@
|
||||
// Code generated by counterfeiter. DO NOT EDIT.
|
||||
package servicefakes
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
|
||||
"github.com/livekit/livekit-server/pkg/service"
|
||||
"github.com/livekit/protocol/livekit"
|
||||
)
|
||||
|
||||
type FakeAgentStore struct {
|
||||
DeleteAgentDispatchStub func(context.Context, *livekit.AgentDispatch) error
|
||||
deleteAgentDispatchMutex sync.RWMutex
|
||||
deleteAgentDispatchArgsForCall []struct {
|
||||
arg1 context.Context
|
||||
arg2 *livekit.AgentDispatch
|
||||
}
|
||||
deleteAgentDispatchReturns struct {
|
||||
result1 error
|
||||
}
|
||||
deleteAgentDispatchReturnsOnCall map[int]struct {
|
||||
result1 error
|
||||
}
|
||||
DeleteAgentJobStub func(context.Context, *livekit.Job) error
|
||||
deleteAgentJobMutex sync.RWMutex
|
||||
deleteAgentJobArgsForCall []struct {
|
||||
arg1 context.Context
|
||||
arg2 *livekit.Job
|
||||
}
|
||||
deleteAgentJobReturns struct {
|
||||
result1 error
|
||||
}
|
||||
deleteAgentJobReturnsOnCall map[int]struct {
|
||||
result1 error
|
||||
}
|
||||
ListAgentDispatchesStub func(context.Context, livekit.RoomName) ([]*livekit.AgentDispatch, error)
|
||||
listAgentDispatchesMutex sync.RWMutex
|
||||
listAgentDispatchesArgsForCall []struct {
|
||||
arg1 context.Context
|
||||
arg2 livekit.RoomName
|
||||
}
|
||||
listAgentDispatchesReturns struct {
|
||||
result1 []*livekit.AgentDispatch
|
||||
result2 error
|
||||
}
|
||||
listAgentDispatchesReturnsOnCall map[int]struct {
|
||||
result1 []*livekit.AgentDispatch
|
||||
result2 error
|
||||
}
|
||||
StoreAgentDispatchStub func(context.Context, *livekit.AgentDispatch) error
|
||||
storeAgentDispatchMutex sync.RWMutex
|
||||
storeAgentDispatchArgsForCall []struct {
|
||||
arg1 context.Context
|
||||
arg2 *livekit.AgentDispatch
|
||||
}
|
||||
storeAgentDispatchReturns struct {
|
||||
result1 error
|
||||
}
|
||||
storeAgentDispatchReturnsOnCall map[int]struct {
|
||||
result1 error
|
||||
}
|
||||
StoreAgentJobStub func(context.Context, *livekit.Job) error
|
||||
storeAgentJobMutex sync.RWMutex
|
||||
storeAgentJobArgsForCall []struct {
|
||||
arg1 context.Context
|
||||
arg2 *livekit.Job
|
||||
}
|
||||
storeAgentJobReturns struct {
|
||||
result1 error
|
||||
}
|
||||
storeAgentJobReturnsOnCall map[int]struct {
|
||||
result1 error
|
||||
}
|
||||
invocations map[string][][]interface{}
|
||||
invocationsMutex sync.RWMutex
|
||||
}
|
||||
|
||||
func (fake *FakeAgentStore) DeleteAgentDispatch(arg1 context.Context, arg2 *livekit.AgentDispatch) error {
|
||||
fake.deleteAgentDispatchMutex.Lock()
|
||||
ret, specificReturn := fake.deleteAgentDispatchReturnsOnCall[len(fake.deleteAgentDispatchArgsForCall)]
|
||||
fake.deleteAgentDispatchArgsForCall = append(fake.deleteAgentDispatchArgsForCall, struct {
|
||||
arg1 context.Context
|
||||
arg2 *livekit.AgentDispatch
|
||||
}{arg1, arg2})
|
||||
stub := fake.DeleteAgentDispatchStub
|
||||
fakeReturns := fake.deleteAgentDispatchReturns
|
||||
fake.recordInvocation("DeleteAgentDispatch", []interface{}{arg1, arg2})
|
||||
fake.deleteAgentDispatchMutex.Unlock()
|
||||
if stub != nil {
|
||||
return stub(arg1, arg2)
|
||||
}
|
||||
if specificReturn {
|
||||
return ret.result1
|
||||
}
|
||||
return fakeReturns.result1
|
||||
}
|
||||
|
||||
func (fake *FakeAgentStore) DeleteAgentDispatchCallCount() int {
|
||||
fake.deleteAgentDispatchMutex.RLock()
|
||||
defer fake.deleteAgentDispatchMutex.RUnlock()
|
||||
return len(fake.deleteAgentDispatchArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *FakeAgentStore) DeleteAgentDispatchCalls(stub func(context.Context, *livekit.AgentDispatch) error) {
|
||||
fake.deleteAgentDispatchMutex.Lock()
|
||||
defer fake.deleteAgentDispatchMutex.Unlock()
|
||||
fake.DeleteAgentDispatchStub = stub
|
||||
}
|
||||
|
||||
func (fake *FakeAgentStore) DeleteAgentDispatchArgsForCall(i int) (context.Context, *livekit.AgentDispatch) {
|
||||
fake.deleteAgentDispatchMutex.RLock()
|
||||
defer fake.deleteAgentDispatchMutex.RUnlock()
|
||||
argsForCall := fake.deleteAgentDispatchArgsForCall[i]
|
||||
return argsForCall.arg1, argsForCall.arg2
|
||||
}
|
||||
|
||||
func (fake *FakeAgentStore) DeleteAgentDispatchReturns(result1 error) {
|
||||
fake.deleteAgentDispatchMutex.Lock()
|
||||
defer fake.deleteAgentDispatchMutex.Unlock()
|
||||
fake.DeleteAgentDispatchStub = nil
|
||||
fake.deleteAgentDispatchReturns = struct {
|
||||
result1 error
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeAgentStore) DeleteAgentDispatchReturnsOnCall(i int, result1 error) {
|
||||
fake.deleteAgentDispatchMutex.Lock()
|
||||
defer fake.deleteAgentDispatchMutex.Unlock()
|
||||
fake.DeleteAgentDispatchStub = nil
|
||||
if fake.deleteAgentDispatchReturnsOnCall == nil {
|
||||
fake.deleteAgentDispatchReturnsOnCall = make(map[int]struct {
|
||||
result1 error
|
||||
})
|
||||
}
|
||||
fake.deleteAgentDispatchReturnsOnCall[i] = struct {
|
||||
result1 error
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeAgentStore) DeleteAgentJob(arg1 context.Context, arg2 *livekit.Job) error {
|
||||
fake.deleteAgentJobMutex.Lock()
|
||||
ret, specificReturn := fake.deleteAgentJobReturnsOnCall[len(fake.deleteAgentJobArgsForCall)]
|
||||
fake.deleteAgentJobArgsForCall = append(fake.deleteAgentJobArgsForCall, struct {
|
||||
arg1 context.Context
|
||||
arg2 *livekit.Job
|
||||
}{arg1, arg2})
|
||||
stub := fake.DeleteAgentJobStub
|
||||
fakeReturns := fake.deleteAgentJobReturns
|
||||
fake.recordInvocation("DeleteAgentJob", []interface{}{arg1, arg2})
|
||||
fake.deleteAgentJobMutex.Unlock()
|
||||
if stub != nil {
|
||||
return stub(arg1, arg2)
|
||||
}
|
||||
if specificReturn {
|
||||
return ret.result1
|
||||
}
|
||||
return fakeReturns.result1
|
||||
}
|
||||
|
||||
func (fake *FakeAgentStore) DeleteAgentJobCallCount() int {
|
||||
fake.deleteAgentJobMutex.RLock()
|
||||
defer fake.deleteAgentJobMutex.RUnlock()
|
||||
return len(fake.deleteAgentJobArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *FakeAgentStore) DeleteAgentJobCalls(stub func(context.Context, *livekit.Job) error) {
|
||||
fake.deleteAgentJobMutex.Lock()
|
||||
defer fake.deleteAgentJobMutex.Unlock()
|
||||
fake.DeleteAgentJobStub = stub
|
||||
}
|
||||
|
||||
func (fake *FakeAgentStore) DeleteAgentJobArgsForCall(i int) (context.Context, *livekit.Job) {
|
||||
fake.deleteAgentJobMutex.RLock()
|
||||
defer fake.deleteAgentJobMutex.RUnlock()
|
||||
argsForCall := fake.deleteAgentJobArgsForCall[i]
|
||||
return argsForCall.arg1, argsForCall.arg2
|
||||
}
|
||||
|
||||
func (fake *FakeAgentStore) DeleteAgentJobReturns(result1 error) {
|
||||
fake.deleteAgentJobMutex.Lock()
|
||||
defer fake.deleteAgentJobMutex.Unlock()
|
||||
fake.DeleteAgentJobStub = nil
|
||||
fake.deleteAgentJobReturns = struct {
|
||||
result1 error
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeAgentStore) DeleteAgentJobReturnsOnCall(i int, result1 error) {
|
||||
fake.deleteAgentJobMutex.Lock()
|
||||
defer fake.deleteAgentJobMutex.Unlock()
|
||||
fake.DeleteAgentJobStub = nil
|
||||
if fake.deleteAgentJobReturnsOnCall == nil {
|
||||
fake.deleteAgentJobReturnsOnCall = make(map[int]struct {
|
||||
result1 error
|
||||
})
|
||||
}
|
||||
fake.deleteAgentJobReturnsOnCall[i] = struct {
|
||||
result1 error
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeAgentStore) ListAgentDispatches(arg1 context.Context, arg2 livekit.RoomName) ([]*livekit.AgentDispatch, error) {
|
||||
fake.listAgentDispatchesMutex.Lock()
|
||||
ret, specificReturn := fake.listAgentDispatchesReturnsOnCall[len(fake.listAgentDispatchesArgsForCall)]
|
||||
fake.listAgentDispatchesArgsForCall = append(fake.listAgentDispatchesArgsForCall, struct {
|
||||
arg1 context.Context
|
||||
arg2 livekit.RoomName
|
||||
}{arg1, arg2})
|
||||
stub := fake.ListAgentDispatchesStub
|
||||
fakeReturns := fake.listAgentDispatchesReturns
|
||||
fake.recordInvocation("ListAgentDispatches", []interface{}{arg1, arg2})
|
||||
fake.listAgentDispatchesMutex.Unlock()
|
||||
if stub != nil {
|
||||
return stub(arg1, arg2)
|
||||
}
|
||||
if specificReturn {
|
||||
return ret.result1, ret.result2
|
||||
}
|
||||
return fakeReturns.result1, fakeReturns.result2
|
||||
}
|
||||
|
||||
func (fake *FakeAgentStore) ListAgentDispatchesCallCount() int {
|
||||
fake.listAgentDispatchesMutex.RLock()
|
||||
defer fake.listAgentDispatchesMutex.RUnlock()
|
||||
return len(fake.listAgentDispatchesArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *FakeAgentStore) ListAgentDispatchesCalls(stub func(context.Context, livekit.RoomName) ([]*livekit.AgentDispatch, error)) {
|
||||
fake.listAgentDispatchesMutex.Lock()
|
||||
defer fake.listAgentDispatchesMutex.Unlock()
|
||||
fake.ListAgentDispatchesStub = stub
|
||||
}
|
||||
|
||||
func (fake *FakeAgentStore) ListAgentDispatchesArgsForCall(i int) (context.Context, livekit.RoomName) {
|
||||
fake.listAgentDispatchesMutex.RLock()
|
||||
defer fake.listAgentDispatchesMutex.RUnlock()
|
||||
argsForCall := fake.listAgentDispatchesArgsForCall[i]
|
||||
return argsForCall.arg1, argsForCall.arg2
|
||||
}
|
||||
|
||||
func (fake *FakeAgentStore) ListAgentDispatchesReturns(result1 []*livekit.AgentDispatch, result2 error) {
|
||||
fake.listAgentDispatchesMutex.Lock()
|
||||
defer fake.listAgentDispatchesMutex.Unlock()
|
||||
fake.ListAgentDispatchesStub = nil
|
||||
fake.listAgentDispatchesReturns = struct {
|
||||
result1 []*livekit.AgentDispatch
|
||||
result2 error
|
||||
}{result1, result2}
|
||||
}
|
||||
|
||||
func (fake *FakeAgentStore) ListAgentDispatchesReturnsOnCall(i int, result1 []*livekit.AgentDispatch, result2 error) {
|
||||
fake.listAgentDispatchesMutex.Lock()
|
||||
defer fake.listAgentDispatchesMutex.Unlock()
|
||||
fake.ListAgentDispatchesStub = nil
|
||||
if fake.listAgentDispatchesReturnsOnCall == nil {
|
||||
fake.listAgentDispatchesReturnsOnCall = make(map[int]struct {
|
||||
result1 []*livekit.AgentDispatch
|
||||
result2 error
|
||||
})
|
||||
}
|
||||
fake.listAgentDispatchesReturnsOnCall[i] = struct {
|
||||
result1 []*livekit.AgentDispatch
|
||||
result2 error
|
||||
}{result1, result2}
|
||||
}
|
||||
|
||||
func (fake *FakeAgentStore) StoreAgentDispatch(arg1 context.Context, arg2 *livekit.AgentDispatch) error {
|
||||
fake.storeAgentDispatchMutex.Lock()
|
||||
ret, specificReturn := fake.storeAgentDispatchReturnsOnCall[len(fake.storeAgentDispatchArgsForCall)]
|
||||
fake.storeAgentDispatchArgsForCall = append(fake.storeAgentDispatchArgsForCall, struct {
|
||||
arg1 context.Context
|
||||
arg2 *livekit.AgentDispatch
|
||||
}{arg1, arg2})
|
||||
stub := fake.StoreAgentDispatchStub
|
||||
fakeReturns := fake.storeAgentDispatchReturns
|
||||
fake.recordInvocation("StoreAgentDispatch", []interface{}{arg1, arg2})
|
||||
fake.storeAgentDispatchMutex.Unlock()
|
||||
if stub != nil {
|
||||
return stub(arg1, arg2)
|
||||
}
|
||||
if specificReturn {
|
||||
return ret.result1
|
||||
}
|
||||
return fakeReturns.result1
|
||||
}
|
||||
|
||||
func (fake *FakeAgentStore) StoreAgentDispatchCallCount() int {
|
||||
fake.storeAgentDispatchMutex.RLock()
|
||||
defer fake.storeAgentDispatchMutex.RUnlock()
|
||||
return len(fake.storeAgentDispatchArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *FakeAgentStore) StoreAgentDispatchCalls(stub func(context.Context, *livekit.AgentDispatch) error) {
|
||||
fake.storeAgentDispatchMutex.Lock()
|
||||
defer fake.storeAgentDispatchMutex.Unlock()
|
||||
fake.StoreAgentDispatchStub = stub
|
||||
}
|
||||
|
||||
func (fake *FakeAgentStore) StoreAgentDispatchArgsForCall(i int) (context.Context, *livekit.AgentDispatch) {
|
||||
fake.storeAgentDispatchMutex.RLock()
|
||||
defer fake.storeAgentDispatchMutex.RUnlock()
|
||||
argsForCall := fake.storeAgentDispatchArgsForCall[i]
|
||||
return argsForCall.arg1, argsForCall.arg2
|
||||
}
|
||||
|
||||
func (fake *FakeAgentStore) StoreAgentDispatchReturns(result1 error) {
|
||||
fake.storeAgentDispatchMutex.Lock()
|
||||
defer fake.storeAgentDispatchMutex.Unlock()
|
||||
fake.StoreAgentDispatchStub = nil
|
||||
fake.storeAgentDispatchReturns = struct {
|
||||
result1 error
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeAgentStore) StoreAgentDispatchReturnsOnCall(i int, result1 error) {
|
||||
fake.storeAgentDispatchMutex.Lock()
|
||||
defer fake.storeAgentDispatchMutex.Unlock()
|
||||
fake.StoreAgentDispatchStub = nil
|
||||
if fake.storeAgentDispatchReturnsOnCall == nil {
|
||||
fake.storeAgentDispatchReturnsOnCall = make(map[int]struct {
|
||||
result1 error
|
||||
})
|
||||
}
|
||||
fake.storeAgentDispatchReturnsOnCall[i] = struct {
|
||||
result1 error
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeAgentStore) StoreAgentJob(arg1 context.Context, arg2 *livekit.Job) error {
|
||||
fake.storeAgentJobMutex.Lock()
|
||||
ret, specificReturn := fake.storeAgentJobReturnsOnCall[len(fake.storeAgentJobArgsForCall)]
|
||||
fake.storeAgentJobArgsForCall = append(fake.storeAgentJobArgsForCall, struct {
|
||||
arg1 context.Context
|
||||
arg2 *livekit.Job
|
||||
}{arg1, arg2})
|
||||
stub := fake.StoreAgentJobStub
|
||||
fakeReturns := fake.storeAgentJobReturns
|
||||
fake.recordInvocation("StoreAgentJob", []interface{}{arg1, arg2})
|
||||
fake.storeAgentJobMutex.Unlock()
|
||||
if stub != nil {
|
||||
return stub(arg1, arg2)
|
||||
}
|
||||
if specificReturn {
|
||||
return ret.result1
|
||||
}
|
||||
return fakeReturns.result1
|
||||
}
|
||||
|
||||
func (fake *FakeAgentStore) StoreAgentJobCallCount() int {
|
||||
fake.storeAgentJobMutex.RLock()
|
||||
defer fake.storeAgentJobMutex.RUnlock()
|
||||
return len(fake.storeAgentJobArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *FakeAgentStore) StoreAgentJobCalls(stub func(context.Context, *livekit.Job) error) {
|
||||
fake.storeAgentJobMutex.Lock()
|
||||
defer fake.storeAgentJobMutex.Unlock()
|
||||
fake.StoreAgentJobStub = stub
|
||||
}
|
||||
|
||||
func (fake *FakeAgentStore) StoreAgentJobArgsForCall(i int) (context.Context, *livekit.Job) {
|
||||
fake.storeAgentJobMutex.RLock()
|
||||
defer fake.storeAgentJobMutex.RUnlock()
|
||||
argsForCall := fake.storeAgentJobArgsForCall[i]
|
||||
return argsForCall.arg1, argsForCall.arg2
|
||||
}
|
||||
|
||||
func (fake *FakeAgentStore) StoreAgentJobReturns(result1 error) {
|
||||
fake.storeAgentJobMutex.Lock()
|
||||
defer fake.storeAgentJobMutex.Unlock()
|
||||
fake.StoreAgentJobStub = nil
|
||||
fake.storeAgentJobReturns = struct {
|
||||
result1 error
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeAgentStore) StoreAgentJobReturnsOnCall(i int, result1 error) {
|
||||
fake.storeAgentJobMutex.Lock()
|
||||
defer fake.storeAgentJobMutex.Unlock()
|
||||
fake.StoreAgentJobStub = nil
|
||||
if fake.storeAgentJobReturnsOnCall == nil {
|
||||
fake.storeAgentJobReturnsOnCall = make(map[int]struct {
|
||||
result1 error
|
||||
})
|
||||
}
|
||||
fake.storeAgentJobReturnsOnCall[i] = struct {
|
||||
result1 error
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeAgentStore) Invocations() map[string][][]interface{} {
|
||||
fake.invocationsMutex.RLock()
|
||||
defer fake.invocationsMutex.RUnlock()
|
||||
fake.deleteAgentDispatchMutex.RLock()
|
||||
defer fake.deleteAgentDispatchMutex.RUnlock()
|
||||
fake.deleteAgentJobMutex.RLock()
|
||||
defer fake.deleteAgentJobMutex.RUnlock()
|
||||
fake.listAgentDispatchesMutex.RLock()
|
||||
defer fake.listAgentDispatchesMutex.RUnlock()
|
||||
fake.storeAgentDispatchMutex.RLock()
|
||||
defer fake.storeAgentDispatchMutex.RUnlock()
|
||||
fake.storeAgentJobMutex.RLock()
|
||||
defer fake.storeAgentJobMutex.RUnlock()
|
||||
copiedInvocations := map[string][][]interface{}{}
|
||||
for key, value := range fake.invocations {
|
||||
copiedInvocations[key] = value
|
||||
}
|
||||
return copiedInvocations
|
||||
}
|
||||
|
||||
func (fake *FakeAgentStore) 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 _ service.AgentStore = new(FakeAgentStore)
|
||||
@@ -0,0 +1,355 @@
|
||||
// Code generated by counterfeiter. DO NOT EDIT.
|
||||
package servicefakes
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
|
||||
"github.com/livekit/livekit-server/pkg/service"
|
||||
"github.com/livekit/protocol/livekit"
|
||||
)
|
||||
|
||||
type FakeEgressStore struct {
|
||||
ListEgressStub func(context.Context, livekit.RoomName, bool) ([]*livekit.EgressInfo, error)
|
||||
listEgressMutex sync.RWMutex
|
||||
listEgressArgsForCall []struct {
|
||||
arg1 context.Context
|
||||
arg2 livekit.RoomName
|
||||
arg3 bool
|
||||
}
|
||||
listEgressReturns struct {
|
||||
result1 []*livekit.EgressInfo
|
||||
result2 error
|
||||
}
|
||||
listEgressReturnsOnCall map[int]struct {
|
||||
result1 []*livekit.EgressInfo
|
||||
result2 error
|
||||
}
|
||||
LoadEgressStub func(context.Context, string) (*livekit.EgressInfo, error)
|
||||
loadEgressMutex sync.RWMutex
|
||||
loadEgressArgsForCall []struct {
|
||||
arg1 context.Context
|
||||
arg2 string
|
||||
}
|
||||
loadEgressReturns struct {
|
||||
result1 *livekit.EgressInfo
|
||||
result2 error
|
||||
}
|
||||
loadEgressReturnsOnCall map[int]struct {
|
||||
result1 *livekit.EgressInfo
|
||||
result2 error
|
||||
}
|
||||
StoreEgressStub func(context.Context, *livekit.EgressInfo) error
|
||||
storeEgressMutex sync.RWMutex
|
||||
storeEgressArgsForCall []struct {
|
||||
arg1 context.Context
|
||||
arg2 *livekit.EgressInfo
|
||||
}
|
||||
storeEgressReturns struct {
|
||||
result1 error
|
||||
}
|
||||
storeEgressReturnsOnCall map[int]struct {
|
||||
result1 error
|
||||
}
|
||||
UpdateEgressStub func(context.Context, *livekit.EgressInfo) error
|
||||
updateEgressMutex sync.RWMutex
|
||||
updateEgressArgsForCall []struct {
|
||||
arg1 context.Context
|
||||
arg2 *livekit.EgressInfo
|
||||
}
|
||||
updateEgressReturns struct {
|
||||
result1 error
|
||||
}
|
||||
updateEgressReturnsOnCall map[int]struct {
|
||||
result1 error
|
||||
}
|
||||
invocations map[string][][]interface{}
|
||||
invocationsMutex sync.RWMutex
|
||||
}
|
||||
|
||||
func (fake *FakeEgressStore) ListEgress(arg1 context.Context, arg2 livekit.RoomName, arg3 bool) ([]*livekit.EgressInfo, error) {
|
||||
fake.listEgressMutex.Lock()
|
||||
ret, specificReturn := fake.listEgressReturnsOnCall[len(fake.listEgressArgsForCall)]
|
||||
fake.listEgressArgsForCall = append(fake.listEgressArgsForCall, struct {
|
||||
arg1 context.Context
|
||||
arg2 livekit.RoomName
|
||||
arg3 bool
|
||||
}{arg1, arg2, arg3})
|
||||
stub := fake.ListEgressStub
|
||||
fakeReturns := fake.listEgressReturns
|
||||
fake.recordInvocation("ListEgress", []interface{}{arg1, arg2, arg3})
|
||||
fake.listEgressMutex.Unlock()
|
||||
if stub != nil {
|
||||
return stub(arg1, arg2, arg3)
|
||||
}
|
||||
if specificReturn {
|
||||
return ret.result1, ret.result2
|
||||
}
|
||||
return fakeReturns.result1, fakeReturns.result2
|
||||
}
|
||||
|
||||
func (fake *FakeEgressStore) ListEgressCallCount() int {
|
||||
fake.listEgressMutex.RLock()
|
||||
defer fake.listEgressMutex.RUnlock()
|
||||
return len(fake.listEgressArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *FakeEgressStore) ListEgressCalls(stub func(context.Context, livekit.RoomName, bool) ([]*livekit.EgressInfo, error)) {
|
||||
fake.listEgressMutex.Lock()
|
||||
defer fake.listEgressMutex.Unlock()
|
||||
fake.ListEgressStub = stub
|
||||
}
|
||||
|
||||
func (fake *FakeEgressStore) ListEgressArgsForCall(i int) (context.Context, livekit.RoomName, bool) {
|
||||
fake.listEgressMutex.RLock()
|
||||
defer fake.listEgressMutex.RUnlock()
|
||||
argsForCall := fake.listEgressArgsForCall[i]
|
||||
return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3
|
||||
}
|
||||
|
||||
func (fake *FakeEgressStore) ListEgressReturns(result1 []*livekit.EgressInfo, result2 error) {
|
||||
fake.listEgressMutex.Lock()
|
||||
defer fake.listEgressMutex.Unlock()
|
||||
fake.ListEgressStub = nil
|
||||
fake.listEgressReturns = struct {
|
||||
result1 []*livekit.EgressInfo
|
||||
result2 error
|
||||
}{result1, result2}
|
||||
}
|
||||
|
||||
func (fake *FakeEgressStore) ListEgressReturnsOnCall(i int, result1 []*livekit.EgressInfo, result2 error) {
|
||||
fake.listEgressMutex.Lock()
|
||||
defer fake.listEgressMutex.Unlock()
|
||||
fake.ListEgressStub = nil
|
||||
if fake.listEgressReturnsOnCall == nil {
|
||||
fake.listEgressReturnsOnCall = make(map[int]struct {
|
||||
result1 []*livekit.EgressInfo
|
||||
result2 error
|
||||
})
|
||||
}
|
||||
fake.listEgressReturnsOnCall[i] = struct {
|
||||
result1 []*livekit.EgressInfo
|
||||
result2 error
|
||||
}{result1, result2}
|
||||
}
|
||||
|
||||
func (fake *FakeEgressStore) LoadEgress(arg1 context.Context, arg2 string) (*livekit.EgressInfo, error) {
|
||||
fake.loadEgressMutex.Lock()
|
||||
ret, specificReturn := fake.loadEgressReturnsOnCall[len(fake.loadEgressArgsForCall)]
|
||||
fake.loadEgressArgsForCall = append(fake.loadEgressArgsForCall, struct {
|
||||
arg1 context.Context
|
||||
arg2 string
|
||||
}{arg1, arg2})
|
||||
stub := fake.LoadEgressStub
|
||||
fakeReturns := fake.loadEgressReturns
|
||||
fake.recordInvocation("LoadEgress", []interface{}{arg1, arg2})
|
||||
fake.loadEgressMutex.Unlock()
|
||||
if stub != nil {
|
||||
return stub(arg1, arg2)
|
||||
}
|
||||
if specificReturn {
|
||||
return ret.result1, ret.result2
|
||||
}
|
||||
return fakeReturns.result1, fakeReturns.result2
|
||||
}
|
||||
|
||||
func (fake *FakeEgressStore) LoadEgressCallCount() int {
|
||||
fake.loadEgressMutex.RLock()
|
||||
defer fake.loadEgressMutex.RUnlock()
|
||||
return len(fake.loadEgressArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *FakeEgressStore) LoadEgressCalls(stub func(context.Context, string) (*livekit.EgressInfo, error)) {
|
||||
fake.loadEgressMutex.Lock()
|
||||
defer fake.loadEgressMutex.Unlock()
|
||||
fake.LoadEgressStub = stub
|
||||
}
|
||||
|
||||
func (fake *FakeEgressStore) LoadEgressArgsForCall(i int) (context.Context, string) {
|
||||
fake.loadEgressMutex.RLock()
|
||||
defer fake.loadEgressMutex.RUnlock()
|
||||
argsForCall := fake.loadEgressArgsForCall[i]
|
||||
return argsForCall.arg1, argsForCall.arg2
|
||||
}
|
||||
|
||||
func (fake *FakeEgressStore) LoadEgressReturns(result1 *livekit.EgressInfo, result2 error) {
|
||||
fake.loadEgressMutex.Lock()
|
||||
defer fake.loadEgressMutex.Unlock()
|
||||
fake.LoadEgressStub = nil
|
||||
fake.loadEgressReturns = struct {
|
||||
result1 *livekit.EgressInfo
|
||||
result2 error
|
||||
}{result1, result2}
|
||||
}
|
||||
|
||||
func (fake *FakeEgressStore) LoadEgressReturnsOnCall(i int, result1 *livekit.EgressInfo, result2 error) {
|
||||
fake.loadEgressMutex.Lock()
|
||||
defer fake.loadEgressMutex.Unlock()
|
||||
fake.LoadEgressStub = nil
|
||||
if fake.loadEgressReturnsOnCall == nil {
|
||||
fake.loadEgressReturnsOnCall = make(map[int]struct {
|
||||
result1 *livekit.EgressInfo
|
||||
result2 error
|
||||
})
|
||||
}
|
||||
fake.loadEgressReturnsOnCall[i] = struct {
|
||||
result1 *livekit.EgressInfo
|
||||
result2 error
|
||||
}{result1, result2}
|
||||
}
|
||||
|
||||
func (fake *FakeEgressStore) StoreEgress(arg1 context.Context, arg2 *livekit.EgressInfo) error {
|
||||
fake.storeEgressMutex.Lock()
|
||||
ret, specificReturn := fake.storeEgressReturnsOnCall[len(fake.storeEgressArgsForCall)]
|
||||
fake.storeEgressArgsForCall = append(fake.storeEgressArgsForCall, struct {
|
||||
arg1 context.Context
|
||||
arg2 *livekit.EgressInfo
|
||||
}{arg1, arg2})
|
||||
stub := fake.StoreEgressStub
|
||||
fakeReturns := fake.storeEgressReturns
|
||||
fake.recordInvocation("StoreEgress", []interface{}{arg1, arg2})
|
||||
fake.storeEgressMutex.Unlock()
|
||||
if stub != nil {
|
||||
return stub(arg1, arg2)
|
||||
}
|
||||
if specificReturn {
|
||||
return ret.result1
|
||||
}
|
||||
return fakeReturns.result1
|
||||
}
|
||||
|
||||
func (fake *FakeEgressStore) StoreEgressCallCount() int {
|
||||
fake.storeEgressMutex.RLock()
|
||||
defer fake.storeEgressMutex.RUnlock()
|
||||
return len(fake.storeEgressArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *FakeEgressStore) StoreEgressCalls(stub func(context.Context, *livekit.EgressInfo) error) {
|
||||
fake.storeEgressMutex.Lock()
|
||||
defer fake.storeEgressMutex.Unlock()
|
||||
fake.StoreEgressStub = stub
|
||||
}
|
||||
|
||||
func (fake *FakeEgressStore) StoreEgressArgsForCall(i int) (context.Context, *livekit.EgressInfo) {
|
||||
fake.storeEgressMutex.RLock()
|
||||
defer fake.storeEgressMutex.RUnlock()
|
||||
argsForCall := fake.storeEgressArgsForCall[i]
|
||||
return argsForCall.arg1, argsForCall.arg2
|
||||
}
|
||||
|
||||
func (fake *FakeEgressStore) StoreEgressReturns(result1 error) {
|
||||
fake.storeEgressMutex.Lock()
|
||||
defer fake.storeEgressMutex.Unlock()
|
||||
fake.StoreEgressStub = nil
|
||||
fake.storeEgressReturns = struct {
|
||||
result1 error
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeEgressStore) StoreEgressReturnsOnCall(i int, result1 error) {
|
||||
fake.storeEgressMutex.Lock()
|
||||
defer fake.storeEgressMutex.Unlock()
|
||||
fake.StoreEgressStub = nil
|
||||
if fake.storeEgressReturnsOnCall == nil {
|
||||
fake.storeEgressReturnsOnCall = make(map[int]struct {
|
||||
result1 error
|
||||
})
|
||||
}
|
||||
fake.storeEgressReturnsOnCall[i] = struct {
|
||||
result1 error
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeEgressStore) UpdateEgress(arg1 context.Context, arg2 *livekit.EgressInfo) error {
|
||||
fake.updateEgressMutex.Lock()
|
||||
ret, specificReturn := fake.updateEgressReturnsOnCall[len(fake.updateEgressArgsForCall)]
|
||||
fake.updateEgressArgsForCall = append(fake.updateEgressArgsForCall, struct {
|
||||
arg1 context.Context
|
||||
arg2 *livekit.EgressInfo
|
||||
}{arg1, arg2})
|
||||
stub := fake.UpdateEgressStub
|
||||
fakeReturns := fake.updateEgressReturns
|
||||
fake.recordInvocation("UpdateEgress", []interface{}{arg1, arg2})
|
||||
fake.updateEgressMutex.Unlock()
|
||||
if stub != nil {
|
||||
return stub(arg1, arg2)
|
||||
}
|
||||
if specificReturn {
|
||||
return ret.result1
|
||||
}
|
||||
return fakeReturns.result1
|
||||
}
|
||||
|
||||
func (fake *FakeEgressStore) UpdateEgressCallCount() int {
|
||||
fake.updateEgressMutex.RLock()
|
||||
defer fake.updateEgressMutex.RUnlock()
|
||||
return len(fake.updateEgressArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *FakeEgressStore) UpdateEgressCalls(stub func(context.Context, *livekit.EgressInfo) error) {
|
||||
fake.updateEgressMutex.Lock()
|
||||
defer fake.updateEgressMutex.Unlock()
|
||||
fake.UpdateEgressStub = stub
|
||||
}
|
||||
|
||||
func (fake *FakeEgressStore) UpdateEgressArgsForCall(i int) (context.Context, *livekit.EgressInfo) {
|
||||
fake.updateEgressMutex.RLock()
|
||||
defer fake.updateEgressMutex.RUnlock()
|
||||
argsForCall := fake.updateEgressArgsForCall[i]
|
||||
return argsForCall.arg1, argsForCall.arg2
|
||||
}
|
||||
|
||||
func (fake *FakeEgressStore) UpdateEgressReturns(result1 error) {
|
||||
fake.updateEgressMutex.Lock()
|
||||
defer fake.updateEgressMutex.Unlock()
|
||||
fake.UpdateEgressStub = nil
|
||||
fake.updateEgressReturns = struct {
|
||||
result1 error
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeEgressStore) UpdateEgressReturnsOnCall(i int, result1 error) {
|
||||
fake.updateEgressMutex.Lock()
|
||||
defer fake.updateEgressMutex.Unlock()
|
||||
fake.UpdateEgressStub = nil
|
||||
if fake.updateEgressReturnsOnCall == nil {
|
||||
fake.updateEgressReturnsOnCall = make(map[int]struct {
|
||||
result1 error
|
||||
})
|
||||
}
|
||||
fake.updateEgressReturnsOnCall[i] = struct {
|
||||
result1 error
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeEgressStore) Invocations() map[string][][]interface{} {
|
||||
fake.invocationsMutex.RLock()
|
||||
defer fake.invocationsMutex.RUnlock()
|
||||
fake.listEgressMutex.RLock()
|
||||
defer fake.listEgressMutex.RUnlock()
|
||||
fake.loadEgressMutex.RLock()
|
||||
defer fake.loadEgressMutex.RUnlock()
|
||||
fake.storeEgressMutex.RLock()
|
||||
defer fake.storeEgressMutex.RUnlock()
|
||||
fake.updateEgressMutex.RLock()
|
||||
defer fake.updateEgressMutex.RUnlock()
|
||||
copiedInvocations := map[string][][]interface{}{}
|
||||
for key, value := range fake.invocations {
|
||||
copiedInvocations[key] = value
|
||||
}
|
||||
return copiedInvocations
|
||||
}
|
||||
|
||||
func (fake *FakeEgressStore) 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 _ service.EgressStore = new(FakeEgressStore)
|
||||
@@ -0,0 +1,588 @@
|
||||
// Code generated by counterfeiter. DO NOT EDIT.
|
||||
package servicefakes
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
|
||||
"github.com/livekit/livekit-server/pkg/service"
|
||||
"github.com/livekit/protocol/livekit"
|
||||
)
|
||||
|
||||
type FakeIngressStore struct {
|
||||
DeleteIngressStub func(context.Context, *livekit.IngressInfo) error
|
||||
deleteIngressMutex sync.RWMutex
|
||||
deleteIngressArgsForCall []struct {
|
||||
arg1 context.Context
|
||||
arg2 *livekit.IngressInfo
|
||||
}
|
||||
deleteIngressReturns struct {
|
||||
result1 error
|
||||
}
|
||||
deleteIngressReturnsOnCall map[int]struct {
|
||||
result1 error
|
||||
}
|
||||
ListIngressStub func(context.Context, livekit.RoomName) ([]*livekit.IngressInfo, error)
|
||||
listIngressMutex sync.RWMutex
|
||||
listIngressArgsForCall []struct {
|
||||
arg1 context.Context
|
||||
arg2 livekit.RoomName
|
||||
}
|
||||
listIngressReturns struct {
|
||||
result1 []*livekit.IngressInfo
|
||||
result2 error
|
||||
}
|
||||
listIngressReturnsOnCall map[int]struct {
|
||||
result1 []*livekit.IngressInfo
|
||||
result2 error
|
||||
}
|
||||
LoadIngressStub func(context.Context, string) (*livekit.IngressInfo, error)
|
||||
loadIngressMutex sync.RWMutex
|
||||
loadIngressArgsForCall []struct {
|
||||
arg1 context.Context
|
||||
arg2 string
|
||||
}
|
||||
loadIngressReturns struct {
|
||||
result1 *livekit.IngressInfo
|
||||
result2 error
|
||||
}
|
||||
loadIngressReturnsOnCall map[int]struct {
|
||||
result1 *livekit.IngressInfo
|
||||
result2 error
|
||||
}
|
||||
LoadIngressFromStreamKeyStub func(context.Context, string) (*livekit.IngressInfo, error)
|
||||
loadIngressFromStreamKeyMutex sync.RWMutex
|
||||
loadIngressFromStreamKeyArgsForCall []struct {
|
||||
arg1 context.Context
|
||||
arg2 string
|
||||
}
|
||||
loadIngressFromStreamKeyReturns struct {
|
||||
result1 *livekit.IngressInfo
|
||||
result2 error
|
||||
}
|
||||
loadIngressFromStreamKeyReturnsOnCall map[int]struct {
|
||||
result1 *livekit.IngressInfo
|
||||
result2 error
|
||||
}
|
||||
StoreIngressStub func(context.Context, *livekit.IngressInfo) error
|
||||
storeIngressMutex sync.RWMutex
|
||||
storeIngressArgsForCall []struct {
|
||||
arg1 context.Context
|
||||
arg2 *livekit.IngressInfo
|
||||
}
|
||||
storeIngressReturns struct {
|
||||
result1 error
|
||||
}
|
||||
storeIngressReturnsOnCall map[int]struct {
|
||||
result1 error
|
||||
}
|
||||
UpdateIngressStub func(context.Context, *livekit.IngressInfo) error
|
||||
updateIngressMutex sync.RWMutex
|
||||
updateIngressArgsForCall []struct {
|
||||
arg1 context.Context
|
||||
arg2 *livekit.IngressInfo
|
||||
}
|
||||
updateIngressReturns struct {
|
||||
result1 error
|
||||
}
|
||||
updateIngressReturnsOnCall map[int]struct {
|
||||
result1 error
|
||||
}
|
||||
UpdateIngressStateStub func(context.Context, string, *livekit.IngressState) error
|
||||
updateIngressStateMutex sync.RWMutex
|
||||
updateIngressStateArgsForCall []struct {
|
||||
arg1 context.Context
|
||||
arg2 string
|
||||
arg3 *livekit.IngressState
|
||||
}
|
||||
updateIngressStateReturns struct {
|
||||
result1 error
|
||||
}
|
||||
updateIngressStateReturnsOnCall map[int]struct {
|
||||
result1 error
|
||||
}
|
||||
invocations map[string][][]interface{}
|
||||
invocationsMutex sync.RWMutex
|
||||
}
|
||||
|
||||
func (fake *FakeIngressStore) DeleteIngress(arg1 context.Context, arg2 *livekit.IngressInfo) error {
|
||||
fake.deleteIngressMutex.Lock()
|
||||
ret, specificReturn := fake.deleteIngressReturnsOnCall[len(fake.deleteIngressArgsForCall)]
|
||||
fake.deleteIngressArgsForCall = append(fake.deleteIngressArgsForCall, struct {
|
||||
arg1 context.Context
|
||||
arg2 *livekit.IngressInfo
|
||||
}{arg1, arg2})
|
||||
stub := fake.DeleteIngressStub
|
||||
fakeReturns := fake.deleteIngressReturns
|
||||
fake.recordInvocation("DeleteIngress", []interface{}{arg1, arg2})
|
||||
fake.deleteIngressMutex.Unlock()
|
||||
if stub != nil {
|
||||
return stub(arg1, arg2)
|
||||
}
|
||||
if specificReturn {
|
||||
return ret.result1
|
||||
}
|
||||
return fakeReturns.result1
|
||||
}
|
||||
|
||||
func (fake *FakeIngressStore) DeleteIngressCallCount() int {
|
||||
fake.deleteIngressMutex.RLock()
|
||||
defer fake.deleteIngressMutex.RUnlock()
|
||||
return len(fake.deleteIngressArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *FakeIngressStore) DeleteIngressCalls(stub func(context.Context, *livekit.IngressInfo) error) {
|
||||
fake.deleteIngressMutex.Lock()
|
||||
defer fake.deleteIngressMutex.Unlock()
|
||||
fake.DeleteIngressStub = stub
|
||||
}
|
||||
|
||||
func (fake *FakeIngressStore) DeleteIngressArgsForCall(i int) (context.Context, *livekit.IngressInfo) {
|
||||
fake.deleteIngressMutex.RLock()
|
||||
defer fake.deleteIngressMutex.RUnlock()
|
||||
argsForCall := fake.deleteIngressArgsForCall[i]
|
||||
return argsForCall.arg1, argsForCall.arg2
|
||||
}
|
||||
|
||||
func (fake *FakeIngressStore) DeleteIngressReturns(result1 error) {
|
||||
fake.deleteIngressMutex.Lock()
|
||||
defer fake.deleteIngressMutex.Unlock()
|
||||
fake.DeleteIngressStub = nil
|
||||
fake.deleteIngressReturns = struct {
|
||||
result1 error
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeIngressStore) DeleteIngressReturnsOnCall(i int, result1 error) {
|
||||
fake.deleteIngressMutex.Lock()
|
||||
defer fake.deleteIngressMutex.Unlock()
|
||||
fake.DeleteIngressStub = nil
|
||||
if fake.deleteIngressReturnsOnCall == nil {
|
||||
fake.deleteIngressReturnsOnCall = make(map[int]struct {
|
||||
result1 error
|
||||
})
|
||||
}
|
||||
fake.deleteIngressReturnsOnCall[i] = struct {
|
||||
result1 error
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeIngressStore) ListIngress(arg1 context.Context, arg2 livekit.RoomName) ([]*livekit.IngressInfo, error) {
|
||||
fake.listIngressMutex.Lock()
|
||||
ret, specificReturn := fake.listIngressReturnsOnCall[len(fake.listIngressArgsForCall)]
|
||||
fake.listIngressArgsForCall = append(fake.listIngressArgsForCall, struct {
|
||||
arg1 context.Context
|
||||
arg2 livekit.RoomName
|
||||
}{arg1, arg2})
|
||||
stub := fake.ListIngressStub
|
||||
fakeReturns := fake.listIngressReturns
|
||||
fake.recordInvocation("ListIngress", []interface{}{arg1, arg2})
|
||||
fake.listIngressMutex.Unlock()
|
||||
if stub != nil {
|
||||
return stub(arg1, arg2)
|
||||
}
|
||||
if specificReturn {
|
||||
return ret.result1, ret.result2
|
||||
}
|
||||
return fakeReturns.result1, fakeReturns.result2
|
||||
}
|
||||
|
||||
func (fake *FakeIngressStore) ListIngressCallCount() int {
|
||||
fake.listIngressMutex.RLock()
|
||||
defer fake.listIngressMutex.RUnlock()
|
||||
return len(fake.listIngressArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *FakeIngressStore) ListIngressCalls(stub func(context.Context, livekit.RoomName) ([]*livekit.IngressInfo, error)) {
|
||||
fake.listIngressMutex.Lock()
|
||||
defer fake.listIngressMutex.Unlock()
|
||||
fake.ListIngressStub = stub
|
||||
}
|
||||
|
||||
func (fake *FakeIngressStore) ListIngressArgsForCall(i int) (context.Context, livekit.RoomName) {
|
||||
fake.listIngressMutex.RLock()
|
||||
defer fake.listIngressMutex.RUnlock()
|
||||
argsForCall := fake.listIngressArgsForCall[i]
|
||||
return argsForCall.arg1, argsForCall.arg2
|
||||
}
|
||||
|
||||
func (fake *FakeIngressStore) ListIngressReturns(result1 []*livekit.IngressInfo, result2 error) {
|
||||
fake.listIngressMutex.Lock()
|
||||
defer fake.listIngressMutex.Unlock()
|
||||
fake.ListIngressStub = nil
|
||||
fake.listIngressReturns = struct {
|
||||
result1 []*livekit.IngressInfo
|
||||
result2 error
|
||||
}{result1, result2}
|
||||
}
|
||||
|
||||
func (fake *FakeIngressStore) ListIngressReturnsOnCall(i int, result1 []*livekit.IngressInfo, result2 error) {
|
||||
fake.listIngressMutex.Lock()
|
||||
defer fake.listIngressMutex.Unlock()
|
||||
fake.ListIngressStub = nil
|
||||
if fake.listIngressReturnsOnCall == nil {
|
||||
fake.listIngressReturnsOnCall = make(map[int]struct {
|
||||
result1 []*livekit.IngressInfo
|
||||
result2 error
|
||||
})
|
||||
}
|
||||
fake.listIngressReturnsOnCall[i] = struct {
|
||||
result1 []*livekit.IngressInfo
|
||||
result2 error
|
||||
}{result1, result2}
|
||||
}
|
||||
|
||||
func (fake *FakeIngressStore) LoadIngress(arg1 context.Context, arg2 string) (*livekit.IngressInfo, error) {
|
||||
fake.loadIngressMutex.Lock()
|
||||
ret, specificReturn := fake.loadIngressReturnsOnCall[len(fake.loadIngressArgsForCall)]
|
||||
fake.loadIngressArgsForCall = append(fake.loadIngressArgsForCall, struct {
|
||||
arg1 context.Context
|
||||
arg2 string
|
||||
}{arg1, arg2})
|
||||
stub := fake.LoadIngressStub
|
||||
fakeReturns := fake.loadIngressReturns
|
||||
fake.recordInvocation("LoadIngress", []interface{}{arg1, arg2})
|
||||
fake.loadIngressMutex.Unlock()
|
||||
if stub != nil {
|
||||
return stub(arg1, arg2)
|
||||
}
|
||||
if specificReturn {
|
||||
return ret.result1, ret.result2
|
||||
}
|
||||
return fakeReturns.result1, fakeReturns.result2
|
||||
}
|
||||
|
||||
func (fake *FakeIngressStore) LoadIngressCallCount() int {
|
||||
fake.loadIngressMutex.RLock()
|
||||
defer fake.loadIngressMutex.RUnlock()
|
||||
return len(fake.loadIngressArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *FakeIngressStore) LoadIngressCalls(stub func(context.Context, string) (*livekit.IngressInfo, error)) {
|
||||
fake.loadIngressMutex.Lock()
|
||||
defer fake.loadIngressMutex.Unlock()
|
||||
fake.LoadIngressStub = stub
|
||||
}
|
||||
|
||||
func (fake *FakeIngressStore) LoadIngressArgsForCall(i int) (context.Context, string) {
|
||||
fake.loadIngressMutex.RLock()
|
||||
defer fake.loadIngressMutex.RUnlock()
|
||||
argsForCall := fake.loadIngressArgsForCall[i]
|
||||
return argsForCall.arg1, argsForCall.arg2
|
||||
}
|
||||
|
||||
func (fake *FakeIngressStore) LoadIngressReturns(result1 *livekit.IngressInfo, result2 error) {
|
||||
fake.loadIngressMutex.Lock()
|
||||
defer fake.loadIngressMutex.Unlock()
|
||||
fake.LoadIngressStub = nil
|
||||
fake.loadIngressReturns = struct {
|
||||
result1 *livekit.IngressInfo
|
||||
result2 error
|
||||
}{result1, result2}
|
||||
}
|
||||
|
||||
func (fake *FakeIngressStore) LoadIngressReturnsOnCall(i int, result1 *livekit.IngressInfo, result2 error) {
|
||||
fake.loadIngressMutex.Lock()
|
||||
defer fake.loadIngressMutex.Unlock()
|
||||
fake.LoadIngressStub = nil
|
||||
if fake.loadIngressReturnsOnCall == nil {
|
||||
fake.loadIngressReturnsOnCall = make(map[int]struct {
|
||||
result1 *livekit.IngressInfo
|
||||
result2 error
|
||||
})
|
||||
}
|
||||
fake.loadIngressReturnsOnCall[i] = struct {
|
||||
result1 *livekit.IngressInfo
|
||||
result2 error
|
||||
}{result1, result2}
|
||||
}
|
||||
|
||||
func (fake *FakeIngressStore) LoadIngressFromStreamKey(arg1 context.Context, arg2 string) (*livekit.IngressInfo, error) {
|
||||
fake.loadIngressFromStreamKeyMutex.Lock()
|
||||
ret, specificReturn := fake.loadIngressFromStreamKeyReturnsOnCall[len(fake.loadIngressFromStreamKeyArgsForCall)]
|
||||
fake.loadIngressFromStreamKeyArgsForCall = append(fake.loadIngressFromStreamKeyArgsForCall, struct {
|
||||
arg1 context.Context
|
||||
arg2 string
|
||||
}{arg1, arg2})
|
||||
stub := fake.LoadIngressFromStreamKeyStub
|
||||
fakeReturns := fake.loadIngressFromStreamKeyReturns
|
||||
fake.recordInvocation("LoadIngressFromStreamKey", []interface{}{arg1, arg2})
|
||||
fake.loadIngressFromStreamKeyMutex.Unlock()
|
||||
if stub != nil {
|
||||
return stub(arg1, arg2)
|
||||
}
|
||||
if specificReturn {
|
||||
return ret.result1, ret.result2
|
||||
}
|
||||
return fakeReturns.result1, fakeReturns.result2
|
||||
}
|
||||
|
||||
func (fake *FakeIngressStore) LoadIngressFromStreamKeyCallCount() int {
|
||||
fake.loadIngressFromStreamKeyMutex.RLock()
|
||||
defer fake.loadIngressFromStreamKeyMutex.RUnlock()
|
||||
return len(fake.loadIngressFromStreamKeyArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *FakeIngressStore) LoadIngressFromStreamKeyCalls(stub func(context.Context, string) (*livekit.IngressInfo, error)) {
|
||||
fake.loadIngressFromStreamKeyMutex.Lock()
|
||||
defer fake.loadIngressFromStreamKeyMutex.Unlock()
|
||||
fake.LoadIngressFromStreamKeyStub = stub
|
||||
}
|
||||
|
||||
func (fake *FakeIngressStore) LoadIngressFromStreamKeyArgsForCall(i int) (context.Context, string) {
|
||||
fake.loadIngressFromStreamKeyMutex.RLock()
|
||||
defer fake.loadIngressFromStreamKeyMutex.RUnlock()
|
||||
argsForCall := fake.loadIngressFromStreamKeyArgsForCall[i]
|
||||
return argsForCall.arg1, argsForCall.arg2
|
||||
}
|
||||
|
||||
func (fake *FakeIngressStore) LoadIngressFromStreamKeyReturns(result1 *livekit.IngressInfo, result2 error) {
|
||||
fake.loadIngressFromStreamKeyMutex.Lock()
|
||||
defer fake.loadIngressFromStreamKeyMutex.Unlock()
|
||||
fake.LoadIngressFromStreamKeyStub = nil
|
||||
fake.loadIngressFromStreamKeyReturns = struct {
|
||||
result1 *livekit.IngressInfo
|
||||
result2 error
|
||||
}{result1, result2}
|
||||
}
|
||||
|
||||
func (fake *FakeIngressStore) LoadIngressFromStreamKeyReturnsOnCall(i int, result1 *livekit.IngressInfo, result2 error) {
|
||||
fake.loadIngressFromStreamKeyMutex.Lock()
|
||||
defer fake.loadIngressFromStreamKeyMutex.Unlock()
|
||||
fake.LoadIngressFromStreamKeyStub = nil
|
||||
if fake.loadIngressFromStreamKeyReturnsOnCall == nil {
|
||||
fake.loadIngressFromStreamKeyReturnsOnCall = make(map[int]struct {
|
||||
result1 *livekit.IngressInfo
|
||||
result2 error
|
||||
})
|
||||
}
|
||||
fake.loadIngressFromStreamKeyReturnsOnCall[i] = struct {
|
||||
result1 *livekit.IngressInfo
|
||||
result2 error
|
||||
}{result1, result2}
|
||||
}
|
||||
|
||||
func (fake *FakeIngressStore) StoreIngress(arg1 context.Context, arg2 *livekit.IngressInfo) error {
|
||||
fake.storeIngressMutex.Lock()
|
||||
ret, specificReturn := fake.storeIngressReturnsOnCall[len(fake.storeIngressArgsForCall)]
|
||||
fake.storeIngressArgsForCall = append(fake.storeIngressArgsForCall, struct {
|
||||
arg1 context.Context
|
||||
arg2 *livekit.IngressInfo
|
||||
}{arg1, arg2})
|
||||
stub := fake.StoreIngressStub
|
||||
fakeReturns := fake.storeIngressReturns
|
||||
fake.recordInvocation("StoreIngress", []interface{}{arg1, arg2})
|
||||
fake.storeIngressMutex.Unlock()
|
||||
if stub != nil {
|
||||
return stub(arg1, arg2)
|
||||
}
|
||||
if specificReturn {
|
||||
return ret.result1
|
||||
}
|
||||
return fakeReturns.result1
|
||||
}
|
||||
|
||||
func (fake *FakeIngressStore) StoreIngressCallCount() int {
|
||||
fake.storeIngressMutex.RLock()
|
||||
defer fake.storeIngressMutex.RUnlock()
|
||||
return len(fake.storeIngressArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *FakeIngressStore) StoreIngressCalls(stub func(context.Context, *livekit.IngressInfo) error) {
|
||||
fake.storeIngressMutex.Lock()
|
||||
defer fake.storeIngressMutex.Unlock()
|
||||
fake.StoreIngressStub = stub
|
||||
}
|
||||
|
||||
func (fake *FakeIngressStore) StoreIngressArgsForCall(i int) (context.Context, *livekit.IngressInfo) {
|
||||
fake.storeIngressMutex.RLock()
|
||||
defer fake.storeIngressMutex.RUnlock()
|
||||
argsForCall := fake.storeIngressArgsForCall[i]
|
||||
return argsForCall.arg1, argsForCall.arg2
|
||||
}
|
||||
|
||||
func (fake *FakeIngressStore) StoreIngressReturns(result1 error) {
|
||||
fake.storeIngressMutex.Lock()
|
||||
defer fake.storeIngressMutex.Unlock()
|
||||
fake.StoreIngressStub = nil
|
||||
fake.storeIngressReturns = struct {
|
||||
result1 error
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeIngressStore) StoreIngressReturnsOnCall(i int, result1 error) {
|
||||
fake.storeIngressMutex.Lock()
|
||||
defer fake.storeIngressMutex.Unlock()
|
||||
fake.StoreIngressStub = nil
|
||||
if fake.storeIngressReturnsOnCall == nil {
|
||||
fake.storeIngressReturnsOnCall = make(map[int]struct {
|
||||
result1 error
|
||||
})
|
||||
}
|
||||
fake.storeIngressReturnsOnCall[i] = struct {
|
||||
result1 error
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeIngressStore) UpdateIngress(arg1 context.Context, arg2 *livekit.IngressInfo) error {
|
||||
fake.updateIngressMutex.Lock()
|
||||
ret, specificReturn := fake.updateIngressReturnsOnCall[len(fake.updateIngressArgsForCall)]
|
||||
fake.updateIngressArgsForCall = append(fake.updateIngressArgsForCall, struct {
|
||||
arg1 context.Context
|
||||
arg2 *livekit.IngressInfo
|
||||
}{arg1, arg2})
|
||||
stub := fake.UpdateIngressStub
|
||||
fakeReturns := fake.updateIngressReturns
|
||||
fake.recordInvocation("UpdateIngress", []interface{}{arg1, arg2})
|
||||
fake.updateIngressMutex.Unlock()
|
||||
if stub != nil {
|
||||
return stub(arg1, arg2)
|
||||
}
|
||||
if specificReturn {
|
||||
return ret.result1
|
||||
}
|
||||
return fakeReturns.result1
|
||||
}
|
||||
|
||||
func (fake *FakeIngressStore) UpdateIngressCallCount() int {
|
||||
fake.updateIngressMutex.RLock()
|
||||
defer fake.updateIngressMutex.RUnlock()
|
||||
return len(fake.updateIngressArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *FakeIngressStore) UpdateIngressCalls(stub func(context.Context, *livekit.IngressInfo) error) {
|
||||
fake.updateIngressMutex.Lock()
|
||||
defer fake.updateIngressMutex.Unlock()
|
||||
fake.UpdateIngressStub = stub
|
||||
}
|
||||
|
||||
func (fake *FakeIngressStore) UpdateIngressArgsForCall(i int) (context.Context, *livekit.IngressInfo) {
|
||||
fake.updateIngressMutex.RLock()
|
||||
defer fake.updateIngressMutex.RUnlock()
|
||||
argsForCall := fake.updateIngressArgsForCall[i]
|
||||
return argsForCall.arg1, argsForCall.arg2
|
||||
}
|
||||
|
||||
func (fake *FakeIngressStore) UpdateIngressReturns(result1 error) {
|
||||
fake.updateIngressMutex.Lock()
|
||||
defer fake.updateIngressMutex.Unlock()
|
||||
fake.UpdateIngressStub = nil
|
||||
fake.updateIngressReturns = struct {
|
||||
result1 error
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeIngressStore) UpdateIngressReturnsOnCall(i int, result1 error) {
|
||||
fake.updateIngressMutex.Lock()
|
||||
defer fake.updateIngressMutex.Unlock()
|
||||
fake.UpdateIngressStub = nil
|
||||
if fake.updateIngressReturnsOnCall == nil {
|
||||
fake.updateIngressReturnsOnCall = make(map[int]struct {
|
||||
result1 error
|
||||
})
|
||||
}
|
||||
fake.updateIngressReturnsOnCall[i] = struct {
|
||||
result1 error
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeIngressStore) UpdateIngressState(arg1 context.Context, arg2 string, arg3 *livekit.IngressState) error {
|
||||
fake.updateIngressStateMutex.Lock()
|
||||
ret, specificReturn := fake.updateIngressStateReturnsOnCall[len(fake.updateIngressStateArgsForCall)]
|
||||
fake.updateIngressStateArgsForCall = append(fake.updateIngressStateArgsForCall, struct {
|
||||
arg1 context.Context
|
||||
arg2 string
|
||||
arg3 *livekit.IngressState
|
||||
}{arg1, arg2, arg3})
|
||||
stub := fake.UpdateIngressStateStub
|
||||
fakeReturns := fake.updateIngressStateReturns
|
||||
fake.recordInvocation("UpdateIngressState", []interface{}{arg1, arg2, arg3})
|
||||
fake.updateIngressStateMutex.Unlock()
|
||||
if stub != nil {
|
||||
return stub(arg1, arg2, arg3)
|
||||
}
|
||||
if specificReturn {
|
||||
return ret.result1
|
||||
}
|
||||
return fakeReturns.result1
|
||||
}
|
||||
|
||||
func (fake *FakeIngressStore) UpdateIngressStateCallCount() int {
|
||||
fake.updateIngressStateMutex.RLock()
|
||||
defer fake.updateIngressStateMutex.RUnlock()
|
||||
return len(fake.updateIngressStateArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *FakeIngressStore) UpdateIngressStateCalls(stub func(context.Context, string, *livekit.IngressState) error) {
|
||||
fake.updateIngressStateMutex.Lock()
|
||||
defer fake.updateIngressStateMutex.Unlock()
|
||||
fake.UpdateIngressStateStub = stub
|
||||
}
|
||||
|
||||
func (fake *FakeIngressStore) UpdateIngressStateArgsForCall(i int) (context.Context, string, *livekit.IngressState) {
|
||||
fake.updateIngressStateMutex.RLock()
|
||||
defer fake.updateIngressStateMutex.RUnlock()
|
||||
argsForCall := fake.updateIngressStateArgsForCall[i]
|
||||
return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3
|
||||
}
|
||||
|
||||
func (fake *FakeIngressStore) UpdateIngressStateReturns(result1 error) {
|
||||
fake.updateIngressStateMutex.Lock()
|
||||
defer fake.updateIngressStateMutex.Unlock()
|
||||
fake.UpdateIngressStateStub = nil
|
||||
fake.updateIngressStateReturns = struct {
|
||||
result1 error
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeIngressStore) UpdateIngressStateReturnsOnCall(i int, result1 error) {
|
||||
fake.updateIngressStateMutex.Lock()
|
||||
defer fake.updateIngressStateMutex.Unlock()
|
||||
fake.UpdateIngressStateStub = nil
|
||||
if fake.updateIngressStateReturnsOnCall == nil {
|
||||
fake.updateIngressStateReturnsOnCall = make(map[int]struct {
|
||||
result1 error
|
||||
})
|
||||
}
|
||||
fake.updateIngressStateReturnsOnCall[i] = struct {
|
||||
result1 error
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeIngressStore) Invocations() map[string][][]interface{} {
|
||||
fake.invocationsMutex.RLock()
|
||||
defer fake.invocationsMutex.RUnlock()
|
||||
fake.deleteIngressMutex.RLock()
|
||||
defer fake.deleteIngressMutex.RUnlock()
|
||||
fake.listIngressMutex.RLock()
|
||||
defer fake.listIngressMutex.RUnlock()
|
||||
fake.loadIngressMutex.RLock()
|
||||
defer fake.loadIngressMutex.RUnlock()
|
||||
fake.loadIngressFromStreamKeyMutex.RLock()
|
||||
defer fake.loadIngressFromStreamKeyMutex.RUnlock()
|
||||
fake.storeIngressMutex.RLock()
|
||||
defer fake.storeIngressMutex.RUnlock()
|
||||
fake.updateIngressMutex.RLock()
|
||||
defer fake.updateIngressMutex.RUnlock()
|
||||
fake.updateIngressStateMutex.RLock()
|
||||
defer fake.updateIngressStateMutex.RUnlock()
|
||||
copiedInvocations := map[string][][]interface{}{}
|
||||
for key, value := range fake.invocations {
|
||||
copiedInvocations[key] = value
|
||||
}
|
||||
return copiedInvocations
|
||||
}
|
||||
|
||||
func (fake *FakeIngressStore) 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 _ service.IngressStore = new(FakeIngressStore)
|
||||
@@ -0,0 +1,446 @@
|
||||
// Code generated by counterfeiter. DO NOT EDIT.
|
||||
package servicefakes
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
|
||||
"github.com/livekit/livekit-server/pkg/service"
|
||||
"github.com/livekit/protocol/livekit"
|
||||
"github.com/livekit/protocol/rpc"
|
||||
"google.golang.org/protobuf/types/known/emptypb"
|
||||
)
|
||||
|
||||
type FakeIOClient struct {
|
||||
CreateEgressStub func(context.Context, *livekit.EgressInfo) (*emptypb.Empty, error)
|
||||
createEgressMutex sync.RWMutex
|
||||
createEgressArgsForCall []struct {
|
||||
arg1 context.Context
|
||||
arg2 *livekit.EgressInfo
|
||||
}
|
||||
createEgressReturns struct {
|
||||
result1 *emptypb.Empty
|
||||
result2 error
|
||||
}
|
||||
createEgressReturnsOnCall map[int]struct {
|
||||
result1 *emptypb.Empty
|
||||
result2 error
|
||||
}
|
||||
CreateIngressStub func(context.Context, *livekit.IngressInfo) (*emptypb.Empty, error)
|
||||
createIngressMutex sync.RWMutex
|
||||
createIngressArgsForCall []struct {
|
||||
arg1 context.Context
|
||||
arg2 *livekit.IngressInfo
|
||||
}
|
||||
createIngressReturns struct {
|
||||
result1 *emptypb.Empty
|
||||
result2 error
|
||||
}
|
||||
createIngressReturnsOnCall map[int]struct {
|
||||
result1 *emptypb.Empty
|
||||
result2 error
|
||||
}
|
||||
GetEgressStub func(context.Context, *rpc.GetEgressRequest) (*livekit.EgressInfo, error)
|
||||
getEgressMutex sync.RWMutex
|
||||
getEgressArgsForCall []struct {
|
||||
arg1 context.Context
|
||||
arg2 *rpc.GetEgressRequest
|
||||
}
|
||||
getEgressReturns struct {
|
||||
result1 *livekit.EgressInfo
|
||||
result2 error
|
||||
}
|
||||
getEgressReturnsOnCall map[int]struct {
|
||||
result1 *livekit.EgressInfo
|
||||
result2 error
|
||||
}
|
||||
ListEgressStub func(context.Context, *livekit.ListEgressRequest) (*livekit.ListEgressResponse, error)
|
||||
listEgressMutex sync.RWMutex
|
||||
listEgressArgsForCall []struct {
|
||||
arg1 context.Context
|
||||
arg2 *livekit.ListEgressRequest
|
||||
}
|
||||
listEgressReturns struct {
|
||||
result1 *livekit.ListEgressResponse
|
||||
result2 error
|
||||
}
|
||||
listEgressReturnsOnCall map[int]struct {
|
||||
result1 *livekit.ListEgressResponse
|
||||
result2 error
|
||||
}
|
||||
UpdateIngressStateStub func(context.Context, *rpc.UpdateIngressStateRequest) (*emptypb.Empty, error)
|
||||
updateIngressStateMutex sync.RWMutex
|
||||
updateIngressStateArgsForCall []struct {
|
||||
arg1 context.Context
|
||||
arg2 *rpc.UpdateIngressStateRequest
|
||||
}
|
||||
updateIngressStateReturns struct {
|
||||
result1 *emptypb.Empty
|
||||
result2 error
|
||||
}
|
||||
updateIngressStateReturnsOnCall map[int]struct {
|
||||
result1 *emptypb.Empty
|
||||
result2 error
|
||||
}
|
||||
invocations map[string][][]interface{}
|
||||
invocationsMutex sync.RWMutex
|
||||
}
|
||||
|
||||
func (fake *FakeIOClient) CreateEgress(arg1 context.Context, arg2 *livekit.EgressInfo) (*emptypb.Empty, error) {
|
||||
fake.createEgressMutex.Lock()
|
||||
ret, specificReturn := fake.createEgressReturnsOnCall[len(fake.createEgressArgsForCall)]
|
||||
fake.createEgressArgsForCall = append(fake.createEgressArgsForCall, struct {
|
||||
arg1 context.Context
|
||||
arg2 *livekit.EgressInfo
|
||||
}{arg1, arg2})
|
||||
stub := fake.CreateEgressStub
|
||||
fakeReturns := fake.createEgressReturns
|
||||
fake.recordInvocation("CreateEgress", []interface{}{arg1, arg2})
|
||||
fake.createEgressMutex.Unlock()
|
||||
if stub != nil {
|
||||
return stub(arg1, arg2)
|
||||
}
|
||||
if specificReturn {
|
||||
return ret.result1, ret.result2
|
||||
}
|
||||
return fakeReturns.result1, fakeReturns.result2
|
||||
}
|
||||
|
||||
func (fake *FakeIOClient) CreateEgressCallCount() int {
|
||||
fake.createEgressMutex.RLock()
|
||||
defer fake.createEgressMutex.RUnlock()
|
||||
return len(fake.createEgressArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *FakeIOClient) CreateEgressCalls(stub func(context.Context, *livekit.EgressInfo) (*emptypb.Empty, error)) {
|
||||
fake.createEgressMutex.Lock()
|
||||
defer fake.createEgressMutex.Unlock()
|
||||
fake.CreateEgressStub = stub
|
||||
}
|
||||
|
||||
func (fake *FakeIOClient) CreateEgressArgsForCall(i int) (context.Context, *livekit.EgressInfo) {
|
||||
fake.createEgressMutex.RLock()
|
||||
defer fake.createEgressMutex.RUnlock()
|
||||
argsForCall := fake.createEgressArgsForCall[i]
|
||||
return argsForCall.arg1, argsForCall.arg2
|
||||
}
|
||||
|
||||
func (fake *FakeIOClient) CreateEgressReturns(result1 *emptypb.Empty, result2 error) {
|
||||
fake.createEgressMutex.Lock()
|
||||
defer fake.createEgressMutex.Unlock()
|
||||
fake.CreateEgressStub = nil
|
||||
fake.createEgressReturns = struct {
|
||||
result1 *emptypb.Empty
|
||||
result2 error
|
||||
}{result1, result2}
|
||||
}
|
||||
|
||||
func (fake *FakeIOClient) CreateEgressReturnsOnCall(i int, result1 *emptypb.Empty, result2 error) {
|
||||
fake.createEgressMutex.Lock()
|
||||
defer fake.createEgressMutex.Unlock()
|
||||
fake.CreateEgressStub = nil
|
||||
if fake.createEgressReturnsOnCall == nil {
|
||||
fake.createEgressReturnsOnCall = make(map[int]struct {
|
||||
result1 *emptypb.Empty
|
||||
result2 error
|
||||
})
|
||||
}
|
||||
fake.createEgressReturnsOnCall[i] = struct {
|
||||
result1 *emptypb.Empty
|
||||
result2 error
|
||||
}{result1, result2}
|
||||
}
|
||||
|
||||
func (fake *FakeIOClient) CreateIngress(arg1 context.Context, arg2 *livekit.IngressInfo) (*emptypb.Empty, error) {
|
||||
fake.createIngressMutex.Lock()
|
||||
ret, specificReturn := fake.createIngressReturnsOnCall[len(fake.createIngressArgsForCall)]
|
||||
fake.createIngressArgsForCall = append(fake.createIngressArgsForCall, struct {
|
||||
arg1 context.Context
|
||||
arg2 *livekit.IngressInfo
|
||||
}{arg1, arg2})
|
||||
stub := fake.CreateIngressStub
|
||||
fakeReturns := fake.createIngressReturns
|
||||
fake.recordInvocation("CreateIngress", []interface{}{arg1, arg2})
|
||||
fake.createIngressMutex.Unlock()
|
||||
if stub != nil {
|
||||
return stub(arg1, arg2)
|
||||
}
|
||||
if specificReturn {
|
||||
return ret.result1, ret.result2
|
||||
}
|
||||
return fakeReturns.result1, fakeReturns.result2
|
||||
}
|
||||
|
||||
func (fake *FakeIOClient) CreateIngressCallCount() int {
|
||||
fake.createIngressMutex.RLock()
|
||||
defer fake.createIngressMutex.RUnlock()
|
||||
return len(fake.createIngressArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *FakeIOClient) CreateIngressCalls(stub func(context.Context, *livekit.IngressInfo) (*emptypb.Empty, error)) {
|
||||
fake.createIngressMutex.Lock()
|
||||
defer fake.createIngressMutex.Unlock()
|
||||
fake.CreateIngressStub = stub
|
||||
}
|
||||
|
||||
func (fake *FakeIOClient) CreateIngressArgsForCall(i int) (context.Context, *livekit.IngressInfo) {
|
||||
fake.createIngressMutex.RLock()
|
||||
defer fake.createIngressMutex.RUnlock()
|
||||
argsForCall := fake.createIngressArgsForCall[i]
|
||||
return argsForCall.arg1, argsForCall.arg2
|
||||
}
|
||||
|
||||
func (fake *FakeIOClient) CreateIngressReturns(result1 *emptypb.Empty, result2 error) {
|
||||
fake.createIngressMutex.Lock()
|
||||
defer fake.createIngressMutex.Unlock()
|
||||
fake.CreateIngressStub = nil
|
||||
fake.createIngressReturns = struct {
|
||||
result1 *emptypb.Empty
|
||||
result2 error
|
||||
}{result1, result2}
|
||||
}
|
||||
|
||||
func (fake *FakeIOClient) CreateIngressReturnsOnCall(i int, result1 *emptypb.Empty, result2 error) {
|
||||
fake.createIngressMutex.Lock()
|
||||
defer fake.createIngressMutex.Unlock()
|
||||
fake.CreateIngressStub = nil
|
||||
if fake.createIngressReturnsOnCall == nil {
|
||||
fake.createIngressReturnsOnCall = make(map[int]struct {
|
||||
result1 *emptypb.Empty
|
||||
result2 error
|
||||
})
|
||||
}
|
||||
fake.createIngressReturnsOnCall[i] = struct {
|
||||
result1 *emptypb.Empty
|
||||
result2 error
|
||||
}{result1, result2}
|
||||
}
|
||||
|
||||
func (fake *FakeIOClient) GetEgress(arg1 context.Context, arg2 *rpc.GetEgressRequest) (*livekit.EgressInfo, error) {
|
||||
fake.getEgressMutex.Lock()
|
||||
ret, specificReturn := fake.getEgressReturnsOnCall[len(fake.getEgressArgsForCall)]
|
||||
fake.getEgressArgsForCall = append(fake.getEgressArgsForCall, struct {
|
||||
arg1 context.Context
|
||||
arg2 *rpc.GetEgressRequest
|
||||
}{arg1, arg2})
|
||||
stub := fake.GetEgressStub
|
||||
fakeReturns := fake.getEgressReturns
|
||||
fake.recordInvocation("GetEgress", []interface{}{arg1, arg2})
|
||||
fake.getEgressMutex.Unlock()
|
||||
if stub != nil {
|
||||
return stub(arg1, arg2)
|
||||
}
|
||||
if specificReturn {
|
||||
return ret.result1, ret.result2
|
||||
}
|
||||
return fakeReturns.result1, fakeReturns.result2
|
||||
}
|
||||
|
||||
func (fake *FakeIOClient) GetEgressCallCount() int {
|
||||
fake.getEgressMutex.RLock()
|
||||
defer fake.getEgressMutex.RUnlock()
|
||||
return len(fake.getEgressArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *FakeIOClient) GetEgressCalls(stub func(context.Context, *rpc.GetEgressRequest) (*livekit.EgressInfo, error)) {
|
||||
fake.getEgressMutex.Lock()
|
||||
defer fake.getEgressMutex.Unlock()
|
||||
fake.GetEgressStub = stub
|
||||
}
|
||||
|
||||
func (fake *FakeIOClient) GetEgressArgsForCall(i int) (context.Context, *rpc.GetEgressRequest) {
|
||||
fake.getEgressMutex.RLock()
|
||||
defer fake.getEgressMutex.RUnlock()
|
||||
argsForCall := fake.getEgressArgsForCall[i]
|
||||
return argsForCall.arg1, argsForCall.arg2
|
||||
}
|
||||
|
||||
func (fake *FakeIOClient) GetEgressReturns(result1 *livekit.EgressInfo, result2 error) {
|
||||
fake.getEgressMutex.Lock()
|
||||
defer fake.getEgressMutex.Unlock()
|
||||
fake.GetEgressStub = nil
|
||||
fake.getEgressReturns = struct {
|
||||
result1 *livekit.EgressInfo
|
||||
result2 error
|
||||
}{result1, result2}
|
||||
}
|
||||
|
||||
func (fake *FakeIOClient) GetEgressReturnsOnCall(i int, result1 *livekit.EgressInfo, result2 error) {
|
||||
fake.getEgressMutex.Lock()
|
||||
defer fake.getEgressMutex.Unlock()
|
||||
fake.GetEgressStub = nil
|
||||
if fake.getEgressReturnsOnCall == nil {
|
||||
fake.getEgressReturnsOnCall = make(map[int]struct {
|
||||
result1 *livekit.EgressInfo
|
||||
result2 error
|
||||
})
|
||||
}
|
||||
fake.getEgressReturnsOnCall[i] = struct {
|
||||
result1 *livekit.EgressInfo
|
||||
result2 error
|
||||
}{result1, result2}
|
||||
}
|
||||
|
||||
func (fake *FakeIOClient) ListEgress(arg1 context.Context, arg2 *livekit.ListEgressRequest) (*livekit.ListEgressResponse, error) {
|
||||
fake.listEgressMutex.Lock()
|
||||
ret, specificReturn := fake.listEgressReturnsOnCall[len(fake.listEgressArgsForCall)]
|
||||
fake.listEgressArgsForCall = append(fake.listEgressArgsForCall, struct {
|
||||
arg1 context.Context
|
||||
arg2 *livekit.ListEgressRequest
|
||||
}{arg1, arg2})
|
||||
stub := fake.ListEgressStub
|
||||
fakeReturns := fake.listEgressReturns
|
||||
fake.recordInvocation("ListEgress", []interface{}{arg1, arg2})
|
||||
fake.listEgressMutex.Unlock()
|
||||
if stub != nil {
|
||||
return stub(arg1, arg2)
|
||||
}
|
||||
if specificReturn {
|
||||
return ret.result1, ret.result2
|
||||
}
|
||||
return fakeReturns.result1, fakeReturns.result2
|
||||
}
|
||||
|
||||
func (fake *FakeIOClient) ListEgressCallCount() int {
|
||||
fake.listEgressMutex.RLock()
|
||||
defer fake.listEgressMutex.RUnlock()
|
||||
return len(fake.listEgressArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *FakeIOClient) ListEgressCalls(stub func(context.Context, *livekit.ListEgressRequest) (*livekit.ListEgressResponse, error)) {
|
||||
fake.listEgressMutex.Lock()
|
||||
defer fake.listEgressMutex.Unlock()
|
||||
fake.ListEgressStub = stub
|
||||
}
|
||||
|
||||
func (fake *FakeIOClient) ListEgressArgsForCall(i int) (context.Context, *livekit.ListEgressRequest) {
|
||||
fake.listEgressMutex.RLock()
|
||||
defer fake.listEgressMutex.RUnlock()
|
||||
argsForCall := fake.listEgressArgsForCall[i]
|
||||
return argsForCall.arg1, argsForCall.arg2
|
||||
}
|
||||
|
||||
func (fake *FakeIOClient) ListEgressReturns(result1 *livekit.ListEgressResponse, result2 error) {
|
||||
fake.listEgressMutex.Lock()
|
||||
defer fake.listEgressMutex.Unlock()
|
||||
fake.ListEgressStub = nil
|
||||
fake.listEgressReturns = struct {
|
||||
result1 *livekit.ListEgressResponse
|
||||
result2 error
|
||||
}{result1, result2}
|
||||
}
|
||||
|
||||
func (fake *FakeIOClient) ListEgressReturnsOnCall(i int, result1 *livekit.ListEgressResponse, result2 error) {
|
||||
fake.listEgressMutex.Lock()
|
||||
defer fake.listEgressMutex.Unlock()
|
||||
fake.ListEgressStub = nil
|
||||
if fake.listEgressReturnsOnCall == nil {
|
||||
fake.listEgressReturnsOnCall = make(map[int]struct {
|
||||
result1 *livekit.ListEgressResponse
|
||||
result2 error
|
||||
})
|
||||
}
|
||||
fake.listEgressReturnsOnCall[i] = struct {
|
||||
result1 *livekit.ListEgressResponse
|
||||
result2 error
|
||||
}{result1, result2}
|
||||
}
|
||||
|
||||
func (fake *FakeIOClient) UpdateIngressState(arg1 context.Context, arg2 *rpc.UpdateIngressStateRequest) (*emptypb.Empty, error) {
|
||||
fake.updateIngressStateMutex.Lock()
|
||||
ret, specificReturn := fake.updateIngressStateReturnsOnCall[len(fake.updateIngressStateArgsForCall)]
|
||||
fake.updateIngressStateArgsForCall = append(fake.updateIngressStateArgsForCall, struct {
|
||||
arg1 context.Context
|
||||
arg2 *rpc.UpdateIngressStateRequest
|
||||
}{arg1, arg2})
|
||||
stub := fake.UpdateIngressStateStub
|
||||
fakeReturns := fake.updateIngressStateReturns
|
||||
fake.recordInvocation("UpdateIngressState", []interface{}{arg1, arg2})
|
||||
fake.updateIngressStateMutex.Unlock()
|
||||
if stub != nil {
|
||||
return stub(arg1, arg2)
|
||||
}
|
||||
if specificReturn {
|
||||
return ret.result1, ret.result2
|
||||
}
|
||||
return fakeReturns.result1, fakeReturns.result2
|
||||
}
|
||||
|
||||
func (fake *FakeIOClient) UpdateIngressStateCallCount() int {
|
||||
fake.updateIngressStateMutex.RLock()
|
||||
defer fake.updateIngressStateMutex.RUnlock()
|
||||
return len(fake.updateIngressStateArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *FakeIOClient) UpdateIngressStateCalls(stub func(context.Context, *rpc.UpdateIngressStateRequest) (*emptypb.Empty, error)) {
|
||||
fake.updateIngressStateMutex.Lock()
|
||||
defer fake.updateIngressStateMutex.Unlock()
|
||||
fake.UpdateIngressStateStub = stub
|
||||
}
|
||||
|
||||
func (fake *FakeIOClient) UpdateIngressStateArgsForCall(i int) (context.Context, *rpc.UpdateIngressStateRequest) {
|
||||
fake.updateIngressStateMutex.RLock()
|
||||
defer fake.updateIngressStateMutex.RUnlock()
|
||||
argsForCall := fake.updateIngressStateArgsForCall[i]
|
||||
return argsForCall.arg1, argsForCall.arg2
|
||||
}
|
||||
|
||||
func (fake *FakeIOClient) UpdateIngressStateReturns(result1 *emptypb.Empty, result2 error) {
|
||||
fake.updateIngressStateMutex.Lock()
|
||||
defer fake.updateIngressStateMutex.Unlock()
|
||||
fake.UpdateIngressStateStub = nil
|
||||
fake.updateIngressStateReturns = struct {
|
||||
result1 *emptypb.Empty
|
||||
result2 error
|
||||
}{result1, result2}
|
||||
}
|
||||
|
||||
func (fake *FakeIOClient) UpdateIngressStateReturnsOnCall(i int, result1 *emptypb.Empty, result2 error) {
|
||||
fake.updateIngressStateMutex.Lock()
|
||||
defer fake.updateIngressStateMutex.Unlock()
|
||||
fake.UpdateIngressStateStub = nil
|
||||
if fake.updateIngressStateReturnsOnCall == nil {
|
||||
fake.updateIngressStateReturnsOnCall = make(map[int]struct {
|
||||
result1 *emptypb.Empty
|
||||
result2 error
|
||||
})
|
||||
}
|
||||
fake.updateIngressStateReturnsOnCall[i] = struct {
|
||||
result1 *emptypb.Empty
|
||||
result2 error
|
||||
}{result1, result2}
|
||||
}
|
||||
|
||||
func (fake *FakeIOClient) Invocations() map[string][][]interface{} {
|
||||
fake.invocationsMutex.RLock()
|
||||
defer fake.invocationsMutex.RUnlock()
|
||||
fake.createEgressMutex.RLock()
|
||||
defer fake.createEgressMutex.RUnlock()
|
||||
fake.createIngressMutex.RLock()
|
||||
defer fake.createIngressMutex.RUnlock()
|
||||
fake.getEgressMutex.RLock()
|
||||
defer fake.getEgressMutex.RUnlock()
|
||||
fake.listEgressMutex.RLock()
|
||||
defer fake.listEgressMutex.RUnlock()
|
||||
fake.updateIngressStateMutex.RLock()
|
||||
defer fake.updateIngressStateMutex.RUnlock()
|
||||
copiedInvocations := map[string][][]interface{}{}
|
||||
for key, value := range fake.invocations {
|
||||
copiedInvocations[key] = value
|
||||
}
|
||||
return copiedInvocations
|
||||
}
|
||||
|
||||
func (fake *FakeIOClient) 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 _ service.IOClient = new(FakeIOClient)
|
||||
@@ -0,0 +1,849 @@
|
||||
// Code generated by counterfeiter. DO NOT EDIT.
|
||||
package servicefakes
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/livekit/livekit-server/pkg/service"
|
||||
"github.com/livekit/protocol/livekit"
|
||||
)
|
||||
|
||||
type FakeObjectStore struct {
|
||||
DeleteParticipantStub func(context.Context, livekit.RoomName, livekit.ParticipantIdentity) error
|
||||
deleteParticipantMutex sync.RWMutex
|
||||
deleteParticipantArgsForCall []struct {
|
||||
arg1 context.Context
|
||||
arg2 livekit.RoomName
|
||||
arg3 livekit.ParticipantIdentity
|
||||
}
|
||||
deleteParticipantReturns struct {
|
||||
result1 error
|
||||
}
|
||||
deleteParticipantReturnsOnCall map[int]struct {
|
||||
result1 error
|
||||
}
|
||||
DeleteRoomStub func(context.Context, livekit.RoomName) error
|
||||
deleteRoomMutex sync.RWMutex
|
||||
deleteRoomArgsForCall []struct {
|
||||
arg1 context.Context
|
||||
arg2 livekit.RoomName
|
||||
}
|
||||
deleteRoomReturns struct {
|
||||
result1 error
|
||||
}
|
||||
deleteRoomReturnsOnCall map[int]struct {
|
||||
result1 error
|
||||
}
|
||||
ListParticipantsStub func(context.Context, livekit.RoomName) ([]*livekit.ParticipantInfo, error)
|
||||
listParticipantsMutex sync.RWMutex
|
||||
listParticipantsArgsForCall []struct {
|
||||
arg1 context.Context
|
||||
arg2 livekit.RoomName
|
||||
}
|
||||
listParticipantsReturns struct {
|
||||
result1 []*livekit.ParticipantInfo
|
||||
result2 error
|
||||
}
|
||||
listParticipantsReturnsOnCall map[int]struct {
|
||||
result1 []*livekit.ParticipantInfo
|
||||
result2 error
|
||||
}
|
||||
ListRoomsStub func(context.Context, []livekit.RoomName) ([]*livekit.Room, error)
|
||||
listRoomsMutex sync.RWMutex
|
||||
listRoomsArgsForCall []struct {
|
||||
arg1 context.Context
|
||||
arg2 []livekit.RoomName
|
||||
}
|
||||
listRoomsReturns struct {
|
||||
result1 []*livekit.Room
|
||||
result2 error
|
||||
}
|
||||
listRoomsReturnsOnCall map[int]struct {
|
||||
result1 []*livekit.Room
|
||||
result2 error
|
||||
}
|
||||
LoadParticipantStub func(context.Context, livekit.RoomName, livekit.ParticipantIdentity) (*livekit.ParticipantInfo, error)
|
||||
loadParticipantMutex sync.RWMutex
|
||||
loadParticipantArgsForCall []struct {
|
||||
arg1 context.Context
|
||||
arg2 livekit.RoomName
|
||||
arg3 livekit.ParticipantIdentity
|
||||
}
|
||||
loadParticipantReturns struct {
|
||||
result1 *livekit.ParticipantInfo
|
||||
result2 error
|
||||
}
|
||||
loadParticipantReturnsOnCall map[int]struct {
|
||||
result1 *livekit.ParticipantInfo
|
||||
result2 error
|
||||
}
|
||||
LoadRoomStub func(context.Context, livekit.RoomName, bool) (*livekit.Room, *livekit.RoomInternal, error)
|
||||
loadRoomMutex sync.RWMutex
|
||||
loadRoomArgsForCall []struct {
|
||||
arg1 context.Context
|
||||
arg2 livekit.RoomName
|
||||
arg3 bool
|
||||
}
|
||||
loadRoomReturns struct {
|
||||
result1 *livekit.Room
|
||||
result2 *livekit.RoomInternal
|
||||
result3 error
|
||||
}
|
||||
loadRoomReturnsOnCall map[int]struct {
|
||||
result1 *livekit.Room
|
||||
result2 *livekit.RoomInternal
|
||||
result3 error
|
||||
}
|
||||
LockRoomStub func(context.Context, livekit.RoomName, time.Duration) (string, error)
|
||||
lockRoomMutex sync.RWMutex
|
||||
lockRoomArgsForCall []struct {
|
||||
arg1 context.Context
|
||||
arg2 livekit.RoomName
|
||||
arg3 time.Duration
|
||||
}
|
||||
lockRoomReturns struct {
|
||||
result1 string
|
||||
result2 error
|
||||
}
|
||||
lockRoomReturnsOnCall map[int]struct {
|
||||
result1 string
|
||||
result2 error
|
||||
}
|
||||
StoreParticipantStub func(context.Context, livekit.RoomName, *livekit.ParticipantInfo) error
|
||||
storeParticipantMutex sync.RWMutex
|
||||
storeParticipantArgsForCall []struct {
|
||||
arg1 context.Context
|
||||
arg2 livekit.RoomName
|
||||
arg3 *livekit.ParticipantInfo
|
||||
}
|
||||
storeParticipantReturns struct {
|
||||
result1 error
|
||||
}
|
||||
storeParticipantReturnsOnCall map[int]struct {
|
||||
result1 error
|
||||
}
|
||||
StoreRoomStub func(context.Context, *livekit.Room, *livekit.RoomInternal) error
|
||||
storeRoomMutex sync.RWMutex
|
||||
storeRoomArgsForCall []struct {
|
||||
arg1 context.Context
|
||||
arg2 *livekit.Room
|
||||
arg3 *livekit.RoomInternal
|
||||
}
|
||||
storeRoomReturns struct {
|
||||
result1 error
|
||||
}
|
||||
storeRoomReturnsOnCall map[int]struct {
|
||||
result1 error
|
||||
}
|
||||
UnlockRoomStub func(context.Context, livekit.RoomName, string) error
|
||||
unlockRoomMutex sync.RWMutex
|
||||
unlockRoomArgsForCall []struct {
|
||||
arg1 context.Context
|
||||
arg2 livekit.RoomName
|
||||
arg3 string
|
||||
}
|
||||
unlockRoomReturns struct {
|
||||
result1 error
|
||||
}
|
||||
unlockRoomReturnsOnCall map[int]struct {
|
||||
result1 error
|
||||
}
|
||||
invocations map[string][][]interface{}
|
||||
invocationsMutex sync.RWMutex
|
||||
}
|
||||
|
||||
func (fake *FakeObjectStore) DeleteParticipant(arg1 context.Context, arg2 livekit.RoomName, arg3 livekit.ParticipantIdentity) error {
|
||||
fake.deleteParticipantMutex.Lock()
|
||||
ret, specificReturn := fake.deleteParticipantReturnsOnCall[len(fake.deleteParticipantArgsForCall)]
|
||||
fake.deleteParticipantArgsForCall = append(fake.deleteParticipantArgsForCall, struct {
|
||||
arg1 context.Context
|
||||
arg2 livekit.RoomName
|
||||
arg3 livekit.ParticipantIdentity
|
||||
}{arg1, arg2, arg3})
|
||||
stub := fake.DeleteParticipantStub
|
||||
fakeReturns := fake.deleteParticipantReturns
|
||||
fake.recordInvocation("DeleteParticipant", []interface{}{arg1, arg2, arg3})
|
||||
fake.deleteParticipantMutex.Unlock()
|
||||
if stub != nil {
|
||||
return stub(arg1, arg2, arg3)
|
||||
}
|
||||
if specificReturn {
|
||||
return ret.result1
|
||||
}
|
||||
return fakeReturns.result1
|
||||
}
|
||||
|
||||
func (fake *FakeObjectStore) DeleteParticipantCallCount() int {
|
||||
fake.deleteParticipantMutex.RLock()
|
||||
defer fake.deleteParticipantMutex.RUnlock()
|
||||
return len(fake.deleteParticipantArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *FakeObjectStore) DeleteParticipantCalls(stub func(context.Context, livekit.RoomName, livekit.ParticipantIdentity) error) {
|
||||
fake.deleteParticipantMutex.Lock()
|
||||
defer fake.deleteParticipantMutex.Unlock()
|
||||
fake.DeleteParticipantStub = stub
|
||||
}
|
||||
|
||||
func (fake *FakeObjectStore) DeleteParticipantArgsForCall(i int) (context.Context, livekit.RoomName, livekit.ParticipantIdentity) {
|
||||
fake.deleteParticipantMutex.RLock()
|
||||
defer fake.deleteParticipantMutex.RUnlock()
|
||||
argsForCall := fake.deleteParticipantArgsForCall[i]
|
||||
return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3
|
||||
}
|
||||
|
||||
func (fake *FakeObjectStore) DeleteParticipantReturns(result1 error) {
|
||||
fake.deleteParticipantMutex.Lock()
|
||||
defer fake.deleteParticipantMutex.Unlock()
|
||||
fake.DeleteParticipantStub = nil
|
||||
fake.deleteParticipantReturns = struct {
|
||||
result1 error
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeObjectStore) DeleteParticipantReturnsOnCall(i int, result1 error) {
|
||||
fake.deleteParticipantMutex.Lock()
|
||||
defer fake.deleteParticipantMutex.Unlock()
|
||||
fake.DeleteParticipantStub = nil
|
||||
if fake.deleteParticipantReturnsOnCall == nil {
|
||||
fake.deleteParticipantReturnsOnCall = make(map[int]struct {
|
||||
result1 error
|
||||
})
|
||||
}
|
||||
fake.deleteParticipantReturnsOnCall[i] = struct {
|
||||
result1 error
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeObjectStore) DeleteRoom(arg1 context.Context, arg2 livekit.RoomName) error {
|
||||
fake.deleteRoomMutex.Lock()
|
||||
ret, specificReturn := fake.deleteRoomReturnsOnCall[len(fake.deleteRoomArgsForCall)]
|
||||
fake.deleteRoomArgsForCall = append(fake.deleteRoomArgsForCall, struct {
|
||||
arg1 context.Context
|
||||
arg2 livekit.RoomName
|
||||
}{arg1, arg2})
|
||||
stub := fake.DeleteRoomStub
|
||||
fakeReturns := fake.deleteRoomReturns
|
||||
fake.recordInvocation("DeleteRoom", []interface{}{arg1, arg2})
|
||||
fake.deleteRoomMutex.Unlock()
|
||||
if stub != nil {
|
||||
return stub(arg1, arg2)
|
||||
}
|
||||
if specificReturn {
|
||||
return ret.result1
|
||||
}
|
||||
return fakeReturns.result1
|
||||
}
|
||||
|
||||
func (fake *FakeObjectStore) DeleteRoomCallCount() int {
|
||||
fake.deleteRoomMutex.RLock()
|
||||
defer fake.deleteRoomMutex.RUnlock()
|
||||
return len(fake.deleteRoomArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *FakeObjectStore) DeleteRoomCalls(stub func(context.Context, livekit.RoomName) error) {
|
||||
fake.deleteRoomMutex.Lock()
|
||||
defer fake.deleteRoomMutex.Unlock()
|
||||
fake.DeleteRoomStub = stub
|
||||
}
|
||||
|
||||
func (fake *FakeObjectStore) DeleteRoomArgsForCall(i int) (context.Context, livekit.RoomName) {
|
||||
fake.deleteRoomMutex.RLock()
|
||||
defer fake.deleteRoomMutex.RUnlock()
|
||||
argsForCall := fake.deleteRoomArgsForCall[i]
|
||||
return argsForCall.arg1, argsForCall.arg2
|
||||
}
|
||||
|
||||
func (fake *FakeObjectStore) DeleteRoomReturns(result1 error) {
|
||||
fake.deleteRoomMutex.Lock()
|
||||
defer fake.deleteRoomMutex.Unlock()
|
||||
fake.DeleteRoomStub = nil
|
||||
fake.deleteRoomReturns = struct {
|
||||
result1 error
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeObjectStore) DeleteRoomReturnsOnCall(i int, result1 error) {
|
||||
fake.deleteRoomMutex.Lock()
|
||||
defer fake.deleteRoomMutex.Unlock()
|
||||
fake.DeleteRoomStub = nil
|
||||
if fake.deleteRoomReturnsOnCall == nil {
|
||||
fake.deleteRoomReturnsOnCall = make(map[int]struct {
|
||||
result1 error
|
||||
})
|
||||
}
|
||||
fake.deleteRoomReturnsOnCall[i] = struct {
|
||||
result1 error
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeObjectStore) ListParticipants(arg1 context.Context, arg2 livekit.RoomName) ([]*livekit.ParticipantInfo, error) {
|
||||
fake.listParticipantsMutex.Lock()
|
||||
ret, specificReturn := fake.listParticipantsReturnsOnCall[len(fake.listParticipantsArgsForCall)]
|
||||
fake.listParticipantsArgsForCall = append(fake.listParticipantsArgsForCall, struct {
|
||||
arg1 context.Context
|
||||
arg2 livekit.RoomName
|
||||
}{arg1, arg2})
|
||||
stub := fake.ListParticipantsStub
|
||||
fakeReturns := fake.listParticipantsReturns
|
||||
fake.recordInvocation("ListParticipants", []interface{}{arg1, arg2})
|
||||
fake.listParticipantsMutex.Unlock()
|
||||
if stub != nil {
|
||||
return stub(arg1, arg2)
|
||||
}
|
||||
if specificReturn {
|
||||
return ret.result1, ret.result2
|
||||
}
|
||||
return fakeReturns.result1, fakeReturns.result2
|
||||
}
|
||||
|
||||
func (fake *FakeObjectStore) ListParticipantsCallCount() int {
|
||||
fake.listParticipantsMutex.RLock()
|
||||
defer fake.listParticipantsMutex.RUnlock()
|
||||
return len(fake.listParticipantsArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *FakeObjectStore) ListParticipantsCalls(stub func(context.Context, livekit.RoomName) ([]*livekit.ParticipantInfo, error)) {
|
||||
fake.listParticipantsMutex.Lock()
|
||||
defer fake.listParticipantsMutex.Unlock()
|
||||
fake.ListParticipantsStub = stub
|
||||
}
|
||||
|
||||
func (fake *FakeObjectStore) ListParticipantsArgsForCall(i int) (context.Context, livekit.RoomName) {
|
||||
fake.listParticipantsMutex.RLock()
|
||||
defer fake.listParticipantsMutex.RUnlock()
|
||||
argsForCall := fake.listParticipantsArgsForCall[i]
|
||||
return argsForCall.arg1, argsForCall.arg2
|
||||
}
|
||||
|
||||
func (fake *FakeObjectStore) ListParticipantsReturns(result1 []*livekit.ParticipantInfo, result2 error) {
|
||||
fake.listParticipantsMutex.Lock()
|
||||
defer fake.listParticipantsMutex.Unlock()
|
||||
fake.ListParticipantsStub = nil
|
||||
fake.listParticipantsReturns = struct {
|
||||
result1 []*livekit.ParticipantInfo
|
||||
result2 error
|
||||
}{result1, result2}
|
||||
}
|
||||
|
||||
func (fake *FakeObjectStore) ListParticipantsReturnsOnCall(i int, result1 []*livekit.ParticipantInfo, result2 error) {
|
||||
fake.listParticipantsMutex.Lock()
|
||||
defer fake.listParticipantsMutex.Unlock()
|
||||
fake.ListParticipantsStub = nil
|
||||
if fake.listParticipantsReturnsOnCall == nil {
|
||||
fake.listParticipantsReturnsOnCall = make(map[int]struct {
|
||||
result1 []*livekit.ParticipantInfo
|
||||
result2 error
|
||||
})
|
||||
}
|
||||
fake.listParticipantsReturnsOnCall[i] = struct {
|
||||
result1 []*livekit.ParticipantInfo
|
||||
result2 error
|
||||
}{result1, result2}
|
||||
}
|
||||
|
||||
func (fake *FakeObjectStore) ListRooms(arg1 context.Context, arg2 []livekit.RoomName) ([]*livekit.Room, error) {
|
||||
var arg2Copy []livekit.RoomName
|
||||
if arg2 != nil {
|
||||
arg2Copy = make([]livekit.RoomName, len(arg2))
|
||||
copy(arg2Copy, arg2)
|
||||
}
|
||||
fake.listRoomsMutex.Lock()
|
||||
ret, specificReturn := fake.listRoomsReturnsOnCall[len(fake.listRoomsArgsForCall)]
|
||||
fake.listRoomsArgsForCall = append(fake.listRoomsArgsForCall, struct {
|
||||
arg1 context.Context
|
||||
arg2 []livekit.RoomName
|
||||
}{arg1, arg2Copy})
|
||||
stub := fake.ListRoomsStub
|
||||
fakeReturns := fake.listRoomsReturns
|
||||
fake.recordInvocation("ListRooms", []interface{}{arg1, arg2Copy})
|
||||
fake.listRoomsMutex.Unlock()
|
||||
if stub != nil {
|
||||
return stub(arg1, arg2)
|
||||
}
|
||||
if specificReturn {
|
||||
return ret.result1, ret.result2
|
||||
}
|
||||
return fakeReturns.result1, fakeReturns.result2
|
||||
}
|
||||
|
||||
func (fake *FakeObjectStore) ListRoomsCallCount() int {
|
||||
fake.listRoomsMutex.RLock()
|
||||
defer fake.listRoomsMutex.RUnlock()
|
||||
return len(fake.listRoomsArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *FakeObjectStore) ListRoomsCalls(stub func(context.Context, []livekit.RoomName) ([]*livekit.Room, error)) {
|
||||
fake.listRoomsMutex.Lock()
|
||||
defer fake.listRoomsMutex.Unlock()
|
||||
fake.ListRoomsStub = stub
|
||||
}
|
||||
|
||||
func (fake *FakeObjectStore) ListRoomsArgsForCall(i int) (context.Context, []livekit.RoomName) {
|
||||
fake.listRoomsMutex.RLock()
|
||||
defer fake.listRoomsMutex.RUnlock()
|
||||
argsForCall := fake.listRoomsArgsForCall[i]
|
||||
return argsForCall.arg1, argsForCall.arg2
|
||||
}
|
||||
|
||||
func (fake *FakeObjectStore) ListRoomsReturns(result1 []*livekit.Room, result2 error) {
|
||||
fake.listRoomsMutex.Lock()
|
||||
defer fake.listRoomsMutex.Unlock()
|
||||
fake.ListRoomsStub = nil
|
||||
fake.listRoomsReturns = struct {
|
||||
result1 []*livekit.Room
|
||||
result2 error
|
||||
}{result1, result2}
|
||||
}
|
||||
|
||||
func (fake *FakeObjectStore) ListRoomsReturnsOnCall(i int, result1 []*livekit.Room, result2 error) {
|
||||
fake.listRoomsMutex.Lock()
|
||||
defer fake.listRoomsMutex.Unlock()
|
||||
fake.ListRoomsStub = nil
|
||||
if fake.listRoomsReturnsOnCall == nil {
|
||||
fake.listRoomsReturnsOnCall = make(map[int]struct {
|
||||
result1 []*livekit.Room
|
||||
result2 error
|
||||
})
|
||||
}
|
||||
fake.listRoomsReturnsOnCall[i] = struct {
|
||||
result1 []*livekit.Room
|
||||
result2 error
|
||||
}{result1, result2}
|
||||
}
|
||||
|
||||
func (fake *FakeObjectStore) LoadParticipant(arg1 context.Context, arg2 livekit.RoomName, arg3 livekit.ParticipantIdentity) (*livekit.ParticipantInfo, error) {
|
||||
fake.loadParticipantMutex.Lock()
|
||||
ret, specificReturn := fake.loadParticipantReturnsOnCall[len(fake.loadParticipantArgsForCall)]
|
||||
fake.loadParticipantArgsForCall = append(fake.loadParticipantArgsForCall, struct {
|
||||
arg1 context.Context
|
||||
arg2 livekit.RoomName
|
||||
arg3 livekit.ParticipantIdentity
|
||||
}{arg1, arg2, arg3})
|
||||
stub := fake.LoadParticipantStub
|
||||
fakeReturns := fake.loadParticipantReturns
|
||||
fake.recordInvocation("LoadParticipant", []interface{}{arg1, arg2, arg3})
|
||||
fake.loadParticipantMutex.Unlock()
|
||||
if stub != nil {
|
||||
return stub(arg1, arg2, arg3)
|
||||
}
|
||||
if specificReturn {
|
||||
return ret.result1, ret.result2
|
||||
}
|
||||
return fakeReturns.result1, fakeReturns.result2
|
||||
}
|
||||
|
||||
func (fake *FakeObjectStore) LoadParticipantCallCount() int {
|
||||
fake.loadParticipantMutex.RLock()
|
||||
defer fake.loadParticipantMutex.RUnlock()
|
||||
return len(fake.loadParticipantArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *FakeObjectStore) LoadParticipantCalls(stub func(context.Context, livekit.RoomName, livekit.ParticipantIdentity) (*livekit.ParticipantInfo, error)) {
|
||||
fake.loadParticipantMutex.Lock()
|
||||
defer fake.loadParticipantMutex.Unlock()
|
||||
fake.LoadParticipantStub = stub
|
||||
}
|
||||
|
||||
func (fake *FakeObjectStore) LoadParticipantArgsForCall(i int) (context.Context, livekit.RoomName, livekit.ParticipantIdentity) {
|
||||
fake.loadParticipantMutex.RLock()
|
||||
defer fake.loadParticipantMutex.RUnlock()
|
||||
argsForCall := fake.loadParticipantArgsForCall[i]
|
||||
return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3
|
||||
}
|
||||
|
||||
func (fake *FakeObjectStore) LoadParticipantReturns(result1 *livekit.ParticipantInfo, result2 error) {
|
||||
fake.loadParticipantMutex.Lock()
|
||||
defer fake.loadParticipantMutex.Unlock()
|
||||
fake.LoadParticipantStub = nil
|
||||
fake.loadParticipantReturns = struct {
|
||||
result1 *livekit.ParticipantInfo
|
||||
result2 error
|
||||
}{result1, result2}
|
||||
}
|
||||
|
||||
func (fake *FakeObjectStore) LoadParticipantReturnsOnCall(i int, result1 *livekit.ParticipantInfo, result2 error) {
|
||||
fake.loadParticipantMutex.Lock()
|
||||
defer fake.loadParticipantMutex.Unlock()
|
||||
fake.LoadParticipantStub = nil
|
||||
if fake.loadParticipantReturnsOnCall == nil {
|
||||
fake.loadParticipantReturnsOnCall = make(map[int]struct {
|
||||
result1 *livekit.ParticipantInfo
|
||||
result2 error
|
||||
})
|
||||
}
|
||||
fake.loadParticipantReturnsOnCall[i] = struct {
|
||||
result1 *livekit.ParticipantInfo
|
||||
result2 error
|
||||
}{result1, result2}
|
||||
}
|
||||
|
||||
func (fake *FakeObjectStore) LoadRoom(arg1 context.Context, arg2 livekit.RoomName, arg3 bool) (*livekit.Room, *livekit.RoomInternal, error) {
|
||||
fake.loadRoomMutex.Lock()
|
||||
ret, specificReturn := fake.loadRoomReturnsOnCall[len(fake.loadRoomArgsForCall)]
|
||||
fake.loadRoomArgsForCall = append(fake.loadRoomArgsForCall, struct {
|
||||
arg1 context.Context
|
||||
arg2 livekit.RoomName
|
||||
arg3 bool
|
||||
}{arg1, arg2, arg3})
|
||||
stub := fake.LoadRoomStub
|
||||
fakeReturns := fake.loadRoomReturns
|
||||
fake.recordInvocation("LoadRoom", []interface{}{arg1, arg2, arg3})
|
||||
fake.loadRoomMutex.Unlock()
|
||||
if stub != nil {
|
||||
return stub(arg1, arg2, arg3)
|
||||
}
|
||||
if specificReturn {
|
||||
return ret.result1, ret.result2, ret.result3
|
||||
}
|
||||
return fakeReturns.result1, fakeReturns.result2, fakeReturns.result3
|
||||
}
|
||||
|
||||
func (fake *FakeObjectStore) LoadRoomCallCount() int {
|
||||
fake.loadRoomMutex.RLock()
|
||||
defer fake.loadRoomMutex.RUnlock()
|
||||
return len(fake.loadRoomArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *FakeObjectStore) LoadRoomCalls(stub func(context.Context, livekit.RoomName, bool) (*livekit.Room, *livekit.RoomInternal, error)) {
|
||||
fake.loadRoomMutex.Lock()
|
||||
defer fake.loadRoomMutex.Unlock()
|
||||
fake.LoadRoomStub = stub
|
||||
}
|
||||
|
||||
func (fake *FakeObjectStore) LoadRoomArgsForCall(i int) (context.Context, livekit.RoomName, bool) {
|
||||
fake.loadRoomMutex.RLock()
|
||||
defer fake.loadRoomMutex.RUnlock()
|
||||
argsForCall := fake.loadRoomArgsForCall[i]
|
||||
return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3
|
||||
}
|
||||
|
||||
func (fake *FakeObjectStore) LoadRoomReturns(result1 *livekit.Room, result2 *livekit.RoomInternal, result3 error) {
|
||||
fake.loadRoomMutex.Lock()
|
||||
defer fake.loadRoomMutex.Unlock()
|
||||
fake.LoadRoomStub = nil
|
||||
fake.loadRoomReturns = struct {
|
||||
result1 *livekit.Room
|
||||
result2 *livekit.RoomInternal
|
||||
result3 error
|
||||
}{result1, result2, result3}
|
||||
}
|
||||
|
||||
func (fake *FakeObjectStore) LoadRoomReturnsOnCall(i int, result1 *livekit.Room, result2 *livekit.RoomInternal, result3 error) {
|
||||
fake.loadRoomMutex.Lock()
|
||||
defer fake.loadRoomMutex.Unlock()
|
||||
fake.LoadRoomStub = nil
|
||||
if fake.loadRoomReturnsOnCall == nil {
|
||||
fake.loadRoomReturnsOnCall = make(map[int]struct {
|
||||
result1 *livekit.Room
|
||||
result2 *livekit.RoomInternal
|
||||
result3 error
|
||||
})
|
||||
}
|
||||
fake.loadRoomReturnsOnCall[i] = struct {
|
||||
result1 *livekit.Room
|
||||
result2 *livekit.RoomInternal
|
||||
result3 error
|
||||
}{result1, result2, result3}
|
||||
}
|
||||
|
||||
func (fake *FakeObjectStore) LockRoom(arg1 context.Context, arg2 livekit.RoomName, arg3 time.Duration) (string, error) {
|
||||
fake.lockRoomMutex.Lock()
|
||||
ret, specificReturn := fake.lockRoomReturnsOnCall[len(fake.lockRoomArgsForCall)]
|
||||
fake.lockRoomArgsForCall = append(fake.lockRoomArgsForCall, struct {
|
||||
arg1 context.Context
|
||||
arg2 livekit.RoomName
|
||||
arg3 time.Duration
|
||||
}{arg1, arg2, arg3})
|
||||
stub := fake.LockRoomStub
|
||||
fakeReturns := fake.lockRoomReturns
|
||||
fake.recordInvocation("LockRoom", []interface{}{arg1, arg2, arg3})
|
||||
fake.lockRoomMutex.Unlock()
|
||||
if stub != nil {
|
||||
return stub(arg1, arg2, arg3)
|
||||
}
|
||||
if specificReturn {
|
||||
return ret.result1, ret.result2
|
||||
}
|
||||
return fakeReturns.result1, fakeReturns.result2
|
||||
}
|
||||
|
||||
func (fake *FakeObjectStore) LockRoomCallCount() int {
|
||||
fake.lockRoomMutex.RLock()
|
||||
defer fake.lockRoomMutex.RUnlock()
|
||||
return len(fake.lockRoomArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *FakeObjectStore) LockRoomCalls(stub func(context.Context, livekit.RoomName, time.Duration) (string, error)) {
|
||||
fake.lockRoomMutex.Lock()
|
||||
defer fake.lockRoomMutex.Unlock()
|
||||
fake.LockRoomStub = stub
|
||||
}
|
||||
|
||||
func (fake *FakeObjectStore) LockRoomArgsForCall(i int) (context.Context, livekit.RoomName, time.Duration) {
|
||||
fake.lockRoomMutex.RLock()
|
||||
defer fake.lockRoomMutex.RUnlock()
|
||||
argsForCall := fake.lockRoomArgsForCall[i]
|
||||
return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3
|
||||
}
|
||||
|
||||
func (fake *FakeObjectStore) LockRoomReturns(result1 string, result2 error) {
|
||||
fake.lockRoomMutex.Lock()
|
||||
defer fake.lockRoomMutex.Unlock()
|
||||
fake.LockRoomStub = nil
|
||||
fake.lockRoomReturns = struct {
|
||||
result1 string
|
||||
result2 error
|
||||
}{result1, result2}
|
||||
}
|
||||
|
||||
func (fake *FakeObjectStore) LockRoomReturnsOnCall(i int, result1 string, result2 error) {
|
||||
fake.lockRoomMutex.Lock()
|
||||
defer fake.lockRoomMutex.Unlock()
|
||||
fake.LockRoomStub = nil
|
||||
if fake.lockRoomReturnsOnCall == nil {
|
||||
fake.lockRoomReturnsOnCall = make(map[int]struct {
|
||||
result1 string
|
||||
result2 error
|
||||
})
|
||||
}
|
||||
fake.lockRoomReturnsOnCall[i] = struct {
|
||||
result1 string
|
||||
result2 error
|
||||
}{result1, result2}
|
||||
}
|
||||
|
||||
func (fake *FakeObjectStore) StoreParticipant(arg1 context.Context, arg2 livekit.RoomName, arg3 *livekit.ParticipantInfo) error {
|
||||
fake.storeParticipantMutex.Lock()
|
||||
ret, specificReturn := fake.storeParticipantReturnsOnCall[len(fake.storeParticipantArgsForCall)]
|
||||
fake.storeParticipantArgsForCall = append(fake.storeParticipantArgsForCall, struct {
|
||||
arg1 context.Context
|
||||
arg2 livekit.RoomName
|
||||
arg3 *livekit.ParticipantInfo
|
||||
}{arg1, arg2, arg3})
|
||||
stub := fake.StoreParticipantStub
|
||||
fakeReturns := fake.storeParticipantReturns
|
||||
fake.recordInvocation("StoreParticipant", []interface{}{arg1, arg2, arg3})
|
||||
fake.storeParticipantMutex.Unlock()
|
||||
if stub != nil {
|
||||
return stub(arg1, arg2, arg3)
|
||||
}
|
||||
if specificReturn {
|
||||
return ret.result1
|
||||
}
|
||||
return fakeReturns.result1
|
||||
}
|
||||
|
||||
func (fake *FakeObjectStore) StoreParticipantCallCount() int {
|
||||
fake.storeParticipantMutex.RLock()
|
||||
defer fake.storeParticipantMutex.RUnlock()
|
||||
return len(fake.storeParticipantArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *FakeObjectStore) StoreParticipantCalls(stub func(context.Context, livekit.RoomName, *livekit.ParticipantInfo) error) {
|
||||
fake.storeParticipantMutex.Lock()
|
||||
defer fake.storeParticipantMutex.Unlock()
|
||||
fake.StoreParticipantStub = stub
|
||||
}
|
||||
|
||||
func (fake *FakeObjectStore) StoreParticipantArgsForCall(i int) (context.Context, livekit.RoomName, *livekit.ParticipantInfo) {
|
||||
fake.storeParticipantMutex.RLock()
|
||||
defer fake.storeParticipantMutex.RUnlock()
|
||||
argsForCall := fake.storeParticipantArgsForCall[i]
|
||||
return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3
|
||||
}
|
||||
|
||||
func (fake *FakeObjectStore) StoreParticipantReturns(result1 error) {
|
||||
fake.storeParticipantMutex.Lock()
|
||||
defer fake.storeParticipantMutex.Unlock()
|
||||
fake.StoreParticipantStub = nil
|
||||
fake.storeParticipantReturns = struct {
|
||||
result1 error
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeObjectStore) StoreParticipantReturnsOnCall(i int, result1 error) {
|
||||
fake.storeParticipantMutex.Lock()
|
||||
defer fake.storeParticipantMutex.Unlock()
|
||||
fake.StoreParticipantStub = nil
|
||||
if fake.storeParticipantReturnsOnCall == nil {
|
||||
fake.storeParticipantReturnsOnCall = make(map[int]struct {
|
||||
result1 error
|
||||
})
|
||||
}
|
||||
fake.storeParticipantReturnsOnCall[i] = struct {
|
||||
result1 error
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeObjectStore) StoreRoom(arg1 context.Context, arg2 *livekit.Room, arg3 *livekit.RoomInternal) error {
|
||||
fake.storeRoomMutex.Lock()
|
||||
ret, specificReturn := fake.storeRoomReturnsOnCall[len(fake.storeRoomArgsForCall)]
|
||||
fake.storeRoomArgsForCall = append(fake.storeRoomArgsForCall, struct {
|
||||
arg1 context.Context
|
||||
arg2 *livekit.Room
|
||||
arg3 *livekit.RoomInternal
|
||||
}{arg1, arg2, arg3})
|
||||
stub := fake.StoreRoomStub
|
||||
fakeReturns := fake.storeRoomReturns
|
||||
fake.recordInvocation("StoreRoom", []interface{}{arg1, arg2, arg3})
|
||||
fake.storeRoomMutex.Unlock()
|
||||
if stub != nil {
|
||||
return stub(arg1, arg2, arg3)
|
||||
}
|
||||
if specificReturn {
|
||||
return ret.result1
|
||||
}
|
||||
return fakeReturns.result1
|
||||
}
|
||||
|
||||
func (fake *FakeObjectStore) StoreRoomCallCount() int {
|
||||
fake.storeRoomMutex.RLock()
|
||||
defer fake.storeRoomMutex.RUnlock()
|
||||
return len(fake.storeRoomArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *FakeObjectStore) StoreRoomCalls(stub func(context.Context, *livekit.Room, *livekit.RoomInternal) error) {
|
||||
fake.storeRoomMutex.Lock()
|
||||
defer fake.storeRoomMutex.Unlock()
|
||||
fake.StoreRoomStub = stub
|
||||
}
|
||||
|
||||
func (fake *FakeObjectStore) StoreRoomArgsForCall(i int) (context.Context, *livekit.Room, *livekit.RoomInternal) {
|
||||
fake.storeRoomMutex.RLock()
|
||||
defer fake.storeRoomMutex.RUnlock()
|
||||
argsForCall := fake.storeRoomArgsForCall[i]
|
||||
return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3
|
||||
}
|
||||
|
||||
func (fake *FakeObjectStore) StoreRoomReturns(result1 error) {
|
||||
fake.storeRoomMutex.Lock()
|
||||
defer fake.storeRoomMutex.Unlock()
|
||||
fake.StoreRoomStub = nil
|
||||
fake.storeRoomReturns = struct {
|
||||
result1 error
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeObjectStore) StoreRoomReturnsOnCall(i int, result1 error) {
|
||||
fake.storeRoomMutex.Lock()
|
||||
defer fake.storeRoomMutex.Unlock()
|
||||
fake.StoreRoomStub = nil
|
||||
if fake.storeRoomReturnsOnCall == nil {
|
||||
fake.storeRoomReturnsOnCall = make(map[int]struct {
|
||||
result1 error
|
||||
})
|
||||
}
|
||||
fake.storeRoomReturnsOnCall[i] = struct {
|
||||
result1 error
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeObjectStore) UnlockRoom(arg1 context.Context, arg2 livekit.RoomName, arg3 string) error {
|
||||
fake.unlockRoomMutex.Lock()
|
||||
ret, specificReturn := fake.unlockRoomReturnsOnCall[len(fake.unlockRoomArgsForCall)]
|
||||
fake.unlockRoomArgsForCall = append(fake.unlockRoomArgsForCall, struct {
|
||||
arg1 context.Context
|
||||
arg2 livekit.RoomName
|
||||
arg3 string
|
||||
}{arg1, arg2, arg3})
|
||||
stub := fake.UnlockRoomStub
|
||||
fakeReturns := fake.unlockRoomReturns
|
||||
fake.recordInvocation("UnlockRoom", []interface{}{arg1, arg2, arg3})
|
||||
fake.unlockRoomMutex.Unlock()
|
||||
if stub != nil {
|
||||
return stub(arg1, arg2, arg3)
|
||||
}
|
||||
if specificReturn {
|
||||
return ret.result1
|
||||
}
|
||||
return fakeReturns.result1
|
||||
}
|
||||
|
||||
func (fake *FakeObjectStore) UnlockRoomCallCount() int {
|
||||
fake.unlockRoomMutex.RLock()
|
||||
defer fake.unlockRoomMutex.RUnlock()
|
||||
return len(fake.unlockRoomArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *FakeObjectStore) UnlockRoomCalls(stub func(context.Context, livekit.RoomName, string) error) {
|
||||
fake.unlockRoomMutex.Lock()
|
||||
defer fake.unlockRoomMutex.Unlock()
|
||||
fake.UnlockRoomStub = stub
|
||||
}
|
||||
|
||||
func (fake *FakeObjectStore) UnlockRoomArgsForCall(i int) (context.Context, livekit.RoomName, string) {
|
||||
fake.unlockRoomMutex.RLock()
|
||||
defer fake.unlockRoomMutex.RUnlock()
|
||||
argsForCall := fake.unlockRoomArgsForCall[i]
|
||||
return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3
|
||||
}
|
||||
|
||||
func (fake *FakeObjectStore) UnlockRoomReturns(result1 error) {
|
||||
fake.unlockRoomMutex.Lock()
|
||||
defer fake.unlockRoomMutex.Unlock()
|
||||
fake.UnlockRoomStub = nil
|
||||
fake.unlockRoomReturns = struct {
|
||||
result1 error
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeObjectStore) UnlockRoomReturnsOnCall(i int, result1 error) {
|
||||
fake.unlockRoomMutex.Lock()
|
||||
defer fake.unlockRoomMutex.Unlock()
|
||||
fake.UnlockRoomStub = nil
|
||||
if fake.unlockRoomReturnsOnCall == nil {
|
||||
fake.unlockRoomReturnsOnCall = make(map[int]struct {
|
||||
result1 error
|
||||
})
|
||||
}
|
||||
fake.unlockRoomReturnsOnCall[i] = struct {
|
||||
result1 error
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeObjectStore) Invocations() map[string][][]interface{} {
|
||||
fake.invocationsMutex.RLock()
|
||||
defer fake.invocationsMutex.RUnlock()
|
||||
fake.deleteParticipantMutex.RLock()
|
||||
defer fake.deleteParticipantMutex.RUnlock()
|
||||
fake.deleteRoomMutex.RLock()
|
||||
defer fake.deleteRoomMutex.RUnlock()
|
||||
fake.listParticipantsMutex.RLock()
|
||||
defer fake.listParticipantsMutex.RUnlock()
|
||||
fake.listRoomsMutex.RLock()
|
||||
defer fake.listRoomsMutex.RUnlock()
|
||||
fake.loadParticipantMutex.RLock()
|
||||
defer fake.loadParticipantMutex.RUnlock()
|
||||
fake.loadRoomMutex.RLock()
|
||||
defer fake.loadRoomMutex.RUnlock()
|
||||
fake.lockRoomMutex.RLock()
|
||||
defer fake.lockRoomMutex.RUnlock()
|
||||
fake.storeParticipantMutex.RLock()
|
||||
defer fake.storeParticipantMutex.RUnlock()
|
||||
fake.storeRoomMutex.RLock()
|
||||
defer fake.storeRoomMutex.RUnlock()
|
||||
fake.unlockRoomMutex.RLock()
|
||||
defer fake.unlockRoomMutex.RUnlock()
|
||||
copiedInvocations := map[string][][]interface{}{}
|
||||
for key, value := range fake.invocations {
|
||||
copiedInvocations[key] = value
|
||||
}
|
||||
return copiedInvocations
|
||||
}
|
||||
|
||||
func (fake *FakeObjectStore) 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 _ service.ObjectStore = new(FakeObjectStore)
|
||||
@@ -0,0 +1,360 @@
|
||||
// Code generated by counterfeiter. DO NOT EDIT.
|
||||
package servicefakes
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
|
||||
"github.com/livekit/livekit-server/pkg/service"
|
||||
"github.com/livekit/protocol/livekit"
|
||||
)
|
||||
|
||||
type FakeRoomAllocator struct {
|
||||
AutoCreateEnabledStub func(context.Context) bool
|
||||
autoCreateEnabledMutex sync.RWMutex
|
||||
autoCreateEnabledArgsForCall []struct {
|
||||
arg1 context.Context
|
||||
}
|
||||
autoCreateEnabledReturns struct {
|
||||
result1 bool
|
||||
}
|
||||
autoCreateEnabledReturnsOnCall map[int]struct {
|
||||
result1 bool
|
||||
}
|
||||
CreateRoomStub func(context.Context, *livekit.CreateRoomRequest, bool) (*livekit.Room, *livekit.RoomInternal, bool, error)
|
||||
createRoomMutex sync.RWMutex
|
||||
createRoomArgsForCall []struct {
|
||||
arg1 context.Context
|
||||
arg2 *livekit.CreateRoomRequest
|
||||
arg3 bool
|
||||
}
|
||||
createRoomReturns struct {
|
||||
result1 *livekit.Room
|
||||
result2 *livekit.RoomInternal
|
||||
result3 bool
|
||||
result4 error
|
||||
}
|
||||
createRoomReturnsOnCall map[int]struct {
|
||||
result1 *livekit.Room
|
||||
result2 *livekit.RoomInternal
|
||||
result3 bool
|
||||
result4 error
|
||||
}
|
||||
SelectRoomNodeStub func(context.Context, livekit.RoomName, livekit.NodeID) error
|
||||
selectRoomNodeMutex sync.RWMutex
|
||||
selectRoomNodeArgsForCall []struct {
|
||||
arg1 context.Context
|
||||
arg2 livekit.RoomName
|
||||
arg3 livekit.NodeID
|
||||
}
|
||||
selectRoomNodeReturns struct {
|
||||
result1 error
|
||||
}
|
||||
selectRoomNodeReturnsOnCall map[int]struct {
|
||||
result1 error
|
||||
}
|
||||
ValidateCreateRoomStub func(context.Context, livekit.RoomName) error
|
||||
validateCreateRoomMutex sync.RWMutex
|
||||
validateCreateRoomArgsForCall []struct {
|
||||
arg1 context.Context
|
||||
arg2 livekit.RoomName
|
||||
}
|
||||
validateCreateRoomReturns struct {
|
||||
result1 error
|
||||
}
|
||||
validateCreateRoomReturnsOnCall map[int]struct {
|
||||
result1 error
|
||||
}
|
||||
invocations map[string][][]interface{}
|
||||
invocationsMutex sync.RWMutex
|
||||
}
|
||||
|
||||
func (fake *FakeRoomAllocator) AutoCreateEnabled(arg1 context.Context) bool {
|
||||
fake.autoCreateEnabledMutex.Lock()
|
||||
ret, specificReturn := fake.autoCreateEnabledReturnsOnCall[len(fake.autoCreateEnabledArgsForCall)]
|
||||
fake.autoCreateEnabledArgsForCall = append(fake.autoCreateEnabledArgsForCall, struct {
|
||||
arg1 context.Context
|
||||
}{arg1})
|
||||
stub := fake.AutoCreateEnabledStub
|
||||
fakeReturns := fake.autoCreateEnabledReturns
|
||||
fake.recordInvocation("AutoCreateEnabled", []interface{}{arg1})
|
||||
fake.autoCreateEnabledMutex.Unlock()
|
||||
if stub != nil {
|
||||
return stub(arg1)
|
||||
}
|
||||
if specificReturn {
|
||||
return ret.result1
|
||||
}
|
||||
return fakeReturns.result1
|
||||
}
|
||||
|
||||
func (fake *FakeRoomAllocator) AutoCreateEnabledCallCount() int {
|
||||
fake.autoCreateEnabledMutex.RLock()
|
||||
defer fake.autoCreateEnabledMutex.RUnlock()
|
||||
return len(fake.autoCreateEnabledArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *FakeRoomAllocator) AutoCreateEnabledCalls(stub func(context.Context) bool) {
|
||||
fake.autoCreateEnabledMutex.Lock()
|
||||
defer fake.autoCreateEnabledMutex.Unlock()
|
||||
fake.AutoCreateEnabledStub = stub
|
||||
}
|
||||
|
||||
func (fake *FakeRoomAllocator) AutoCreateEnabledArgsForCall(i int) context.Context {
|
||||
fake.autoCreateEnabledMutex.RLock()
|
||||
defer fake.autoCreateEnabledMutex.RUnlock()
|
||||
argsForCall := fake.autoCreateEnabledArgsForCall[i]
|
||||
return argsForCall.arg1
|
||||
}
|
||||
|
||||
func (fake *FakeRoomAllocator) AutoCreateEnabledReturns(result1 bool) {
|
||||
fake.autoCreateEnabledMutex.Lock()
|
||||
defer fake.autoCreateEnabledMutex.Unlock()
|
||||
fake.AutoCreateEnabledStub = nil
|
||||
fake.autoCreateEnabledReturns = struct {
|
||||
result1 bool
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeRoomAllocator) AutoCreateEnabledReturnsOnCall(i int, result1 bool) {
|
||||
fake.autoCreateEnabledMutex.Lock()
|
||||
defer fake.autoCreateEnabledMutex.Unlock()
|
||||
fake.AutoCreateEnabledStub = nil
|
||||
if fake.autoCreateEnabledReturnsOnCall == nil {
|
||||
fake.autoCreateEnabledReturnsOnCall = make(map[int]struct {
|
||||
result1 bool
|
||||
})
|
||||
}
|
||||
fake.autoCreateEnabledReturnsOnCall[i] = struct {
|
||||
result1 bool
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeRoomAllocator) CreateRoom(arg1 context.Context, arg2 *livekit.CreateRoomRequest, arg3 bool) (*livekit.Room, *livekit.RoomInternal, bool, error) {
|
||||
fake.createRoomMutex.Lock()
|
||||
ret, specificReturn := fake.createRoomReturnsOnCall[len(fake.createRoomArgsForCall)]
|
||||
fake.createRoomArgsForCall = append(fake.createRoomArgsForCall, struct {
|
||||
arg1 context.Context
|
||||
arg2 *livekit.CreateRoomRequest
|
||||
arg3 bool
|
||||
}{arg1, arg2, arg3})
|
||||
stub := fake.CreateRoomStub
|
||||
fakeReturns := fake.createRoomReturns
|
||||
fake.recordInvocation("CreateRoom", []interface{}{arg1, arg2, arg3})
|
||||
fake.createRoomMutex.Unlock()
|
||||
if stub != nil {
|
||||
return stub(arg1, arg2, arg3)
|
||||
}
|
||||
if specificReturn {
|
||||
return ret.result1, ret.result2, ret.result3, ret.result4
|
||||
}
|
||||
return fakeReturns.result1, fakeReturns.result2, fakeReturns.result3, fakeReturns.result4
|
||||
}
|
||||
|
||||
func (fake *FakeRoomAllocator) CreateRoomCallCount() int {
|
||||
fake.createRoomMutex.RLock()
|
||||
defer fake.createRoomMutex.RUnlock()
|
||||
return len(fake.createRoomArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *FakeRoomAllocator) CreateRoomCalls(stub func(context.Context, *livekit.CreateRoomRequest, bool) (*livekit.Room, *livekit.RoomInternal, bool, error)) {
|
||||
fake.createRoomMutex.Lock()
|
||||
defer fake.createRoomMutex.Unlock()
|
||||
fake.CreateRoomStub = stub
|
||||
}
|
||||
|
||||
func (fake *FakeRoomAllocator) CreateRoomArgsForCall(i int) (context.Context, *livekit.CreateRoomRequest, bool) {
|
||||
fake.createRoomMutex.RLock()
|
||||
defer fake.createRoomMutex.RUnlock()
|
||||
argsForCall := fake.createRoomArgsForCall[i]
|
||||
return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3
|
||||
}
|
||||
|
||||
func (fake *FakeRoomAllocator) CreateRoomReturns(result1 *livekit.Room, result2 *livekit.RoomInternal, result3 bool, result4 error) {
|
||||
fake.createRoomMutex.Lock()
|
||||
defer fake.createRoomMutex.Unlock()
|
||||
fake.CreateRoomStub = nil
|
||||
fake.createRoomReturns = struct {
|
||||
result1 *livekit.Room
|
||||
result2 *livekit.RoomInternal
|
||||
result3 bool
|
||||
result4 error
|
||||
}{result1, result2, result3, result4}
|
||||
}
|
||||
|
||||
func (fake *FakeRoomAllocator) CreateRoomReturnsOnCall(i int, result1 *livekit.Room, result2 *livekit.RoomInternal, result3 bool, result4 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 *livekit.RoomInternal
|
||||
result3 bool
|
||||
result4 error
|
||||
})
|
||||
}
|
||||
fake.createRoomReturnsOnCall[i] = struct {
|
||||
result1 *livekit.Room
|
||||
result2 *livekit.RoomInternal
|
||||
result3 bool
|
||||
result4 error
|
||||
}{result1, result2, result3, result4}
|
||||
}
|
||||
|
||||
func (fake *FakeRoomAllocator) SelectRoomNode(arg1 context.Context, arg2 livekit.RoomName, arg3 livekit.NodeID) error {
|
||||
fake.selectRoomNodeMutex.Lock()
|
||||
ret, specificReturn := fake.selectRoomNodeReturnsOnCall[len(fake.selectRoomNodeArgsForCall)]
|
||||
fake.selectRoomNodeArgsForCall = append(fake.selectRoomNodeArgsForCall, struct {
|
||||
arg1 context.Context
|
||||
arg2 livekit.RoomName
|
||||
arg3 livekit.NodeID
|
||||
}{arg1, arg2, arg3})
|
||||
stub := fake.SelectRoomNodeStub
|
||||
fakeReturns := fake.selectRoomNodeReturns
|
||||
fake.recordInvocation("SelectRoomNode", []interface{}{arg1, arg2, arg3})
|
||||
fake.selectRoomNodeMutex.Unlock()
|
||||
if stub != nil {
|
||||
return stub(arg1, arg2, arg3)
|
||||
}
|
||||
if specificReturn {
|
||||
return ret.result1
|
||||
}
|
||||
return fakeReturns.result1
|
||||
}
|
||||
|
||||
func (fake *FakeRoomAllocator) SelectRoomNodeCallCount() int {
|
||||
fake.selectRoomNodeMutex.RLock()
|
||||
defer fake.selectRoomNodeMutex.RUnlock()
|
||||
return len(fake.selectRoomNodeArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *FakeRoomAllocator) SelectRoomNodeCalls(stub func(context.Context, livekit.RoomName, livekit.NodeID) error) {
|
||||
fake.selectRoomNodeMutex.Lock()
|
||||
defer fake.selectRoomNodeMutex.Unlock()
|
||||
fake.SelectRoomNodeStub = stub
|
||||
}
|
||||
|
||||
func (fake *FakeRoomAllocator) SelectRoomNodeArgsForCall(i int) (context.Context, livekit.RoomName, livekit.NodeID) {
|
||||
fake.selectRoomNodeMutex.RLock()
|
||||
defer fake.selectRoomNodeMutex.RUnlock()
|
||||
argsForCall := fake.selectRoomNodeArgsForCall[i]
|
||||
return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3
|
||||
}
|
||||
|
||||
func (fake *FakeRoomAllocator) SelectRoomNodeReturns(result1 error) {
|
||||
fake.selectRoomNodeMutex.Lock()
|
||||
defer fake.selectRoomNodeMutex.Unlock()
|
||||
fake.SelectRoomNodeStub = nil
|
||||
fake.selectRoomNodeReturns = struct {
|
||||
result1 error
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeRoomAllocator) SelectRoomNodeReturnsOnCall(i int, result1 error) {
|
||||
fake.selectRoomNodeMutex.Lock()
|
||||
defer fake.selectRoomNodeMutex.Unlock()
|
||||
fake.SelectRoomNodeStub = nil
|
||||
if fake.selectRoomNodeReturnsOnCall == nil {
|
||||
fake.selectRoomNodeReturnsOnCall = make(map[int]struct {
|
||||
result1 error
|
||||
})
|
||||
}
|
||||
fake.selectRoomNodeReturnsOnCall[i] = struct {
|
||||
result1 error
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeRoomAllocator) ValidateCreateRoom(arg1 context.Context, arg2 livekit.RoomName) error {
|
||||
fake.validateCreateRoomMutex.Lock()
|
||||
ret, specificReturn := fake.validateCreateRoomReturnsOnCall[len(fake.validateCreateRoomArgsForCall)]
|
||||
fake.validateCreateRoomArgsForCall = append(fake.validateCreateRoomArgsForCall, struct {
|
||||
arg1 context.Context
|
||||
arg2 livekit.RoomName
|
||||
}{arg1, arg2})
|
||||
stub := fake.ValidateCreateRoomStub
|
||||
fakeReturns := fake.validateCreateRoomReturns
|
||||
fake.recordInvocation("ValidateCreateRoom", []interface{}{arg1, arg2})
|
||||
fake.validateCreateRoomMutex.Unlock()
|
||||
if stub != nil {
|
||||
return stub(arg1, arg2)
|
||||
}
|
||||
if specificReturn {
|
||||
return ret.result1
|
||||
}
|
||||
return fakeReturns.result1
|
||||
}
|
||||
|
||||
func (fake *FakeRoomAllocator) ValidateCreateRoomCallCount() int {
|
||||
fake.validateCreateRoomMutex.RLock()
|
||||
defer fake.validateCreateRoomMutex.RUnlock()
|
||||
return len(fake.validateCreateRoomArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *FakeRoomAllocator) ValidateCreateRoomCalls(stub func(context.Context, livekit.RoomName) error) {
|
||||
fake.validateCreateRoomMutex.Lock()
|
||||
defer fake.validateCreateRoomMutex.Unlock()
|
||||
fake.ValidateCreateRoomStub = stub
|
||||
}
|
||||
|
||||
func (fake *FakeRoomAllocator) ValidateCreateRoomArgsForCall(i int) (context.Context, livekit.RoomName) {
|
||||
fake.validateCreateRoomMutex.RLock()
|
||||
defer fake.validateCreateRoomMutex.RUnlock()
|
||||
argsForCall := fake.validateCreateRoomArgsForCall[i]
|
||||
return argsForCall.arg1, argsForCall.arg2
|
||||
}
|
||||
|
||||
func (fake *FakeRoomAllocator) ValidateCreateRoomReturns(result1 error) {
|
||||
fake.validateCreateRoomMutex.Lock()
|
||||
defer fake.validateCreateRoomMutex.Unlock()
|
||||
fake.ValidateCreateRoomStub = nil
|
||||
fake.validateCreateRoomReturns = struct {
|
||||
result1 error
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeRoomAllocator) ValidateCreateRoomReturnsOnCall(i int, result1 error) {
|
||||
fake.validateCreateRoomMutex.Lock()
|
||||
defer fake.validateCreateRoomMutex.Unlock()
|
||||
fake.ValidateCreateRoomStub = nil
|
||||
if fake.validateCreateRoomReturnsOnCall == nil {
|
||||
fake.validateCreateRoomReturnsOnCall = make(map[int]struct {
|
||||
result1 error
|
||||
})
|
||||
}
|
||||
fake.validateCreateRoomReturnsOnCall[i] = struct {
|
||||
result1 error
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeRoomAllocator) Invocations() map[string][][]interface{} {
|
||||
fake.invocationsMutex.RLock()
|
||||
defer fake.invocationsMutex.RUnlock()
|
||||
fake.autoCreateEnabledMutex.RLock()
|
||||
defer fake.autoCreateEnabledMutex.RUnlock()
|
||||
fake.createRoomMutex.RLock()
|
||||
defer fake.createRoomMutex.RUnlock()
|
||||
fake.selectRoomNodeMutex.RLock()
|
||||
defer fake.selectRoomNodeMutex.RUnlock()
|
||||
fake.validateCreateRoomMutex.RLock()
|
||||
defer fake.validateCreateRoomMutex.RUnlock()
|
||||
copiedInvocations := map[string][][]interface{}{}
|
||||
for key, value := range fake.invocations {
|
||||
copiedInvocations[key] = value
|
||||
}
|
||||
return copiedInvocations
|
||||
}
|
||||
|
||||
func (fake *FakeRoomAllocator) 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 _ service.RoomAllocator = new(FakeRoomAllocator)
|
||||
@@ -0,0 +1,453 @@
|
||||
// Code generated by counterfeiter. DO NOT EDIT.
|
||||
package servicefakes
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
|
||||
"github.com/livekit/livekit-server/pkg/service"
|
||||
"github.com/livekit/protocol/livekit"
|
||||
)
|
||||
|
||||
type FakeServiceStore struct {
|
||||
DeleteRoomStub func(context.Context, livekit.RoomName) error
|
||||
deleteRoomMutex sync.RWMutex
|
||||
deleteRoomArgsForCall []struct {
|
||||
arg1 context.Context
|
||||
arg2 livekit.RoomName
|
||||
}
|
||||
deleteRoomReturns struct {
|
||||
result1 error
|
||||
}
|
||||
deleteRoomReturnsOnCall map[int]struct {
|
||||
result1 error
|
||||
}
|
||||
ListParticipantsStub func(context.Context, livekit.RoomName) ([]*livekit.ParticipantInfo, error)
|
||||
listParticipantsMutex sync.RWMutex
|
||||
listParticipantsArgsForCall []struct {
|
||||
arg1 context.Context
|
||||
arg2 livekit.RoomName
|
||||
}
|
||||
listParticipantsReturns struct {
|
||||
result1 []*livekit.ParticipantInfo
|
||||
result2 error
|
||||
}
|
||||
listParticipantsReturnsOnCall map[int]struct {
|
||||
result1 []*livekit.ParticipantInfo
|
||||
result2 error
|
||||
}
|
||||
ListRoomsStub func(context.Context, []livekit.RoomName) ([]*livekit.Room, error)
|
||||
listRoomsMutex sync.RWMutex
|
||||
listRoomsArgsForCall []struct {
|
||||
arg1 context.Context
|
||||
arg2 []livekit.RoomName
|
||||
}
|
||||
listRoomsReturns struct {
|
||||
result1 []*livekit.Room
|
||||
result2 error
|
||||
}
|
||||
listRoomsReturnsOnCall map[int]struct {
|
||||
result1 []*livekit.Room
|
||||
result2 error
|
||||
}
|
||||
LoadParticipantStub func(context.Context, livekit.RoomName, livekit.ParticipantIdentity) (*livekit.ParticipantInfo, error)
|
||||
loadParticipantMutex sync.RWMutex
|
||||
loadParticipantArgsForCall []struct {
|
||||
arg1 context.Context
|
||||
arg2 livekit.RoomName
|
||||
arg3 livekit.ParticipantIdentity
|
||||
}
|
||||
loadParticipantReturns struct {
|
||||
result1 *livekit.ParticipantInfo
|
||||
result2 error
|
||||
}
|
||||
loadParticipantReturnsOnCall map[int]struct {
|
||||
result1 *livekit.ParticipantInfo
|
||||
result2 error
|
||||
}
|
||||
LoadRoomStub func(context.Context, livekit.RoomName, bool) (*livekit.Room, *livekit.RoomInternal, error)
|
||||
loadRoomMutex sync.RWMutex
|
||||
loadRoomArgsForCall []struct {
|
||||
arg1 context.Context
|
||||
arg2 livekit.RoomName
|
||||
arg3 bool
|
||||
}
|
||||
loadRoomReturns struct {
|
||||
result1 *livekit.Room
|
||||
result2 *livekit.RoomInternal
|
||||
result3 error
|
||||
}
|
||||
loadRoomReturnsOnCall map[int]struct {
|
||||
result1 *livekit.Room
|
||||
result2 *livekit.RoomInternal
|
||||
result3 error
|
||||
}
|
||||
invocations map[string][][]interface{}
|
||||
invocationsMutex sync.RWMutex
|
||||
}
|
||||
|
||||
func (fake *FakeServiceStore) DeleteRoom(arg1 context.Context, arg2 livekit.RoomName) error {
|
||||
fake.deleteRoomMutex.Lock()
|
||||
ret, specificReturn := fake.deleteRoomReturnsOnCall[len(fake.deleteRoomArgsForCall)]
|
||||
fake.deleteRoomArgsForCall = append(fake.deleteRoomArgsForCall, struct {
|
||||
arg1 context.Context
|
||||
arg2 livekit.RoomName
|
||||
}{arg1, arg2})
|
||||
stub := fake.DeleteRoomStub
|
||||
fakeReturns := fake.deleteRoomReturns
|
||||
fake.recordInvocation("DeleteRoom", []interface{}{arg1, arg2})
|
||||
fake.deleteRoomMutex.Unlock()
|
||||
if stub != nil {
|
||||
return stub(arg1, arg2)
|
||||
}
|
||||
if specificReturn {
|
||||
return ret.result1
|
||||
}
|
||||
return fakeReturns.result1
|
||||
}
|
||||
|
||||
func (fake *FakeServiceStore) DeleteRoomCallCount() int {
|
||||
fake.deleteRoomMutex.RLock()
|
||||
defer fake.deleteRoomMutex.RUnlock()
|
||||
return len(fake.deleteRoomArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *FakeServiceStore) DeleteRoomCalls(stub func(context.Context, livekit.RoomName) error) {
|
||||
fake.deleteRoomMutex.Lock()
|
||||
defer fake.deleteRoomMutex.Unlock()
|
||||
fake.DeleteRoomStub = stub
|
||||
}
|
||||
|
||||
func (fake *FakeServiceStore) DeleteRoomArgsForCall(i int) (context.Context, livekit.RoomName) {
|
||||
fake.deleteRoomMutex.RLock()
|
||||
defer fake.deleteRoomMutex.RUnlock()
|
||||
argsForCall := fake.deleteRoomArgsForCall[i]
|
||||
return argsForCall.arg1, argsForCall.arg2
|
||||
}
|
||||
|
||||
func (fake *FakeServiceStore) DeleteRoomReturns(result1 error) {
|
||||
fake.deleteRoomMutex.Lock()
|
||||
defer fake.deleteRoomMutex.Unlock()
|
||||
fake.DeleteRoomStub = nil
|
||||
fake.deleteRoomReturns = struct {
|
||||
result1 error
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeServiceStore) DeleteRoomReturnsOnCall(i int, result1 error) {
|
||||
fake.deleteRoomMutex.Lock()
|
||||
defer fake.deleteRoomMutex.Unlock()
|
||||
fake.DeleteRoomStub = nil
|
||||
if fake.deleteRoomReturnsOnCall == nil {
|
||||
fake.deleteRoomReturnsOnCall = make(map[int]struct {
|
||||
result1 error
|
||||
})
|
||||
}
|
||||
fake.deleteRoomReturnsOnCall[i] = struct {
|
||||
result1 error
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeServiceStore) ListParticipants(arg1 context.Context, arg2 livekit.RoomName) ([]*livekit.ParticipantInfo, error) {
|
||||
fake.listParticipantsMutex.Lock()
|
||||
ret, specificReturn := fake.listParticipantsReturnsOnCall[len(fake.listParticipantsArgsForCall)]
|
||||
fake.listParticipantsArgsForCall = append(fake.listParticipantsArgsForCall, struct {
|
||||
arg1 context.Context
|
||||
arg2 livekit.RoomName
|
||||
}{arg1, arg2})
|
||||
stub := fake.ListParticipantsStub
|
||||
fakeReturns := fake.listParticipantsReturns
|
||||
fake.recordInvocation("ListParticipants", []interface{}{arg1, arg2})
|
||||
fake.listParticipantsMutex.Unlock()
|
||||
if stub != nil {
|
||||
return stub(arg1, arg2)
|
||||
}
|
||||
if specificReturn {
|
||||
return ret.result1, ret.result2
|
||||
}
|
||||
return fakeReturns.result1, fakeReturns.result2
|
||||
}
|
||||
|
||||
func (fake *FakeServiceStore) ListParticipantsCallCount() int {
|
||||
fake.listParticipantsMutex.RLock()
|
||||
defer fake.listParticipantsMutex.RUnlock()
|
||||
return len(fake.listParticipantsArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *FakeServiceStore) ListParticipantsCalls(stub func(context.Context, livekit.RoomName) ([]*livekit.ParticipantInfo, error)) {
|
||||
fake.listParticipantsMutex.Lock()
|
||||
defer fake.listParticipantsMutex.Unlock()
|
||||
fake.ListParticipantsStub = stub
|
||||
}
|
||||
|
||||
func (fake *FakeServiceStore) ListParticipantsArgsForCall(i int) (context.Context, livekit.RoomName) {
|
||||
fake.listParticipantsMutex.RLock()
|
||||
defer fake.listParticipantsMutex.RUnlock()
|
||||
argsForCall := fake.listParticipantsArgsForCall[i]
|
||||
return argsForCall.arg1, argsForCall.arg2
|
||||
}
|
||||
|
||||
func (fake *FakeServiceStore) ListParticipantsReturns(result1 []*livekit.ParticipantInfo, result2 error) {
|
||||
fake.listParticipantsMutex.Lock()
|
||||
defer fake.listParticipantsMutex.Unlock()
|
||||
fake.ListParticipantsStub = nil
|
||||
fake.listParticipantsReturns = struct {
|
||||
result1 []*livekit.ParticipantInfo
|
||||
result2 error
|
||||
}{result1, result2}
|
||||
}
|
||||
|
||||
func (fake *FakeServiceStore) ListParticipantsReturnsOnCall(i int, result1 []*livekit.ParticipantInfo, result2 error) {
|
||||
fake.listParticipantsMutex.Lock()
|
||||
defer fake.listParticipantsMutex.Unlock()
|
||||
fake.ListParticipantsStub = nil
|
||||
if fake.listParticipantsReturnsOnCall == nil {
|
||||
fake.listParticipantsReturnsOnCall = make(map[int]struct {
|
||||
result1 []*livekit.ParticipantInfo
|
||||
result2 error
|
||||
})
|
||||
}
|
||||
fake.listParticipantsReturnsOnCall[i] = struct {
|
||||
result1 []*livekit.ParticipantInfo
|
||||
result2 error
|
||||
}{result1, result2}
|
||||
}
|
||||
|
||||
func (fake *FakeServiceStore) ListRooms(arg1 context.Context, arg2 []livekit.RoomName) ([]*livekit.Room, error) {
|
||||
var arg2Copy []livekit.RoomName
|
||||
if arg2 != nil {
|
||||
arg2Copy = make([]livekit.RoomName, len(arg2))
|
||||
copy(arg2Copy, arg2)
|
||||
}
|
||||
fake.listRoomsMutex.Lock()
|
||||
ret, specificReturn := fake.listRoomsReturnsOnCall[len(fake.listRoomsArgsForCall)]
|
||||
fake.listRoomsArgsForCall = append(fake.listRoomsArgsForCall, struct {
|
||||
arg1 context.Context
|
||||
arg2 []livekit.RoomName
|
||||
}{arg1, arg2Copy})
|
||||
stub := fake.ListRoomsStub
|
||||
fakeReturns := fake.listRoomsReturns
|
||||
fake.recordInvocation("ListRooms", []interface{}{arg1, arg2Copy})
|
||||
fake.listRoomsMutex.Unlock()
|
||||
if stub != nil {
|
||||
return stub(arg1, arg2)
|
||||
}
|
||||
if specificReturn {
|
||||
return ret.result1, ret.result2
|
||||
}
|
||||
return fakeReturns.result1, fakeReturns.result2
|
||||
}
|
||||
|
||||
func (fake *FakeServiceStore) ListRoomsCallCount() int {
|
||||
fake.listRoomsMutex.RLock()
|
||||
defer fake.listRoomsMutex.RUnlock()
|
||||
return len(fake.listRoomsArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *FakeServiceStore) ListRoomsCalls(stub func(context.Context, []livekit.RoomName) ([]*livekit.Room, error)) {
|
||||
fake.listRoomsMutex.Lock()
|
||||
defer fake.listRoomsMutex.Unlock()
|
||||
fake.ListRoomsStub = stub
|
||||
}
|
||||
|
||||
func (fake *FakeServiceStore) ListRoomsArgsForCall(i int) (context.Context, []livekit.RoomName) {
|
||||
fake.listRoomsMutex.RLock()
|
||||
defer fake.listRoomsMutex.RUnlock()
|
||||
argsForCall := fake.listRoomsArgsForCall[i]
|
||||
return argsForCall.arg1, argsForCall.arg2
|
||||
}
|
||||
|
||||
func (fake *FakeServiceStore) ListRoomsReturns(result1 []*livekit.Room, result2 error) {
|
||||
fake.listRoomsMutex.Lock()
|
||||
defer fake.listRoomsMutex.Unlock()
|
||||
fake.ListRoomsStub = nil
|
||||
fake.listRoomsReturns = struct {
|
||||
result1 []*livekit.Room
|
||||
result2 error
|
||||
}{result1, result2}
|
||||
}
|
||||
|
||||
func (fake *FakeServiceStore) ListRoomsReturnsOnCall(i int, result1 []*livekit.Room, result2 error) {
|
||||
fake.listRoomsMutex.Lock()
|
||||
defer fake.listRoomsMutex.Unlock()
|
||||
fake.ListRoomsStub = nil
|
||||
if fake.listRoomsReturnsOnCall == nil {
|
||||
fake.listRoomsReturnsOnCall = make(map[int]struct {
|
||||
result1 []*livekit.Room
|
||||
result2 error
|
||||
})
|
||||
}
|
||||
fake.listRoomsReturnsOnCall[i] = struct {
|
||||
result1 []*livekit.Room
|
||||
result2 error
|
||||
}{result1, result2}
|
||||
}
|
||||
|
||||
func (fake *FakeServiceStore) LoadParticipant(arg1 context.Context, arg2 livekit.RoomName, arg3 livekit.ParticipantIdentity) (*livekit.ParticipantInfo, error) {
|
||||
fake.loadParticipantMutex.Lock()
|
||||
ret, specificReturn := fake.loadParticipantReturnsOnCall[len(fake.loadParticipantArgsForCall)]
|
||||
fake.loadParticipantArgsForCall = append(fake.loadParticipantArgsForCall, struct {
|
||||
arg1 context.Context
|
||||
arg2 livekit.RoomName
|
||||
arg3 livekit.ParticipantIdentity
|
||||
}{arg1, arg2, arg3})
|
||||
stub := fake.LoadParticipantStub
|
||||
fakeReturns := fake.loadParticipantReturns
|
||||
fake.recordInvocation("LoadParticipant", []interface{}{arg1, arg2, arg3})
|
||||
fake.loadParticipantMutex.Unlock()
|
||||
if stub != nil {
|
||||
return stub(arg1, arg2, arg3)
|
||||
}
|
||||
if specificReturn {
|
||||
return ret.result1, ret.result2
|
||||
}
|
||||
return fakeReturns.result1, fakeReturns.result2
|
||||
}
|
||||
|
||||
func (fake *FakeServiceStore) LoadParticipantCallCount() int {
|
||||
fake.loadParticipantMutex.RLock()
|
||||
defer fake.loadParticipantMutex.RUnlock()
|
||||
return len(fake.loadParticipantArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *FakeServiceStore) LoadParticipantCalls(stub func(context.Context, livekit.RoomName, livekit.ParticipantIdentity) (*livekit.ParticipantInfo, error)) {
|
||||
fake.loadParticipantMutex.Lock()
|
||||
defer fake.loadParticipantMutex.Unlock()
|
||||
fake.LoadParticipantStub = stub
|
||||
}
|
||||
|
||||
func (fake *FakeServiceStore) LoadParticipantArgsForCall(i int) (context.Context, livekit.RoomName, livekit.ParticipantIdentity) {
|
||||
fake.loadParticipantMutex.RLock()
|
||||
defer fake.loadParticipantMutex.RUnlock()
|
||||
argsForCall := fake.loadParticipantArgsForCall[i]
|
||||
return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3
|
||||
}
|
||||
|
||||
func (fake *FakeServiceStore) LoadParticipantReturns(result1 *livekit.ParticipantInfo, result2 error) {
|
||||
fake.loadParticipantMutex.Lock()
|
||||
defer fake.loadParticipantMutex.Unlock()
|
||||
fake.LoadParticipantStub = nil
|
||||
fake.loadParticipantReturns = struct {
|
||||
result1 *livekit.ParticipantInfo
|
||||
result2 error
|
||||
}{result1, result2}
|
||||
}
|
||||
|
||||
func (fake *FakeServiceStore) LoadParticipantReturnsOnCall(i int, result1 *livekit.ParticipantInfo, result2 error) {
|
||||
fake.loadParticipantMutex.Lock()
|
||||
defer fake.loadParticipantMutex.Unlock()
|
||||
fake.LoadParticipantStub = nil
|
||||
if fake.loadParticipantReturnsOnCall == nil {
|
||||
fake.loadParticipantReturnsOnCall = make(map[int]struct {
|
||||
result1 *livekit.ParticipantInfo
|
||||
result2 error
|
||||
})
|
||||
}
|
||||
fake.loadParticipantReturnsOnCall[i] = struct {
|
||||
result1 *livekit.ParticipantInfo
|
||||
result2 error
|
||||
}{result1, result2}
|
||||
}
|
||||
|
||||
func (fake *FakeServiceStore) LoadRoom(arg1 context.Context, arg2 livekit.RoomName, arg3 bool) (*livekit.Room, *livekit.RoomInternal, error) {
|
||||
fake.loadRoomMutex.Lock()
|
||||
ret, specificReturn := fake.loadRoomReturnsOnCall[len(fake.loadRoomArgsForCall)]
|
||||
fake.loadRoomArgsForCall = append(fake.loadRoomArgsForCall, struct {
|
||||
arg1 context.Context
|
||||
arg2 livekit.RoomName
|
||||
arg3 bool
|
||||
}{arg1, arg2, arg3})
|
||||
stub := fake.LoadRoomStub
|
||||
fakeReturns := fake.loadRoomReturns
|
||||
fake.recordInvocation("LoadRoom", []interface{}{arg1, arg2, arg3})
|
||||
fake.loadRoomMutex.Unlock()
|
||||
if stub != nil {
|
||||
return stub(arg1, arg2, arg3)
|
||||
}
|
||||
if specificReturn {
|
||||
return ret.result1, ret.result2, ret.result3
|
||||
}
|
||||
return fakeReturns.result1, fakeReturns.result2, fakeReturns.result3
|
||||
}
|
||||
|
||||
func (fake *FakeServiceStore) LoadRoomCallCount() int {
|
||||
fake.loadRoomMutex.RLock()
|
||||
defer fake.loadRoomMutex.RUnlock()
|
||||
return len(fake.loadRoomArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *FakeServiceStore) LoadRoomCalls(stub func(context.Context, livekit.RoomName, bool) (*livekit.Room, *livekit.RoomInternal, error)) {
|
||||
fake.loadRoomMutex.Lock()
|
||||
defer fake.loadRoomMutex.Unlock()
|
||||
fake.LoadRoomStub = stub
|
||||
}
|
||||
|
||||
func (fake *FakeServiceStore) LoadRoomArgsForCall(i int) (context.Context, livekit.RoomName, bool) {
|
||||
fake.loadRoomMutex.RLock()
|
||||
defer fake.loadRoomMutex.RUnlock()
|
||||
argsForCall := fake.loadRoomArgsForCall[i]
|
||||
return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3
|
||||
}
|
||||
|
||||
func (fake *FakeServiceStore) LoadRoomReturns(result1 *livekit.Room, result2 *livekit.RoomInternal, result3 error) {
|
||||
fake.loadRoomMutex.Lock()
|
||||
defer fake.loadRoomMutex.Unlock()
|
||||
fake.LoadRoomStub = nil
|
||||
fake.loadRoomReturns = struct {
|
||||
result1 *livekit.Room
|
||||
result2 *livekit.RoomInternal
|
||||
result3 error
|
||||
}{result1, result2, result3}
|
||||
}
|
||||
|
||||
func (fake *FakeServiceStore) LoadRoomReturnsOnCall(i int, result1 *livekit.Room, result2 *livekit.RoomInternal, result3 error) {
|
||||
fake.loadRoomMutex.Lock()
|
||||
defer fake.loadRoomMutex.Unlock()
|
||||
fake.LoadRoomStub = nil
|
||||
if fake.loadRoomReturnsOnCall == nil {
|
||||
fake.loadRoomReturnsOnCall = make(map[int]struct {
|
||||
result1 *livekit.Room
|
||||
result2 *livekit.RoomInternal
|
||||
result3 error
|
||||
})
|
||||
}
|
||||
fake.loadRoomReturnsOnCall[i] = struct {
|
||||
result1 *livekit.Room
|
||||
result2 *livekit.RoomInternal
|
||||
result3 error
|
||||
}{result1, result2, result3}
|
||||
}
|
||||
|
||||
func (fake *FakeServiceStore) Invocations() map[string][][]interface{} {
|
||||
fake.invocationsMutex.RLock()
|
||||
defer fake.invocationsMutex.RUnlock()
|
||||
fake.deleteRoomMutex.RLock()
|
||||
defer fake.deleteRoomMutex.RUnlock()
|
||||
fake.listParticipantsMutex.RLock()
|
||||
defer fake.listParticipantsMutex.RUnlock()
|
||||
fake.listRoomsMutex.RLock()
|
||||
defer fake.listRoomsMutex.RUnlock()
|
||||
fake.loadParticipantMutex.RLock()
|
||||
defer fake.loadParticipantMutex.RUnlock()
|
||||
fake.loadRoomMutex.RLock()
|
||||
defer fake.loadRoomMutex.RUnlock()
|
||||
copiedInvocations := map[string][][]interface{}{}
|
||||
for key, value := range fake.invocations {
|
||||
copiedInvocations[key] = value
|
||||
}
|
||||
return copiedInvocations
|
||||
}
|
||||
|
||||
func (fake *FakeServiceStore) 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 _ service.ServiceStore = new(FakeServiceStore)
|
||||
@@ -0,0 +1,197 @@
|
||||
// Code generated by counterfeiter. DO NOT EDIT.
|
||||
package servicefakes
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
|
||||
"github.com/livekit/livekit-server/pkg/routing"
|
||||
"github.com/livekit/livekit-server/pkg/service"
|
||||
"github.com/livekit/protocol/livekit"
|
||||
"github.com/livekit/protocol/logger"
|
||||
)
|
||||
|
||||
type FakeSessionHandler struct {
|
||||
HandleSessionStub func(context.Context, routing.ParticipantInit, livekit.ConnectionID, routing.MessageSource, routing.MessageSink) error
|
||||
handleSessionMutex sync.RWMutex
|
||||
handleSessionArgsForCall []struct {
|
||||
arg1 context.Context
|
||||
arg2 routing.ParticipantInit
|
||||
arg3 livekit.ConnectionID
|
||||
arg4 routing.MessageSource
|
||||
arg5 routing.MessageSink
|
||||
}
|
||||
handleSessionReturns struct {
|
||||
result1 error
|
||||
}
|
||||
handleSessionReturnsOnCall map[int]struct {
|
||||
result1 error
|
||||
}
|
||||
LoggerStub func(context.Context) logger.Logger
|
||||
loggerMutex sync.RWMutex
|
||||
loggerArgsForCall []struct {
|
||||
arg1 context.Context
|
||||
}
|
||||
loggerReturns struct {
|
||||
result1 logger.Logger
|
||||
}
|
||||
loggerReturnsOnCall map[int]struct {
|
||||
result1 logger.Logger
|
||||
}
|
||||
invocations map[string][][]interface{}
|
||||
invocationsMutex sync.RWMutex
|
||||
}
|
||||
|
||||
func (fake *FakeSessionHandler) HandleSession(arg1 context.Context, arg2 routing.ParticipantInit, arg3 livekit.ConnectionID, arg4 routing.MessageSource, arg5 routing.MessageSink) error {
|
||||
fake.handleSessionMutex.Lock()
|
||||
ret, specificReturn := fake.handleSessionReturnsOnCall[len(fake.handleSessionArgsForCall)]
|
||||
fake.handleSessionArgsForCall = append(fake.handleSessionArgsForCall, struct {
|
||||
arg1 context.Context
|
||||
arg2 routing.ParticipantInit
|
||||
arg3 livekit.ConnectionID
|
||||
arg4 routing.MessageSource
|
||||
arg5 routing.MessageSink
|
||||
}{arg1, arg2, arg3, arg4, arg5})
|
||||
stub := fake.HandleSessionStub
|
||||
fakeReturns := fake.handleSessionReturns
|
||||
fake.recordInvocation("HandleSession", []interface{}{arg1, arg2, arg3, arg4, arg5})
|
||||
fake.handleSessionMutex.Unlock()
|
||||
if stub != nil {
|
||||
return stub(arg1, arg2, arg3, arg4, arg5)
|
||||
}
|
||||
if specificReturn {
|
||||
return ret.result1
|
||||
}
|
||||
return fakeReturns.result1
|
||||
}
|
||||
|
||||
func (fake *FakeSessionHandler) HandleSessionCallCount() int {
|
||||
fake.handleSessionMutex.RLock()
|
||||
defer fake.handleSessionMutex.RUnlock()
|
||||
return len(fake.handleSessionArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *FakeSessionHandler) HandleSessionCalls(stub func(context.Context, routing.ParticipantInit, livekit.ConnectionID, routing.MessageSource, routing.MessageSink) error) {
|
||||
fake.handleSessionMutex.Lock()
|
||||
defer fake.handleSessionMutex.Unlock()
|
||||
fake.HandleSessionStub = stub
|
||||
}
|
||||
|
||||
func (fake *FakeSessionHandler) HandleSessionArgsForCall(i int) (context.Context, routing.ParticipantInit, livekit.ConnectionID, routing.MessageSource, routing.MessageSink) {
|
||||
fake.handleSessionMutex.RLock()
|
||||
defer fake.handleSessionMutex.RUnlock()
|
||||
argsForCall := fake.handleSessionArgsForCall[i]
|
||||
return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3, argsForCall.arg4, argsForCall.arg5
|
||||
}
|
||||
|
||||
func (fake *FakeSessionHandler) HandleSessionReturns(result1 error) {
|
||||
fake.handleSessionMutex.Lock()
|
||||
defer fake.handleSessionMutex.Unlock()
|
||||
fake.HandleSessionStub = nil
|
||||
fake.handleSessionReturns = struct {
|
||||
result1 error
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeSessionHandler) HandleSessionReturnsOnCall(i int, result1 error) {
|
||||
fake.handleSessionMutex.Lock()
|
||||
defer fake.handleSessionMutex.Unlock()
|
||||
fake.HandleSessionStub = nil
|
||||
if fake.handleSessionReturnsOnCall == nil {
|
||||
fake.handleSessionReturnsOnCall = make(map[int]struct {
|
||||
result1 error
|
||||
})
|
||||
}
|
||||
fake.handleSessionReturnsOnCall[i] = struct {
|
||||
result1 error
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeSessionHandler) Logger(arg1 context.Context) logger.Logger {
|
||||
fake.loggerMutex.Lock()
|
||||
ret, specificReturn := fake.loggerReturnsOnCall[len(fake.loggerArgsForCall)]
|
||||
fake.loggerArgsForCall = append(fake.loggerArgsForCall, struct {
|
||||
arg1 context.Context
|
||||
}{arg1})
|
||||
stub := fake.LoggerStub
|
||||
fakeReturns := fake.loggerReturns
|
||||
fake.recordInvocation("Logger", []interface{}{arg1})
|
||||
fake.loggerMutex.Unlock()
|
||||
if stub != nil {
|
||||
return stub(arg1)
|
||||
}
|
||||
if specificReturn {
|
||||
return ret.result1
|
||||
}
|
||||
return fakeReturns.result1
|
||||
}
|
||||
|
||||
func (fake *FakeSessionHandler) LoggerCallCount() int {
|
||||
fake.loggerMutex.RLock()
|
||||
defer fake.loggerMutex.RUnlock()
|
||||
return len(fake.loggerArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *FakeSessionHandler) LoggerCalls(stub func(context.Context) logger.Logger) {
|
||||
fake.loggerMutex.Lock()
|
||||
defer fake.loggerMutex.Unlock()
|
||||
fake.LoggerStub = stub
|
||||
}
|
||||
|
||||
func (fake *FakeSessionHandler) LoggerArgsForCall(i int) context.Context {
|
||||
fake.loggerMutex.RLock()
|
||||
defer fake.loggerMutex.RUnlock()
|
||||
argsForCall := fake.loggerArgsForCall[i]
|
||||
return argsForCall.arg1
|
||||
}
|
||||
|
||||
func (fake *FakeSessionHandler) LoggerReturns(result1 logger.Logger) {
|
||||
fake.loggerMutex.Lock()
|
||||
defer fake.loggerMutex.Unlock()
|
||||
fake.LoggerStub = nil
|
||||
fake.loggerReturns = struct {
|
||||
result1 logger.Logger
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeSessionHandler) LoggerReturnsOnCall(i int, result1 logger.Logger) {
|
||||
fake.loggerMutex.Lock()
|
||||
defer fake.loggerMutex.Unlock()
|
||||
fake.LoggerStub = nil
|
||||
if fake.loggerReturnsOnCall == nil {
|
||||
fake.loggerReturnsOnCall = make(map[int]struct {
|
||||
result1 logger.Logger
|
||||
})
|
||||
}
|
||||
fake.loggerReturnsOnCall[i] = struct {
|
||||
result1 logger.Logger
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeSessionHandler) Invocations() map[string][][]interface{} {
|
||||
fake.invocationsMutex.RLock()
|
||||
defer fake.invocationsMutex.RUnlock()
|
||||
fake.handleSessionMutex.RLock()
|
||||
defer fake.handleSessionMutex.RUnlock()
|
||||
fake.loggerMutex.RLock()
|
||||
defer fake.loggerMutex.RUnlock()
|
||||
copiedInvocations := map[string][][]interface{}{}
|
||||
for key, value := range fake.invocations {
|
||||
copiedInvocations[key] = value
|
||||
}
|
||||
return copiedInvocations
|
||||
}
|
||||
|
||||
func (fake *FakeSessionHandler) 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 _ service.SessionHandler = new(FakeSessionHandler)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,210 @@
|
||||
// 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 service
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"github.com/livekit/livekit-server/pkg/config"
|
||||
"github.com/livekit/livekit-server/pkg/routing"
|
||||
"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/psrpc"
|
||||
"github.com/livekit/psrpc/pkg/metadata"
|
||||
"github.com/livekit/psrpc/pkg/middleware"
|
||||
)
|
||||
|
||||
//counterfeiter:generate . SessionHandler
|
||||
type SessionHandler interface {
|
||||
Logger(ctx context.Context) logger.Logger
|
||||
|
||||
HandleSession(
|
||||
ctx context.Context,
|
||||
pi routing.ParticipantInit,
|
||||
connectionID livekit.ConnectionID,
|
||||
requestSource routing.MessageSource,
|
||||
responseSink routing.MessageSink,
|
||||
) error
|
||||
}
|
||||
|
||||
type SignalServer struct {
|
||||
server rpc.TypedSignalServer
|
||||
nodeID livekit.NodeID
|
||||
}
|
||||
|
||||
func NewSignalServer(
|
||||
nodeID livekit.NodeID,
|
||||
region string,
|
||||
bus psrpc.MessageBus,
|
||||
config config.SignalRelayConfig,
|
||||
sessionHandler SessionHandler,
|
||||
) (*SignalServer, error) {
|
||||
s, err := rpc.NewTypedSignalServer(
|
||||
nodeID,
|
||||
&signalService{region, sessionHandler, config},
|
||||
bus,
|
||||
middleware.WithServerMetrics(rpc.PSRPCMetricsObserver{}),
|
||||
psrpc.WithServerChannelSize(config.StreamBufferSize),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &SignalServer{s, nodeID}, nil
|
||||
}
|
||||
|
||||
func NewDefaultSignalServer(
|
||||
currentNode routing.LocalNode,
|
||||
bus psrpc.MessageBus,
|
||||
config config.SignalRelayConfig,
|
||||
router routing.Router,
|
||||
roomManager *RoomManager,
|
||||
) (r *SignalServer, err error) {
|
||||
return NewSignalServer(currentNode.NodeID(), currentNode.Region(), bus, config, &defaultSessionHandler{currentNode, router, roomManager})
|
||||
}
|
||||
|
||||
type defaultSessionHandler struct {
|
||||
currentNode routing.LocalNode
|
||||
router routing.Router
|
||||
roomManager *RoomManager
|
||||
}
|
||||
|
||||
func (s *defaultSessionHandler) Logger(ctx context.Context) logger.Logger {
|
||||
return logger.GetLogger()
|
||||
}
|
||||
|
||||
func (s *defaultSessionHandler) HandleSession(
|
||||
ctx context.Context,
|
||||
pi routing.ParticipantInit,
|
||||
connectionID livekit.ConnectionID,
|
||||
requestSource routing.MessageSource,
|
||||
responseSink routing.MessageSink,
|
||||
) error {
|
||||
prometheus.IncrementParticipantRtcInit(1)
|
||||
|
||||
rtcNode, err := s.router.GetNodeForRoom(ctx, livekit.RoomName(pi.CreateRoom.Name))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if livekit.NodeID(rtcNode.Id) != s.currentNode.NodeID() {
|
||||
err = routing.ErrIncorrectRTCNode
|
||||
logger.Errorw("called participant on incorrect node", err,
|
||||
"rtcNode", rtcNode,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
return s.roomManager.StartSession(ctx, pi, requestSource, responseSink, false)
|
||||
}
|
||||
|
||||
func (s *SignalServer) Start() error {
|
||||
logger.Debugw("starting relay signal server", "topic", s.nodeID)
|
||||
return s.server.RegisterAllNodeTopics(s.nodeID)
|
||||
}
|
||||
|
||||
func (r *SignalServer) Stop() {
|
||||
r.server.Kill()
|
||||
}
|
||||
|
||||
type signalService struct {
|
||||
region string
|
||||
sessionHandler SessionHandler
|
||||
config config.SignalRelayConfig
|
||||
}
|
||||
|
||||
func (r *signalService) RelaySignal(stream psrpc.ServerStream[*rpc.RelaySignalResponse, *rpc.RelaySignalRequest]) (err error) {
|
||||
req, ok := <-stream.Channel()
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
ss := req.StartSession
|
||||
if ss == nil {
|
||||
return errors.New("expected start session message")
|
||||
}
|
||||
|
||||
pi, err := routing.ParticipantInitFromStartSession(ss, r.region)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to read participant from session")
|
||||
}
|
||||
|
||||
l := r.sessionHandler.Logger(stream.Context()).WithValues(
|
||||
"room", ss.RoomName,
|
||||
"participant", ss.Identity,
|
||||
"connID", ss.ConnectionId,
|
||||
)
|
||||
|
||||
stream.Hijack()
|
||||
sink := routing.NewSignalMessageSink(routing.SignalSinkParams[*rpc.RelaySignalResponse, *rpc.RelaySignalRequest]{
|
||||
Logger: l,
|
||||
Stream: stream,
|
||||
Config: r.config,
|
||||
Writer: signalResponseMessageWriter{},
|
||||
ConnectionID: livekit.ConnectionID(ss.ConnectionId),
|
||||
})
|
||||
reqChan := routing.NewDefaultMessageChannel(livekit.ConnectionID(ss.ConnectionId))
|
||||
|
||||
go func() {
|
||||
err := routing.CopySignalStreamToMessageChannel[*rpc.RelaySignalResponse, *rpc.RelaySignalRequest](
|
||||
stream,
|
||||
reqChan,
|
||||
signalRequestMessageReader{},
|
||||
r.config,
|
||||
)
|
||||
l.Debugw("signal stream closed", "error", err)
|
||||
|
||||
reqChan.Close()
|
||||
}()
|
||||
|
||||
// copy the context to prevent a race between the session handler closing
|
||||
// and the delivery of any parting messages from the client. take care to
|
||||
// copy the incoming rpc headers to avoid dropping any session vars.
|
||||
ctx := metadata.NewContextWithIncomingHeader(context.Background(), metadata.IncomingHeader(stream.Context()))
|
||||
err = r.sessionHandler.HandleSession(ctx, *pi, livekit.ConnectionID(ss.ConnectionId), reqChan, sink)
|
||||
if err != nil {
|
||||
sink.Close()
|
||||
l.Errorw("could not handle new participant", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type signalResponseMessageWriter struct{}
|
||||
|
||||
func (e signalResponseMessageWriter) Write(seq uint64, close bool, msgs []proto.Message) *rpc.RelaySignalResponse {
|
||||
r := &rpc.RelaySignalResponse{
|
||||
Seq: seq,
|
||||
Responses: make([]*livekit.SignalResponse, 0, len(msgs)),
|
||||
Close: close,
|
||||
}
|
||||
for _, m := range msgs {
|
||||
r.Responses = append(r.Responses, m.(*livekit.SignalResponse))
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
type signalRequestMessageReader struct{}
|
||||
|
||||
func (e signalRequestMessageReader) Read(rm *rpc.RelaySignalRequest) ([]proto.Message, error) {
|
||||
msgs := make([]proto.Message, 0, len(rm.Requests))
|
||||
for _, m := range rm.Requests {
|
||||
msgs = append(msgs, m)
|
||||
}
|
||||
return msgs, nil
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
// 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 service_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/protobuf/encoding/protojson"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"github.com/livekit/livekit-server/pkg/config"
|
||||
"github.com/livekit/livekit-server/pkg/routing"
|
||||
"github.com/livekit/livekit-server/pkg/service"
|
||||
"github.com/livekit/livekit-server/pkg/service/servicefakes"
|
||||
"github.com/livekit/livekit-server/pkg/telemetry/prometheus"
|
||||
"github.com/livekit/protocol/livekit"
|
||||
"github.com/livekit/protocol/logger"
|
||||
"github.com/livekit/psrpc"
|
||||
)
|
||||
|
||||
func init() {
|
||||
prometheus.Init("node", livekit.NodeType_CONTROLLER)
|
||||
}
|
||||
|
||||
func TestSignal(t *testing.T) {
|
||||
cfg := config.SignalRelayConfig{
|
||||
RetryTimeout: 30 * time.Second,
|
||||
MinRetryInterval: 500 * time.Millisecond,
|
||||
MaxRetryInterval: 5 * time.Second,
|
||||
StreamBufferSize: 1000,
|
||||
}
|
||||
|
||||
t.Run("messages are delivered", func(t *testing.T) {
|
||||
bus := psrpc.NewLocalMessageBus()
|
||||
|
||||
reqMessageIn := &livekit.SignalRequest{
|
||||
Message: &livekit.SignalRequest_Ping{Ping: 123},
|
||||
}
|
||||
resMessageIn := &livekit.SignalResponse{
|
||||
Message: &livekit.SignalResponse_Pong{Pong: 321},
|
||||
}
|
||||
|
||||
var reqMessageOut proto.Message
|
||||
var resErr error
|
||||
done := make(chan struct{})
|
||||
|
||||
client, err := routing.NewSignalClient(livekit.NodeID("node0"), bus, cfg)
|
||||
require.NoError(t, err)
|
||||
|
||||
handler := &servicefakes.FakeSessionHandler{
|
||||
LoggerStub: func(context.Context) logger.Logger { return logger.GetLogger() },
|
||||
HandleSessionStub: func(
|
||||
ctx context.Context,
|
||||
pi routing.ParticipantInit,
|
||||
connectionID livekit.ConnectionID,
|
||||
requestSource routing.MessageSource,
|
||||
responseSink routing.MessageSink,
|
||||
) error {
|
||||
go func() {
|
||||
reqMessageOut = <-requestSource.ReadChan()
|
||||
resErr = responseSink.WriteMessage(resMessageIn)
|
||||
responseSink.Close()
|
||||
close(done)
|
||||
}()
|
||||
return nil
|
||||
},
|
||||
}
|
||||
server, err := service.NewSignalServer(livekit.NodeID("node1"), "region", bus, cfg, handler)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = server.Start()
|
||||
require.NoError(t, err)
|
||||
|
||||
_, reqSink, resSource, err := client.StartParticipantSignal(
|
||||
context.Background(),
|
||||
livekit.RoomName("room1"),
|
||||
routing.ParticipantInit{},
|
||||
livekit.NodeID("node1"),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = reqSink.WriteMessage(reqMessageIn)
|
||||
require.NoError(t, err)
|
||||
|
||||
<-done
|
||||
require.True(t, proto.Equal(reqMessageIn, reqMessageOut), "req message should match %s %s", protojson.Format(reqMessageIn), protojson.Format(reqMessageOut))
|
||||
require.NoError(t, resErr)
|
||||
|
||||
resMessageOut := <-resSource.ReadChan()
|
||||
require.True(t, proto.Equal(resMessageIn, resMessageOut), "res message should match %s %s", protojson.Format(resMessageIn), protojson.Format(resMessageOut))
|
||||
})
|
||||
|
||||
t.Run("messages are delivered when session handler fails", func(t *testing.T) {
|
||||
bus := psrpc.NewLocalMessageBus()
|
||||
|
||||
resMessageIn := &livekit.SignalResponse{
|
||||
Message: &livekit.SignalResponse_Pong{Pong: 321},
|
||||
}
|
||||
|
||||
var resErr error
|
||||
done := make(chan struct{})
|
||||
|
||||
client, err := routing.NewSignalClient(livekit.NodeID("node0"), bus, cfg)
|
||||
require.NoError(t, err)
|
||||
|
||||
handler := &servicefakes.FakeSessionHandler{
|
||||
LoggerStub: func(context.Context) logger.Logger { return logger.GetLogger() },
|
||||
HandleSessionStub: func(
|
||||
ctx context.Context,
|
||||
pi routing.ParticipantInit,
|
||||
connectionID livekit.ConnectionID,
|
||||
requestSource routing.MessageSource,
|
||||
responseSink routing.MessageSink,
|
||||
) error {
|
||||
defer close(done)
|
||||
resErr = responseSink.WriteMessage(resMessageIn)
|
||||
return errors.New("start session failed")
|
||||
},
|
||||
}
|
||||
server, err := service.NewSignalServer(livekit.NodeID("node1"), "region", bus, cfg, handler)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = server.Start()
|
||||
require.NoError(t, err)
|
||||
|
||||
_, _, resSource, err := client.StartParticipantSignal(
|
||||
context.Background(),
|
||||
livekit.RoomName("room1"),
|
||||
routing.ParticipantInit{},
|
||||
livekit.NodeID("node1"),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
<-done
|
||||
require.NoError(t, resErr)
|
||||
|
||||
resMessageOut := <-resSource.ReadChan()
|
||||
require.True(t, proto.Equal(resMessageIn, resMessageOut), "res message should match %s %s", protojson.Format(resMessageIn), protojson.Format(resMessageOut))
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,568 @@
|
||||
// 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 service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/dennwc/iters"
|
||||
"github.com/twitchtv/twirp"
|
||||
"google.golang.org/protobuf/types/known/emptypb"
|
||||
|
||||
"github.com/livekit/protocol/livekit"
|
||||
"github.com/livekit/protocol/logger"
|
||||
"github.com/livekit/protocol/rpc"
|
||||
"github.com/livekit/protocol/sip"
|
||||
"github.com/livekit/protocol/utils"
|
||||
"github.com/livekit/protocol/utils/guid"
|
||||
"github.com/livekit/psrpc"
|
||||
|
||||
"github.com/livekit/livekit-server/pkg/config"
|
||||
"github.com/livekit/livekit-server/pkg/telemetry"
|
||||
)
|
||||
|
||||
type SIPService struct {
|
||||
conf *config.SIPConfig
|
||||
nodeID livekit.NodeID
|
||||
bus psrpc.MessageBus
|
||||
psrpcClient rpc.SIPClient
|
||||
store SIPStore
|
||||
roomService livekit.RoomService
|
||||
}
|
||||
|
||||
func NewSIPService(
|
||||
conf *config.SIPConfig,
|
||||
nodeID livekit.NodeID,
|
||||
bus psrpc.MessageBus,
|
||||
psrpcClient rpc.SIPClient,
|
||||
store SIPStore,
|
||||
rs livekit.RoomService,
|
||||
ts telemetry.TelemetryService,
|
||||
) *SIPService {
|
||||
return &SIPService{
|
||||
conf: conf,
|
||||
nodeID: nodeID,
|
||||
bus: bus,
|
||||
psrpcClient: psrpcClient,
|
||||
store: store,
|
||||
roomService: rs,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SIPService) CreateSIPTrunk(ctx context.Context, req *livekit.CreateSIPTrunkRequest) (*livekit.SIPTrunkInfo, error) {
|
||||
if err := EnsureSIPAdminPermission(ctx); err != nil {
|
||||
return nil, twirpAuthError(err)
|
||||
}
|
||||
if s.store == nil {
|
||||
return nil, ErrSIPNotConnected
|
||||
}
|
||||
if len(req.InboundNumbersRegex) != 0 {
|
||||
return nil, twirp.NewError(twirp.InvalidArgument, "Trunks with InboundNumbersRegex are deprecated. Use InboundNumbers instead.")
|
||||
}
|
||||
|
||||
// Keep ID empty, so that validation can print "<new>" instead of a non-existent ID in the error.
|
||||
info := &livekit.SIPTrunkInfo{
|
||||
InboundAddresses: req.InboundAddresses,
|
||||
OutboundAddress: req.OutboundAddress,
|
||||
OutboundNumber: req.OutboundNumber,
|
||||
InboundNumbers: req.InboundNumbers,
|
||||
InboundUsername: req.InboundUsername,
|
||||
InboundPassword: req.InboundPassword,
|
||||
OutboundUsername: req.OutboundUsername,
|
||||
OutboundPassword: req.OutboundPassword,
|
||||
Name: req.Name,
|
||||
Metadata: req.Metadata,
|
||||
}
|
||||
|
||||
// Validate all trunks including the new one first.
|
||||
it, err := ListSIPInboundTrunk(ctx, s.store, &livekit.ListSIPInboundTrunkRequest{}, info.AsInbound())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer it.Close()
|
||||
if err = sip.ValidateTrunksIter(it); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Now we can generate ID and store.
|
||||
info.SipTrunkId = guid.New(utils.SIPTrunkPrefix)
|
||||
if err := s.store.StoreSIPTrunk(ctx, info); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return info, nil
|
||||
}
|
||||
|
||||
func (s *SIPService) CreateSIPInboundTrunk(ctx context.Context, req *livekit.CreateSIPInboundTrunkRequest) (*livekit.SIPInboundTrunkInfo, error) {
|
||||
if err := EnsureSIPAdminPermission(ctx); err != nil {
|
||||
return nil, twirpAuthError(err)
|
||||
}
|
||||
if s.store == nil {
|
||||
return nil, ErrSIPNotConnected
|
||||
}
|
||||
if err := req.Validate(); err != nil {
|
||||
return nil, twirp.WrapError(twirp.NewError(twirp.InvalidArgument, err.Error()), err)
|
||||
}
|
||||
|
||||
info := req.Trunk
|
||||
if info.SipTrunkId != "" {
|
||||
return nil, twirp.NewError(twirp.InvalidArgument, "trunk ID must be empty")
|
||||
}
|
||||
AppendLogFields(ctx, "trunk", logger.Proto(req.Trunk))
|
||||
|
||||
// Keep ID empty still, so that validation can print "<new>" instead of a non-existent ID in the error.
|
||||
|
||||
// Validate all trunks including the new one first.
|
||||
it, err := ListSIPInboundTrunk(ctx, s.store, &livekit.ListSIPInboundTrunkRequest{
|
||||
Numbers: req.GetTrunk().GetNumbers(),
|
||||
}, info)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer it.Close()
|
||||
if err = sip.ValidateTrunksIter(it); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Now we can generate ID and store.
|
||||
info.SipTrunkId = guid.New(utils.SIPTrunkPrefix)
|
||||
if err := s.store.StoreSIPInboundTrunk(ctx, info); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return info, nil
|
||||
}
|
||||
|
||||
func (s *SIPService) CreateSIPOutboundTrunk(ctx context.Context, req *livekit.CreateSIPOutboundTrunkRequest) (*livekit.SIPOutboundTrunkInfo, error) {
|
||||
if err := EnsureSIPAdminPermission(ctx); err != nil {
|
||||
return nil, twirpAuthError(err)
|
||||
}
|
||||
if s.store == nil {
|
||||
return nil, ErrSIPNotConnected
|
||||
}
|
||||
if err := req.Validate(); err != nil {
|
||||
return nil, twirp.WrapError(twirp.NewError(twirp.InvalidArgument, err.Error()), err)
|
||||
}
|
||||
|
||||
info := req.Trunk
|
||||
if info.SipTrunkId != "" {
|
||||
return nil, twirp.NewError(twirp.InvalidArgument, "trunk ID must be empty")
|
||||
}
|
||||
AppendLogFields(ctx, "trunk", logger.Proto(req.Trunk))
|
||||
|
||||
// No additional validation needed for outbound.
|
||||
info.SipTrunkId = guid.New(utils.SIPTrunkPrefix)
|
||||
if err := s.store.StoreSIPOutboundTrunk(ctx, info); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return info, nil
|
||||
}
|
||||
|
||||
func (s *SIPService) GetSIPInboundTrunk(ctx context.Context, req *livekit.GetSIPInboundTrunkRequest) (*livekit.GetSIPInboundTrunkResponse, error) {
|
||||
if err := EnsureSIPAdminPermission(ctx); err != nil {
|
||||
return nil, twirpAuthError(err)
|
||||
}
|
||||
if s.store == nil {
|
||||
return nil, ErrSIPNotConnected
|
||||
}
|
||||
if req.SipTrunkId == "" {
|
||||
return nil, twirp.NewError(twirp.InvalidArgument, "trunk ID is required")
|
||||
}
|
||||
AppendLogFields(ctx, "trunkID", req.SipTrunkId)
|
||||
|
||||
trunk, err := s.store.LoadSIPInboundTrunk(ctx, req.SipTrunkId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &livekit.GetSIPInboundTrunkResponse{Trunk: trunk}, nil
|
||||
}
|
||||
|
||||
func (s *SIPService) GetSIPOutboundTrunk(ctx context.Context, req *livekit.GetSIPOutboundTrunkRequest) (*livekit.GetSIPOutboundTrunkResponse, error) {
|
||||
if err := EnsureSIPAdminPermission(ctx); err != nil {
|
||||
return nil, twirpAuthError(err)
|
||||
}
|
||||
if s.store == nil {
|
||||
return nil, ErrSIPNotConnected
|
||||
}
|
||||
if req.SipTrunkId == "" {
|
||||
return nil, twirp.NewError(twirp.InvalidArgument, "trunk ID is required")
|
||||
}
|
||||
AppendLogFields(ctx, "trunkID", req.SipTrunkId)
|
||||
|
||||
trunk, err := s.store.LoadSIPOutboundTrunk(ctx, req.SipTrunkId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &livekit.GetSIPOutboundTrunkResponse{Trunk: trunk}, nil
|
||||
}
|
||||
|
||||
// deprecated: ListSIPTrunk will be removed in the future
|
||||
func (s *SIPService) ListSIPTrunk(ctx context.Context, req *livekit.ListSIPTrunkRequest) (*livekit.ListSIPTrunkResponse, error) {
|
||||
if err := EnsureSIPAdminPermission(ctx); err != nil {
|
||||
return nil, twirpAuthError(err)
|
||||
}
|
||||
if s.store == nil {
|
||||
return nil, ErrSIPNotConnected
|
||||
}
|
||||
it := livekit.ListPageIter(s.store.ListSIPTrunk, req)
|
||||
defer it.Close()
|
||||
|
||||
items, err := iters.AllPages(ctx, it)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &livekit.ListSIPTrunkResponse{Items: items}, nil
|
||||
}
|
||||
|
||||
func ListSIPInboundTrunk(ctx context.Context, s SIPStore, req *livekit.ListSIPInboundTrunkRequest, add ...*livekit.SIPInboundTrunkInfo) (iters.Iter[*livekit.SIPInboundTrunkInfo], error) {
|
||||
if s == nil {
|
||||
return nil, ErrSIPNotConnected
|
||||
}
|
||||
pages := livekit.ListPageIter(s.ListSIPInboundTrunk, req)
|
||||
it := iters.PagesAsIter(ctx, pages)
|
||||
if len(add) != 0 {
|
||||
it = iters.MultiIter(true, it, iters.Slice(add))
|
||||
}
|
||||
return it, nil
|
||||
}
|
||||
|
||||
func ListSIPOutboundTrunk(ctx context.Context, s SIPStore, req *livekit.ListSIPOutboundTrunkRequest, add ...*livekit.SIPOutboundTrunkInfo) (iters.Iter[*livekit.SIPOutboundTrunkInfo], error) {
|
||||
if s == nil {
|
||||
return nil, ErrSIPNotConnected
|
||||
}
|
||||
pages := livekit.ListPageIter(s.ListSIPOutboundTrunk, req)
|
||||
it := iters.PagesAsIter(ctx, pages)
|
||||
if len(add) != 0 {
|
||||
it = iters.MultiIter(true, it, iters.Slice(add))
|
||||
}
|
||||
return it, nil
|
||||
}
|
||||
|
||||
func (s *SIPService) ListSIPInboundTrunk(ctx context.Context, req *livekit.ListSIPInboundTrunkRequest) (*livekit.ListSIPInboundTrunkResponse, error) {
|
||||
if err := EnsureSIPAdminPermission(ctx); err != nil {
|
||||
return nil, twirpAuthError(err)
|
||||
}
|
||||
if s.store == nil {
|
||||
return nil, ErrSIPNotConnected
|
||||
}
|
||||
it, err := ListSIPInboundTrunk(ctx, s.store, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer it.Close()
|
||||
|
||||
items, err := iters.All(it)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &livekit.ListSIPInboundTrunkResponse{Items: items}, nil
|
||||
}
|
||||
|
||||
func (s *SIPService) ListSIPOutboundTrunk(ctx context.Context, req *livekit.ListSIPOutboundTrunkRequest) (*livekit.ListSIPOutboundTrunkResponse, error) {
|
||||
if err := EnsureSIPAdminPermission(ctx); err != nil {
|
||||
return nil, twirpAuthError(err)
|
||||
}
|
||||
if s.store == nil {
|
||||
return nil, ErrSIPNotConnected
|
||||
}
|
||||
it, err := ListSIPOutboundTrunk(ctx, s.store, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer it.Close()
|
||||
|
||||
items, err := iters.All(it)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &livekit.ListSIPOutboundTrunkResponse{Items: items}, nil
|
||||
}
|
||||
|
||||
func (s *SIPService) DeleteSIPTrunk(ctx context.Context, req *livekit.DeleteSIPTrunkRequest) (*livekit.SIPTrunkInfo, error) {
|
||||
if err := EnsureSIPAdminPermission(ctx); err != nil {
|
||||
return nil, twirpAuthError(err)
|
||||
}
|
||||
if s.store == nil {
|
||||
return nil, ErrSIPNotConnected
|
||||
}
|
||||
if req.SipTrunkId == "" {
|
||||
return nil, twirp.NewError(twirp.InvalidArgument, "trunk ID is required")
|
||||
}
|
||||
|
||||
AppendLogFields(ctx, "trunkID", req.SipTrunkId)
|
||||
if err := s.store.DeleteSIPTrunk(ctx, req.SipTrunkId); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &livekit.SIPTrunkInfo{SipTrunkId: req.SipTrunkId}, nil
|
||||
}
|
||||
|
||||
func (s *SIPService) CreateSIPDispatchRule(ctx context.Context, req *livekit.CreateSIPDispatchRuleRequest) (*livekit.SIPDispatchRuleInfo, error) {
|
||||
if err := EnsureSIPAdminPermission(ctx); err != nil {
|
||||
return nil, twirpAuthError(err)
|
||||
}
|
||||
if s.store == nil {
|
||||
return nil, ErrSIPNotConnected
|
||||
}
|
||||
if err := req.Validate(); err != nil {
|
||||
return nil, twirp.WrapError(twirp.NewError(twirp.InvalidArgument, err.Error()), err)
|
||||
}
|
||||
|
||||
AppendLogFields(ctx,
|
||||
"request", logger.Proto(req),
|
||||
"trunkID", req.TrunkIds,
|
||||
)
|
||||
// Keep ID empty, so that validation can print "<new>" instead of a non-existent ID in the error.
|
||||
info := &livekit.SIPDispatchRuleInfo{
|
||||
Rule: req.Rule,
|
||||
TrunkIds: req.TrunkIds,
|
||||
InboundNumbers: req.InboundNumbers,
|
||||
HidePhoneNumber: req.HidePhoneNumber,
|
||||
Name: req.Name,
|
||||
Metadata: req.Metadata,
|
||||
Attributes: req.Attributes,
|
||||
RoomConfig: req.RoomConfig,
|
||||
}
|
||||
|
||||
// Validate all rules including the new one first.
|
||||
it, err := ListSIPDispatchRule(ctx, s.store, &livekit.ListSIPDispatchRuleRequest{
|
||||
TrunkIds: req.TrunkIds,
|
||||
}, info)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer it.Close()
|
||||
if _, err = sip.ValidateDispatchRulesIter(it); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Now we can generate ID and store.
|
||||
info.SipDispatchRuleId = guid.New(utils.SIPDispatchRulePrefix)
|
||||
if err := s.store.StoreSIPDispatchRule(ctx, info); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return info, nil
|
||||
}
|
||||
|
||||
func ListSIPDispatchRule(ctx context.Context, s SIPStore, req *livekit.ListSIPDispatchRuleRequest, add ...*livekit.SIPDispatchRuleInfo) (iters.Iter[*livekit.SIPDispatchRuleInfo], error) {
|
||||
if s == nil {
|
||||
return nil, ErrSIPNotConnected
|
||||
}
|
||||
pages := livekit.ListPageIter(s.ListSIPDispatchRule, req)
|
||||
it := iters.PagesAsIter(ctx, pages)
|
||||
if len(add) != 0 {
|
||||
it = iters.MultiIter(true, it, iters.Slice(add))
|
||||
}
|
||||
return it, nil
|
||||
}
|
||||
|
||||
func (s *SIPService) ListSIPDispatchRule(ctx context.Context, req *livekit.ListSIPDispatchRuleRequest) (*livekit.ListSIPDispatchRuleResponse, error) {
|
||||
if err := EnsureSIPAdminPermission(ctx); err != nil {
|
||||
return nil, twirpAuthError(err)
|
||||
}
|
||||
if s.store == nil {
|
||||
return nil, ErrSIPNotConnected
|
||||
}
|
||||
it, err := ListSIPDispatchRule(ctx, s.store, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer it.Close()
|
||||
|
||||
items, err := iters.All(it)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &livekit.ListSIPDispatchRuleResponse{Items: items}, nil
|
||||
}
|
||||
|
||||
func (s *SIPService) DeleteSIPDispatchRule(ctx context.Context, req *livekit.DeleteSIPDispatchRuleRequest) (*livekit.SIPDispatchRuleInfo, error) {
|
||||
if err := EnsureSIPAdminPermission(ctx); err != nil {
|
||||
return nil, twirpAuthError(err)
|
||||
}
|
||||
if s.store == nil {
|
||||
return nil, ErrSIPNotConnected
|
||||
}
|
||||
if req.SipDispatchRuleId == "" {
|
||||
return nil, twirp.NewError(twirp.InvalidArgument, "dispatch rule ID is required")
|
||||
}
|
||||
|
||||
info, err := s.store.LoadSIPDispatchRule(ctx, req.SipDispatchRuleId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = s.store.DeleteSIPDispatchRule(ctx, info.SipDispatchRuleId); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return info, nil
|
||||
}
|
||||
|
||||
func (s *SIPService) CreateSIPParticipant(ctx context.Context, req *livekit.CreateSIPParticipantRequest) (*livekit.SIPParticipantInfo, error) {
|
||||
unlikelyLogger := logger.GetLogger().WithUnlikelyValues(
|
||||
"room", req.RoomName,
|
||||
"sipTrunk", req.SipTrunkId,
|
||||
"toUser", req.SipCallTo,
|
||||
"participant", req.ParticipantIdentity,
|
||||
)
|
||||
AppendLogFields(ctx,
|
||||
"room", req.RoomName,
|
||||
"participant", req.ParticipantIdentity,
|
||||
"toUser", req.SipCallTo,
|
||||
"trunkID", req.SipTrunkId,
|
||||
)
|
||||
ireq, err := s.CreateSIPParticipantRequest(ctx, req, "", "", "", "")
|
||||
if err != nil {
|
||||
unlikelyLogger.Errorw("cannot create sip participant request", err)
|
||||
return nil, err
|
||||
}
|
||||
unlikelyLogger = unlikelyLogger.WithValues(
|
||||
"callID", ireq.SipCallId,
|
||||
"fromUser", ireq.Number,
|
||||
"toHost", ireq.Address,
|
||||
)
|
||||
AppendLogFields(ctx,
|
||||
"callID", ireq.SipCallId,
|
||||
"fromUser", ireq.Number,
|
||||
"toHost", ireq.Address,
|
||||
)
|
||||
|
||||
// CreateSIPParticipant will wait for LiveKit Participant to be created and that can take some time.
|
||||
// Thus, we must set a higher deadline for it, if it's not set already.
|
||||
timeout := 30 * time.Second
|
||||
if req.WaitUntilAnswered {
|
||||
timeout = 80 * time.Second
|
||||
}
|
||||
if deadline, ok := ctx.Deadline(); ok {
|
||||
timeout = time.Until(deadline)
|
||||
} else {
|
||||
var cancel func()
|
||||
ctx, cancel = context.WithTimeout(ctx, timeout)
|
||||
defer cancel()
|
||||
}
|
||||
resp, err := s.psrpcClient.CreateSIPParticipant(ctx, "", ireq, psrpc.WithRequestTimeout(timeout))
|
||||
if err != nil {
|
||||
unlikelyLogger.Errorw("cannot create sip participant", err)
|
||||
return nil, err
|
||||
}
|
||||
return &livekit.SIPParticipantInfo{
|
||||
ParticipantId: resp.ParticipantId,
|
||||
ParticipantIdentity: resp.ParticipantIdentity,
|
||||
RoomName: req.RoomName,
|
||||
SipCallId: ireq.SipCallId,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *SIPService) CreateSIPParticipantRequest(ctx context.Context, req *livekit.CreateSIPParticipantRequest, projectID, host, wsUrl, token string) (*rpc.InternalCreateSIPParticipantRequest, error) {
|
||||
if err := EnsureSIPCallPermission(ctx); err != nil {
|
||||
return nil, twirpAuthError(err)
|
||||
}
|
||||
if s.store == nil {
|
||||
return nil, ErrSIPNotConnected
|
||||
}
|
||||
callID := sip.NewCallID()
|
||||
log := logger.GetLogger().WithUnlikelyValues(
|
||||
"callID", callID,
|
||||
"room", req.RoomName,
|
||||
"sipTrunk", req.SipTrunkId,
|
||||
"toUser", req.SipCallTo,
|
||||
)
|
||||
if projectID != "" {
|
||||
log = log.WithValues("projectID", projectID)
|
||||
}
|
||||
|
||||
trunk, err := s.store.LoadSIPOutboundTrunk(ctx, req.SipTrunkId)
|
||||
if err != nil {
|
||||
log.Errorw("cannot get trunk to update sip participant", err)
|
||||
return nil, err
|
||||
}
|
||||
return rpc.NewCreateSIPParticipantRequest(projectID, callID, host, wsUrl, token, req, trunk)
|
||||
}
|
||||
|
||||
func (s *SIPService) TransferSIPParticipant(ctx context.Context, req *livekit.TransferSIPParticipantRequest) (*emptypb.Empty, error) {
|
||||
log := logger.GetLogger().WithUnlikelyValues(
|
||||
"room", req.RoomName,
|
||||
"participant", req.ParticipantIdentity,
|
||||
"transferTo", req.TransferTo,
|
||||
"playDialtone", req.PlayDialtone,
|
||||
)
|
||||
AppendLogFields(ctx,
|
||||
"room", req.RoomName,
|
||||
"participant", req.ParticipantIdentity,
|
||||
"transferTo", req.TransferTo,
|
||||
"playDialtone", req.PlayDialtone,
|
||||
)
|
||||
|
||||
ireq, err := s.transferSIPParticipantRequest(ctx, req)
|
||||
if err != nil {
|
||||
log.Errorw("cannot create transfer sip participant request", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
timeout := 30 * time.Second
|
||||
if deadline, ok := ctx.Deadline(); ok {
|
||||
timeout = time.Until(deadline)
|
||||
} else {
|
||||
var cancel func()
|
||||
ctx, cancel = context.WithTimeout(ctx, timeout)
|
||||
defer cancel()
|
||||
}
|
||||
_, err = s.psrpcClient.TransferSIPParticipant(ctx, ireq.SipCallId, ireq, psrpc.WithRequestTimeout(timeout))
|
||||
if err != nil {
|
||||
log.Errorw("cannot transfer sip participant", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &emptypb.Empty{}, nil
|
||||
}
|
||||
|
||||
func (s *SIPService) transferSIPParticipantRequest(ctx context.Context, req *livekit.TransferSIPParticipantRequest) (*rpc.InternalTransferSIPParticipantRequest, error) {
|
||||
if req.RoomName == "" {
|
||||
return nil, psrpc.NewErrorf(psrpc.InvalidArgument, "Missing room name")
|
||||
}
|
||||
|
||||
if req.ParticipantIdentity == "" {
|
||||
return nil, psrpc.NewErrorf(psrpc.InvalidArgument, "Missing participant identity")
|
||||
}
|
||||
|
||||
if err := EnsureSIPCallPermission(ctx); err != nil {
|
||||
return nil, twirpAuthError(err)
|
||||
}
|
||||
if err := EnsureAdminPermission(ctx, livekit.RoomName(req.RoomName)); err != nil {
|
||||
return nil, twirpAuthError(err)
|
||||
}
|
||||
|
||||
resp, err := s.roomService.GetParticipant(ctx, &livekit.RoomParticipantIdentity{
|
||||
Room: req.RoomName,
|
||||
Identity: req.ParticipantIdentity,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
callID, ok := resp.Attributes[livekit.AttrSIPCallID]
|
||||
if !ok {
|
||||
return nil, psrpc.NewErrorf(psrpc.InvalidArgument, "no SIP session associated with participant")
|
||||
}
|
||||
|
||||
return &rpc.InternalTransferSIPParticipantRequest{
|
||||
SipCallId: callID,
|
||||
TransferTo: req.TransferTo,
|
||||
PlayDialtone: req.PlayDialtone,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
// 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 service
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/jxskiss/base62"
|
||||
"github.com/pion/turn/v4"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/livekit/protocol/auth"
|
||||
"github.com/livekit/protocol/livekit"
|
||||
"github.com/livekit/protocol/logger"
|
||||
"github.com/livekit/protocol/logger/pionlogger"
|
||||
|
||||
"github.com/livekit/livekit-server/pkg/config"
|
||||
"github.com/livekit/livekit-server/pkg/telemetry"
|
||||
"github.com/livekit/livekit-server/pkg/telemetry/prometheus"
|
||||
)
|
||||
|
||||
const (
|
||||
LivekitRealm = "livekit"
|
||||
|
||||
allocateRetries = 50
|
||||
turnMinPort = 1024
|
||||
turnMaxPort = 30000
|
||||
)
|
||||
|
||||
func NewTurnServer(conf *config.Config, authHandler turn.AuthHandler, standalone bool) (*turn.Server, error) {
|
||||
turnConf := conf.TURN
|
||||
if !turnConf.Enabled {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if turnConf.TLSPort <= 0 && turnConf.UDPPort <= 0 {
|
||||
return nil, errors.New("invalid TURN ports")
|
||||
}
|
||||
|
||||
serverConfig := turn.ServerConfig{
|
||||
Realm: LivekitRealm,
|
||||
AuthHandler: authHandler,
|
||||
LoggerFactory: pionlogger.NewLoggerFactory(logger.GetLogger()),
|
||||
}
|
||||
var relayAddrGen turn.RelayAddressGenerator = &turn.RelayAddressGeneratorPortRange{
|
||||
RelayAddress: net.ParseIP(conf.RTC.NodeIP),
|
||||
Address: "0.0.0.0",
|
||||
MinPort: turnConf.RelayPortRangeStart,
|
||||
MaxPort: turnConf.RelayPortRangeEnd,
|
||||
MaxRetries: allocateRetries,
|
||||
}
|
||||
if standalone {
|
||||
relayAddrGen = telemetry.NewRelayAddressGenerator(relayAddrGen)
|
||||
}
|
||||
var logValues []interface{}
|
||||
|
||||
logValues = append(logValues, "turn.relay_range_start", turnConf.RelayPortRangeStart)
|
||||
logValues = append(logValues, "turn.relay_range_end", turnConf.RelayPortRangeEnd)
|
||||
|
||||
if turnConf.TLSPort > 0 {
|
||||
if turnConf.Domain == "" {
|
||||
return nil, errors.New("TURN domain required")
|
||||
}
|
||||
|
||||
if !IsValidDomain(turnConf.Domain) {
|
||||
return nil, errors.New("TURN domain is not correct")
|
||||
}
|
||||
|
||||
if !turnConf.ExternalTLS {
|
||||
cert, err := tls.LoadX509KeyPair(turnConf.CertFile, turnConf.KeyFile)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "TURN tls cert required")
|
||||
}
|
||||
|
||||
tlsListener, err := tls.Listen("tcp4", "0.0.0.0:"+strconv.Itoa(turnConf.TLSPort),
|
||||
&tls.Config{
|
||||
MinVersion: tls.VersionTLS12,
|
||||
Certificates: []tls.Certificate{cert},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not listen on TURN TCP port")
|
||||
}
|
||||
if standalone {
|
||||
tlsListener = telemetry.NewListener(tlsListener)
|
||||
}
|
||||
|
||||
listenerConfig := turn.ListenerConfig{
|
||||
Listener: tlsListener,
|
||||
RelayAddressGenerator: relayAddrGen,
|
||||
}
|
||||
serverConfig.ListenerConfigs = append(serverConfig.ListenerConfigs, listenerConfig)
|
||||
} else {
|
||||
tcpListener, err := net.Listen("tcp4", "0.0.0.0:"+strconv.Itoa(turnConf.TLSPort))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not listen on TURN TCP port")
|
||||
}
|
||||
if standalone {
|
||||
tcpListener = telemetry.NewListener(tcpListener)
|
||||
}
|
||||
|
||||
listenerConfig := turn.ListenerConfig{
|
||||
Listener: tcpListener,
|
||||
RelayAddressGenerator: relayAddrGen,
|
||||
}
|
||||
serverConfig.ListenerConfigs = append(serverConfig.ListenerConfigs, listenerConfig)
|
||||
}
|
||||
logValues = append(logValues, "turn.portTLS", turnConf.TLSPort, "turn.externalTLS", turnConf.ExternalTLS)
|
||||
}
|
||||
|
||||
if turnConf.UDPPort > 0 {
|
||||
udpListener, err := net.ListenPacket("udp4", "0.0.0.0:"+strconv.Itoa(turnConf.UDPPort))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not listen on TURN UDP port")
|
||||
}
|
||||
|
||||
if standalone {
|
||||
udpListener = telemetry.NewPacketConn(udpListener, prometheus.Incoming)
|
||||
}
|
||||
|
||||
packetConfig := turn.PacketConnConfig{
|
||||
PacketConn: udpListener,
|
||||
RelayAddressGenerator: relayAddrGen,
|
||||
}
|
||||
serverConfig.PacketConnConfigs = append(serverConfig.PacketConnConfigs, packetConfig)
|
||||
logValues = append(logValues, "turn.portUDP", turnConf.UDPPort)
|
||||
}
|
||||
|
||||
logger.Infow("Starting TURN server", logValues...)
|
||||
return turn.NewServer(serverConfig)
|
||||
}
|
||||
|
||||
func getTURNAuthHandlerFunc(handler *TURNAuthHandler) turn.AuthHandler {
|
||||
return handler.HandleAuth
|
||||
}
|
||||
|
||||
type TURNAuthHandler struct {
|
||||
keyProvider auth.KeyProvider
|
||||
}
|
||||
|
||||
func NewTURNAuthHandler(keyProvider auth.KeyProvider) *TURNAuthHandler {
|
||||
return &TURNAuthHandler{
|
||||
keyProvider: keyProvider,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *TURNAuthHandler) CreateUsername(apiKey string, pID livekit.ParticipantID) string {
|
||||
return base62.EncodeToString([]byte(fmt.Sprintf("%s|%s", apiKey, pID)))
|
||||
}
|
||||
|
||||
func (h *TURNAuthHandler) CreatePassword(apiKey string, pID livekit.ParticipantID) (string, error) {
|
||||
secret := h.keyProvider.GetSecret(apiKey)
|
||||
if secret == "" {
|
||||
return "", ErrInvalidAPIKey
|
||||
}
|
||||
keyInput := fmt.Sprintf("%s|%s", secret, pID)
|
||||
sum := sha256.Sum256([]byte(keyInput))
|
||||
return base62.EncodeToString(sum[:]), nil
|
||||
}
|
||||
|
||||
func (h *TURNAuthHandler) HandleAuth(username, realm string, srcAddr net.Addr) (key []byte, ok bool) {
|
||||
decoded, err := base62.DecodeString(username)
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
parts := strings.Split(string(decoded), "|")
|
||||
if len(parts) != 2 {
|
||||
return nil, false
|
||||
}
|
||||
password, err := h.CreatePassword(parts[0], livekit.ParticipantID(parts[1]))
|
||||
if err != nil {
|
||||
logger.Warnw("could not create TURN password", err, "username", username)
|
||||
return nil, false
|
||||
}
|
||||
return turn.GenerateAuthKey(username, LivekitRealm, password), true
|
||||
}
|
||||
@@ -0,0 +1,414 @@
|
||||
/*
|
||||
* Copyright 2022 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 service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/twitchtv/twirp"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/livekit/livekit-server/pkg/telemetry"
|
||||
"github.com/livekit/livekit-server/pkg/telemetry/prometheus"
|
||||
"github.com/livekit/livekit-server/pkg/utils"
|
||||
"github.com/livekit/protocol/livekit"
|
||||
)
|
||||
|
||||
type twirpRequestFields struct {
|
||||
service string
|
||||
method string
|
||||
error twirp.Error
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
|
||||
type twirpLoggerKey struct{}
|
||||
|
||||
// logging handling inspired by https://github.com/bakins/twirpzap
|
||||
// License: Apache-2.0
|
||||
func TwirpLogger() *twirp.ServerHooks {
|
||||
loggerPool := &sync.Pool{
|
||||
New: func() interface{} {
|
||||
return &twirpLogger{
|
||||
fieldsOrig: make([]interface{}, 0, 30),
|
||||
}
|
||||
},
|
||||
}
|
||||
return &twirp.ServerHooks{
|
||||
RequestReceived: func(ctx context.Context) (context.Context, error) {
|
||||
return loggerRequestReceived(ctx, loggerPool)
|
||||
},
|
||||
RequestRouted: loggerRequestRouted,
|
||||
Error: loggerErrorReceived,
|
||||
ResponseSent: func(ctx context.Context) {
|
||||
loggerResponseSent(ctx, loggerPool)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type twirpLogger struct {
|
||||
twirpRequestFields
|
||||
|
||||
fieldsOrig []interface{}
|
||||
fields []interface{}
|
||||
startedAt time.Time
|
||||
}
|
||||
|
||||
func AppendLogFields(ctx context.Context, fields ...interface{}) {
|
||||
r, ok := ctx.Value(twirpLoggerKey{}).(*twirpLogger)
|
||||
if !ok || r == nil {
|
||||
return
|
||||
}
|
||||
|
||||
r.fields = append(r.fields, fields...)
|
||||
}
|
||||
|
||||
func loggerRequestReceived(ctx context.Context, twirpLoggerPool *sync.Pool) (context.Context, error) {
|
||||
r := twirpLoggerPool.Get().(*twirpLogger)
|
||||
r.startedAt = time.Now()
|
||||
r.fields = r.fieldsOrig
|
||||
r.error = nil
|
||||
|
||||
if svc, ok := twirp.ServiceName(ctx); ok {
|
||||
r.service = svc
|
||||
r.fields = append(r.fields, "service", svc)
|
||||
}
|
||||
|
||||
return context.WithValue(ctx, twirpLoggerKey{}, r), nil
|
||||
}
|
||||
|
||||
func loggerRequestRouted(ctx context.Context) (context.Context, error) {
|
||||
if meth, ok := twirp.MethodName(ctx); ok {
|
||||
l, ok := ctx.Value(twirpLoggerKey{}).(*twirpLogger)
|
||||
if !ok || l == nil {
|
||||
return ctx, nil
|
||||
}
|
||||
l.method = meth
|
||||
l.fields = append(l.fields, "method", meth)
|
||||
}
|
||||
|
||||
return ctx, nil
|
||||
}
|
||||
|
||||
func loggerResponseSent(ctx context.Context, twirpLoggerPool *sync.Pool) {
|
||||
r, ok := ctx.Value(twirpLoggerKey{}).(*twirpLogger)
|
||||
if !ok || r == nil {
|
||||
return
|
||||
}
|
||||
|
||||
r.fields = append(r.fields, "duration", time.Since(r.startedAt))
|
||||
|
||||
if status, ok := twirp.StatusCode(ctx); ok {
|
||||
r.fields = append(r.fields, "status", status)
|
||||
}
|
||||
if r.error != nil {
|
||||
r.fields = append(r.fields, "error", r.error.Msg())
|
||||
r.fields = append(r.fields, "code", r.error.Code())
|
||||
}
|
||||
|
||||
serviceMethod := "API " + r.service + "." + r.method
|
||||
utils.GetLogger(ctx).WithComponent(utils.ComponentAPI).Infow(serviceMethod, r.fields...)
|
||||
|
||||
r.fields = r.fieldsOrig
|
||||
r.error = nil
|
||||
|
||||
twirpLoggerPool.Put(r)
|
||||
}
|
||||
|
||||
func loggerErrorReceived(ctx context.Context, e twirp.Error) context.Context {
|
||||
r, ok := ctx.Value(twirpLoggerKey{}).(*twirpLogger)
|
||||
if !ok || r == nil {
|
||||
return ctx
|
||||
}
|
||||
|
||||
r.error = e
|
||||
return ctx
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
|
||||
type statusReporterKey struct{}
|
||||
|
||||
func TwirpRequestStatusReporter() *twirp.ServerHooks {
|
||||
return &twirp.ServerHooks{
|
||||
RequestReceived: statusReporterRequestReceived,
|
||||
RequestRouted: statusReporterRequestRouted,
|
||||
Error: statusReporterErrorReceived,
|
||||
ResponseSent: statusReporterResponseSent,
|
||||
}
|
||||
}
|
||||
|
||||
func statusReporterRequestReceived(ctx context.Context) (context.Context, error) {
|
||||
r := &twirpRequestFields{}
|
||||
|
||||
if svc, ok := twirp.ServiceName(ctx); ok {
|
||||
r.service = svc
|
||||
}
|
||||
|
||||
return context.WithValue(ctx, statusReporterKey{}, r), nil
|
||||
}
|
||||
|
||||
func statusReporterRequestRouted(ctx context.Context) (context.Context, error) {
|
||||
if meth, ok := twirp.MethodName(ctx); ok {
|
||||
l, ok := ctx.Value(statusReporterKey{}).(*twirpRequestFields)
|
||||
if !ok || l == nil {
|
||||
return ctx, nil
|
||||
}
|
||||
l.method = meth
|
||||
}
|
||||
|
||||
return ctx, nil
|
||||
}
|
||||
|
||||
func statusReporterResponseSent(ctx context.Context) {
|
||||
r, ok := ctx.Value(statusReporterKey{}).(*twirpRequestFields)
|
||||
if !ok || r == nil {
|
||||
return
|
||||
}
|
||||
|
||||
var statusFamily string
|
||||
if statusCode, ok := twirp.StatusCode(ctx); ok {
|
||||
if status, err := strconv.Atoi(statusCode); err == nil {
|
||||
switch {
|
||||
case status >= 400 && status <= 499:
|
||||
statusFamily = "4xx"
|
||||
case status >= 500 && status <= 599:
|
||||
statusFamily = "5xx"
|
||||
default:
|
||||
statusFamily = statusCode
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var code twirp.ErrorCode
|
||||
if r.error != nil {
|
||||
code = r.error.Code()
|
||||
}
|
||||
|
||||
prometheus.TwirpRequestStatusCounter.WithLabelValues(r.service, r.method, statusFamily, string(code)).Add(1)
|
||||
}
|
||||
|
||||
func statusReporterErrorReceived(ctx context.Context, e twirp.Error) context.Context {
|
||||
r, ok := ctx.Value(statusReporterKey{}).(*twirpRequestFields)
|
||||
if !ok || r == nil {
|
||||
return ctx
|
||||
}
|
||||
|
||||
r.error = e
|
||||
return ctx
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
|
||||
type twirpTelemetryKey struct{}
|
||||
|
||||
func TwirpTelemetry(
|
||||
nodeID livekit.NodeID,
|
||||
getProjectID func(ctx context.Context) string,
|
||||
telemetry telemetry.TelemetryService,
|
||||
) *twirp.ServerHooks {
|
||||
return &twirp.ServerHooks{
|
||||
RequestReceived: telemetryRequestReceived,
|
||||
Error: telemetryErrorReceived,
|
||||
ResponseSent: func(ctx context.Context) {
|
||||
telemetryResponseSent(ctx, nodeID, getProjectID, telemetry)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func RecordRequest(ctx context.Context, request proto.Message) {
|
||||
if request == nil {
|
||||
return
|
||||
}
|
||||
|
||||
a, ok := ctx.Value(twirpTelemetryKey{}).(*livekit.APICallInfo)
|
||||
if !ok || a == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// capture request and extract common fields to top level as appropriate
|
||||
switch msg := request.(type) {
|
||||
case *livekit.CreateRoomRequest:
|
||||
a.Request = &livekit.APICallRequest{
|
||||
Message: &livekit.APICallRequest_CreateRoomRequest{
|
||||
CreateRoomRequest: msg,
|
||||
},
|
||||
}
|
||||
a.RoomName = msg.GetName()
|
||||
|
||||
case *livekit.ListRoomsRequest:
|
||||
a.Request = &livekit.APICallRequest{
|
||||
Message: &livekit.APICallRequest_ListRoomsRequest{
|
||||
ListRoomsRequest: msg,
|
||||
},
|
||||
}
|
||||
|
||||
case *livekit.DeleteRoomRequest:
|
||||
a.Request = &livekit.APICallRequest{
|
||||
Message: &livekit.APICallRequest_DeleteRoomRequest{
|
||||
DeleteRoomRequest: msg,
|
||||
},
|
||||
}
|
||||
a.RoomName = msg.GetRoom()
|
||||
|
||||
case *livekit.ListParticipantsRequest:
|
||||
a.Request = &livekit.APICallRequest{
|
||||
Message: &livekit.APICallRequest_ListParticipantsRequest{
|
||||
ListParticipantsRequest: msg,
|
||||
},
|
||||
}
|
||||
a.RoomName = msg.GetRoom()
|
||||
|
||||
case *livekit.RoomParticipantIdentity:
|
||||
a.Request = &livekit.APICallRequest{
|
||||
Message: &livekit.APICallRequest_RoomParticipantIdentity{
|
||||
RoomParticipantIdentity: msg,
|
||||
},
|
||||
}
|
||||
a.RoomName = msg.GetRoom()
|
||||
a.ParticipantIdentity = msg.GetIdentity()
|
||||
|
||||
case *livekit.MuteRoomTrackRequest:
|
||||
a.Request = &livekit.APICallRequest{
|
||||
Message: &livekit.APICallRequest_MuteRoomTrackRequest{
|
||||
MuteRoomTrackRequest: msg,
|
||||
},
|
||||
}
|
||||
a.RoomName = msg.GetRoom()
|
||||
a.ParticipantIdentity = msg.GetIdentity()
|
||||
a.TrackId = msg.GetTrackSid()
|
||||
|
||||
case *livekit.UpdateParticipantRequest:
|
||||
a.Request = &livekit.APICallRequest{
|
||||
Message: &livekit.APICallRequest_UpdateParticipantRequest{
|
||||
UpdateParticipantRequest: msg,
|
||||
},
|
||||
}
|
||||
a.RoomName = msg.GetRoom()
|
||||
a.ParticipantIdentity = msg.GetIdentity()
|
||||
|
||||
case *livekit.UpdateSubscriptionsRequest:
|
||||
a.Request = &livekit.APICallRequest{
|
||||
Message: &livekit.APICallRequest_UpdateSubscriptionsRequest{
|
||||
UpdateSubscriptionsRequest: msg,
|
||||
},
|
||||
}
|
||||
a.RoomName = msg.GetRoom()
|
||||
a.ParticipantIdentity = msg.GetIdentity()
|
||||
|
||||
case *livekit.SendDataRequest:
|
||||
a.Request = &livekit.APICallRequest{
|
||||
Message: &livekit.APICallRequest_SendDataRequest{
|
||||
SendDataRequest: msg,
|
||||
},
|
||||
}
|
||||
a.RoomName = msg.GetRoom()
|
||||
|
||||
case *livekit.UpdateRoomMetadataRequest:
|
||||
a.Request = &livekit.APICallRequest{
|
||||
Message: &livekit.APICallRequest_UpdateRoomMetadataRequest{
|
||||
UpdateRoomMetadataRequest: msg,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func RecordResponse(ctx context.Context, response proto.Message) {
|
||||
if response == nil {
|
||||
return
|
||||
}
|
||||
|
||||
a, ok := ctx.Value(twirpTelemetryKey{}).(*livekit.APICallInfo)
|
||||
if !ok || a == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// extract common fields to top level as appropriate
|
||||
switch msg := response.(type) {
|
||||
case *livekit.Room:
|
||||
a.RoomId = msg.GetSid()
|
||||
|
||||
case *livekit.ParticipantInfo:
|
||||
a.ParticipantId = msg.GetSid()
|
||||
}
|
||||
}
|
||||
|
||||
func telemetryRequestReceived(ctx context.Context) (context.Context, error) {
|
||||
a := &livekit.APICallInfo{}
|
||||
a.StartedAt = timestamppb.Now()
|
||||
|
||||
if svc, ok := twirp.ServiceName(ctx); ok {
|
||||
a.Service = svc
|
||||
}
|
||||
|
||||
return context.WithValue(ctx, twirpTelemetryKey{}, a), nil
|
||||
}
|
||||
|
||||
func telemetryRequestRouted(ctx context.Context) (context.Context, error) {
|
||||
if meth, ok := twirp.MethodName(ctx); ok {
|
||||
a, ok := ctx.Value(twirpTelemetryKey{}).(*livekit.APICallInfo)
|
||||
if !ok || a == nil {
|
||||
return ctx, nil
|
||||
}
|
||||
a.Method = meth
|
||||
}
|
||||
|
||||
return ctx, nil
|
||||
}
|
||||
|
||||
func telemetryResponseSent(
|
||||
ctx context.Context,
|
||||
nodeID livekit.NodeID,
|
||||
getProjectID func(ctx context.Context) string,
|
||||
telemetry telemetry.TelemetryService,
|
||||
) {
|
||||
a, ok := ctx.Value(twirpTelemetryKey{}).(*livekit.APICallInfo)
|
||||
if !ok || a == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if getProjectID != nil {
|
||||
a.ProjectId = getProjectID(ctx)
|
||||
}
|
||||
a.NodeId = string(nodeID)
|
||||
if statusCode, ok := twirp.StatusCode(ctx); ok {
|
||||
if status, err := strconv.Atoi(statusCode); err == nil {
|
||||
a.Status = int32(status)
|
||||
}
|
||||
}
|
||||
a.DurationNs = time.Since(a.StartedAt.AsTime()).Nanoseconds()
|
||||
if telemetry != nil {
|
||||
telemetry.APICall(ctx, a)
|
||||
}
|
||||
}
|
||||
|
||||
func telemetryErrorReceived(ctx context.Context, e twirp.Error) context.Context {
|
||||
a, ok := ctx.Value(twirpTelemetryKey{}).(*livekit.APICallInfo)
|
||||
if !ok || a == nil {
|
||||
return ctx
|
||||
}
|
||||
|
||||
a.TwirpErrorCode = string(e.Code())
|
||||
a.TwirpErrorMessage = e.Msg()
|
||||
return ctx
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2024 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 service
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/twitchtv/twirp"
|
||||
)
|
||||
|
||||
func TestConvertErrToTwirp(t *testing.T) {
|
||||
t.Run("handles not found", func(t *testing.T) {
|
||||
err := ErrRoomNotFound
|
||||
var tErr twirp.Error
|
||||
require.True(t, errors.As(err, &tErr))
|
||||
require.Equal(t, twirp.NotFound, tErr.Code())
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
// 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 service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/livekit/protocol/livekit"
|
||||
"github.com/livekit/protocol/logger"
|
||||
)
|
||||
|
||||
func handleError(w http.ResponseWriter, r *http.Request, status int, err error, keysAndValues ...interface{}) {
|
||||
keysAndValues = append(keysAndValues, "status", status)
|
||||
if r != nil && r.URL != nil {
|
||||
keysAndValues = append(keysAndValues, "method", r.Method, "path", r.URL.Path)
|
||||
}
|
||||
if !errors.Is(err, context.Canceled) && !errors.Is(r.Context().Err(), context.Canceled) {
|
||||
logger.GetLogger().WithCallDepth(1).Warnw("error handling request", err, keysAndValues...)
|
||||
}
|
||||
w.WriteHeader(status)
|
||||
_, _ = w.Write([]byte(err.Error()))
|
||||
}
|
||||
|
||||
func boolValue(s string) bool {
|
||||
return s == "1" || s == "true"
|
||||
}
|
||||
|
||||
func RemoveDoubleSlashes(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
|
||||
if strings.HasPrefix(r.URL.Path, "//") {
|
||||
r.URL.Path = r.URL.Path[1:]
|
||||
}
|
||||
next(w, r)
|
||||
}
|
||||
|
||||
func IsValidDomain(domain string) bool {
|
||||
domainRegexp := regexp.MustCompile(`^(?i)[a-z0-9-]+(\.[a-z0-9-]+)+\.?$`)
|
||||
return domainRegexp.MatchString(domain)
|
||||
}
|
||||
|
||||
func GetClientIP(r *http.Request) string {
|
||||
// CF proxy typically is first thing the user reaches
|
||||
if ip := r.Header.Get("CF-Connecting-IP"); ip != "" {
|
||||
return ip
|
||||
}
|
||||
if ip := r.Header.Get("X-Forwarded-For"); ip != "" {
|
||||
return ip
|
||||
}
|
||||
if ip := r.Header.Get("X-Real-IP"); ip != "" {
|
||||
return ip
|
||||
}
|
||||
ip, _, _ := net.SplitHostPort(r.RemoteAddr)
|
||||
return ip
|
||||
}
|
||||
|
||||
func SetRoomConfiguration(createRequest *livekit.CreateRoomRequest, conf *livekit.RoomConfiguration) {
|
||||
if conf == nil {
|
||||
return
|
||||
}
|
||||
createRequest.Agents = conf.Agents
|
||||
createRequest.Egress = conf.Egress
|
||||
createRequest.EmptyTimeout = conf.EmptyTimeout
|
||||
createRequest.DepartureTimeout = conf.DepartureTimeout
|
||||
createRequest.MaxParticipants = conf.MaxParticipants
|
||||
createRequest.MinPlayoutDelay = conf.MinPlayoutDelay
|
||||
createRequest.MaxPlayoutDelay = conf.MaxPlayoutDelay
|
||||
createRequest.SyncStreams = conf.SyncStreams
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
// 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 service_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/livekit/livekit-server/pkg/service"
|
||||
)
|
||||
|
||||
func redisClientDocker(t testing.TB) *redis.Client {
|
||||
addr := runRedis(t)
|
||||
cli := redis.NewClient(&redis.Options{
|
||||
Addr: addr,
|
||||
})
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if err := cli.Ping(ctx).Err(); err != nil {
|
||||
_ = cli.Close()
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
_ = cli.Close()
|
||||
})
|
||||
return cli
|
||||
}
|
||||
|
||||
func redisClient(t testing.TB) *redis.Client {
|
||||
cli := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
})
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
err := cli.Ping(ctx).Err()
|
||||
if err == nil {
|
||||
t.Cleanup(func() {
|
||||
_ = cli.Close()
|
||||
})
|
||||
return cli
|
||||
}
|
||||
_ = cli.Close()
|
||||
t.Logf("local redis not available: %v", err)
|
||||
|
||||
t.Logf("starting redis in docker")
|
||||
return redisClientDocker(t)
|
||||
}
|
||||
|
||||
func TestIsValidDomain(t *testing.T) {
|
||||
list := map[string]bool{
|
||||
"turn.myhost.com": true,
|
||||
"turn.google.com": true,
|
||||
"https://host.com": false,
|
||||
"turn://host.com": false,
|
||||
}
|
||||
for key, result := range list {
|
||||
service.IsValidDomain(key)
|
||||
require.Equal(t, service.IsValidDomain(key), result)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,271 @@
|
||||
// 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.
|
||||
|
||||
//go:build wireinject
|
||||
// +build wireinject
|
||||
|
||||
package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/google/wire"
|
||||
"github.com/pion/turn/v4"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/livekit/livekit-server/pkg/agent"
|
||||
"github.com/livekit/livekit-server/pkg/clientconfiguration"
|
||||
"github.com/livekit/livekit-server/pkg/config"
|
||||
"github.com/livekit/livekit-server/pkg/routing"
|
||||
"github.com/livekit/livekit-server/pkg/sfu"
|
||||
"github.com/livekit/livekit-server/pkg/telemetry"
|
||||
"github.com/livekit/protocol/auth"
|
||||
"github.com/livekit/protocol/livekit"
|
||||
"github.com/livekit/protocol/logger"
|
||||
redisLiveKit "github.com/livekit/protocol/redis"
|
||||
"github.com/livekit/protocol/rpc"
|
||||
"github.com/livekit/protocol/utils"
|
||||
"github.com/livekit/protocol/webhook"
|
||||
"github.com/livekit/psrpc"
|
||||
)
|
||||
|
||||
func InitializeServer(conf *config.Config, currentNode routing.LocalNode) (*LivekitServer, error) {
|
||||
wire.Build(
|
||||
getNodeID,
|
||||
createRedisClient,
|
||||
createStore,
|
||||
wire.Bind(new(ServiceStore), new(ObjectStore)),
|
||||
createKeyProvider,
|
||||
createWebhookNotifier,
|
||||
createClientConfiguration,
|
||||
createForwardStats,
|
||||
routing.CreateRouter,
|
||||
getLimitConf,
|
||||
config.DefaultAPIConfig,
|
||||
wire.Bind(new(routing.MessageRouter), new(routing.Router)),
|
||||
wire.Bind(new(livekit.RoomService), new(*RoomService)),
|
||||
telemetry.NewAnalyticsService,
|
||||
telemetry.NewTelemetryService,
|
||||
getMessageBus,
|
||||
NewIOInfoService,
|
||||
wire.Bind(new(IOClient), new(*IOInfoService)),
|
||||
rpc.NewEgressClient,
|
||||
rpc.NewIngressClient,
|
||||
getEgressStore,
|
||||
NewEgressLauncher,
|
||||
NewEgressService,
|
||||
getIngressStore,
|
||||
getIngressConfig,
|
||||
NewIngressService,
|
||||
rpc.NewSIPClient,
|
||||
getSIPStore,
|
||||
getSIPConfig,
|
||||
NewSIPService,
|
||||
NewRoomAllocator,
|
||||
NewRoomService,
|
||||
NewRTCService,
|
||||
NewAgentService,
|
||||
NewAgentDispatchService,
|
||||
agent.NewAgentClient,
|
||||
getAgentStore,
|
||||
getSignalRelayConfig,
|
||||
NewDefaultSignalServer,
|
||||
routing.NewSignalClient,
|
||||
getRoomConfig,
|
||||
routing.NewRoomManagerClient,
|
||||
rpc.NewKeepalivePubSub,
|
||||
getPSRPCConfig,
|
||||
getPSRPCClientParams,
|
||||
rpc.NewTopicFormatter,
|
||||
rpc.NewTypedRoomClient,
|
||||
rpc.NewTypedParticipantClient,
|
||||
rpc.NewTypedAgentDispatchInternalClient,
|
||||
NewLocalRoomManager,
|
||||
NewTURNAuthHandler,
|
||||
getTURNAuthHandlerFunc,
|
||||
newInProcessTurnServer,
|
||||
utils.NewDefaultTimedVersionGenerator,
|
||||
NewLivekitServer,
|
||||
)
|
||||
return &LivekitServer{}, nil
|
||||
}
|
||||
|
||||
func InitializeRouter(conf *config.Config, currentNode routing.LocalNode) (routing.Router, error) {
|
||||
wire.Build(
|
||||
createRedisClient,
|
||||
getNodeID,
|
||||
getMessageBus,
|
||||
getSignalRelayConfig,
|
||||
getPSRPCConfig,
|
||||
getPSRPCClientParams,
|
||||
routing.NewSignalClient,
|
||||
getRoomConfig,
|
||||
routing.NewRoomManagerClient,
|
||||
rpc.NewKeepalivePubSub,
|
||||
routing.CreateRouter,
|
||||
)
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func getNodeID(currentNode routing.LocalNode) livekit.NodeID {
|
||||
return currentNode.NodeID()
|
||||
}
|
||||
|
||||
func createKeyProvider(conf *config.Config) (auth.KeyProvider, error) {
|
||||
// prefer keyfile if set
|
||||
if conf.KeyFile != "" {
|
||||
var otherFilter os.FileMode = 0007
|
||||
if st, err := os.Stat(conf.KeyFile); err != nil {
|
||||
return nil, err
|
||||
} else if st.Mode().Perm()&otherFilter != 0000 {
|
||||
return nil, fmt.Errorf("key file others permissions must be set to 0")
|
||||
}
|
||||
f, err := os.Open(conf.KeyFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
_ = f.Close()
|
||||
}()
|
||||
decoder := yaml.NewDecoder(f)
|
||||
if err = decoder.Decode(conf.Keys); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if len(conf.Keys) == 0 {
|
||||
return nil, errors.New("one of key-file or keys must be provided in order to support a secure installation")
|
||||
}
|
||||
|
||||
return auth.NewFileBasedKeyProviderFromMap(conf.Keys), nil
|
||||
}
|
||||
|
||||
func createWebhookNotifier(conf *config.Config, provider auth.KeyProvider) (webhook.QueuedNotifier, error) {
|
||||
wc := conf.WebHook
|
||||
if len(wc.URLs) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
secret := provider.GetSecret(wc.APIKey)
|
||||
if secret == "" {
|
||||
return nil, ErrWebHookMissingAPIKey
|
||||
}
|
||||
|
||||
return webhook.NewDefaultNotifier(wc.APIKey, secret, wc.URLs), nil
|
||||
}
|
||||
|
||||
func createRedisClient(conf *config.Config) (redis.UniversalClient, error) {
|
||||
if !conf.Redis.IsConfigured() {
|
||||
return nil, nil
|
||||
}
|
||||
return redisLiveKit.GetRedisClient(&conf.Redis)
|
||||
}
|
||||
|
||||
func createStore(rc redis.UniversalClient) ObjectStore {
|
||||
if rc != nil {
|
||||
return NewRedisStore(rc)
|
||||
}
|
||||
return NewLocalStore()
|
||||
}
|
||||
|
||||
func getMessageBus(rc redis.UniversalClient) psrpc.MessageBus {
|
||||
if rc == nil {
|
||||
return psrpc.NewLocalMessageBus()
|
||||
}
|
||||
return psrpc.NewRedisMessageBus(rc)
|
||||
}
|
||||
|
||||
func getEgressStore(s ObjectStore) EgressStore {
|
||||
switch store := s.(type) {
|
||||
case *RedisStore:
|
||||
return store
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func getIngressStore(s ObjectStore) IngressStore {
|
||||
switch store := s.(type) {
|
||||
case *RedisStore:
|
||||
return store
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func getAgentStore(s ObjectStore) AgentStore {
|
||||
switch store := s.(type) {
|
||||
case *RedisStore:
|
||||
return store
|
||||
case *LocalStore:
|
||||
return store
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func getIngressConfig(conf *config.Config) *config.IngressConfig {
|
||||
return &conf.Ingress
|
||||
}
|
||||
|
||||
func getSIPStore(s ObjectStore) SIPStore {
|
||||
switch store := s.(type) {
|
||||
case *RedisStore:
|
||||
return store
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func getSIPConfig(conf *config.Config) *config.SIPConfig {
|
||||
return &conf.SIP
|
||||
}
|
||||
|
||||
func createClientConfiguration() clientconfiguration.ClientConfigurationManager {
|
||||
return clientconfiguration.NewStaticClientConfigurationManager(clientconfiguration.StaticConfigurations)
|
||||
}
|
||||
|
||||
func getLimitConf(config *config.Config) config.LimitConfig {
|
||||
return config.Limit
|
||||
}
|
||||
|
||||
func getRoomConfig(config *config.Config) config.RoomConfig {
|
||||
return config.Room
|
||||
}
|
||||
|
||||
func getSignalRelayConfig(config *config.Config) config.SignalRelayConfig {
|
||||
return config.SignalRelay
|
||||
}
|
||||
|
||||
func getPSRPCConfig(config *config.Config) rpc.PSRPCConfig {
|
||||
return config.PSRPC
|
||||
}
|
||||
|
||||
func getPSRPCClientParams(config rpc.PSRPCConfig, bus psrpc.MessageBus) rpc.ClientParams {
|
||||
return rpc.NewClientParams(config, bus, logger.GetLogger(), rpc.PSRPCMetricsObserver{})
|
||||
}
|
||||
|
||||
func createForwardStats(conf *config.Config) *sfu.ForwardStats {
|
||||
if conf.RTC.ForwardStats.SummaryInterval == 0 || conf.RTC.ForwardStats.ReportInterval == 0 || conf.RTC.ForwardStats.ReportWindow == 0 {
|
||||
return nil
|
||||
}
|
||||
return sfu.NewForwardStats(conf.RTC.ForwardStats.SummaryInterval, conf.RTC.ForwardStats.ReportInterval, conf.RTC.ForwardStats.ReportWindow)
|
||||
}
|
||||
|
||||
func newInProcessTurnServer(conf *config.Config, authHandler turn.AuthHandler) (*turn.Server, error) {
|
||||
return NewTurnServer(conf, authHandler, false)
|
||||
}
|
||||
@@ -0,0 +1,331 @@
|
||||
// Code generated by Wire. DO NOT EDIT.
|
||||
|
||||
//go:generate go run -mod=mod github.com/google/wire/cmd/wire
|
||||
//go:build !wireinject
|
||||
// +build !wireinject
|
||||
|
||||
package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/livekit/livekit-server/pkg/agent"
|
||||
"github.com/livekit/livekit-server/pkg/clientconfiguration"
|
||||
"github.com/livekit/livekit-server/pkg/config"
|
||||
"github.com/livekit/livekit-server/pkg/routing"
|
||||
"github.com/livekit/livekit-server/pkg/sfu"
|
||||
"github.com/livekit/livekit-server/pkg/telemetry"
|
||||
"github.com/livekit/protocol/auth"
|
||||
"github.com/livekit/protocol/livekit"
|
||||
"github.com/livekit/protocol/logger"
|
||||
redis2 "github.com/livekit/protocol/redis"
|
||||
"github.com/livekit/protocol/rpc"
|
||||
"github.com/livekit/protocol/utils"
|
||||
"github.com/livekit/protocol/webhook"
|
||||
"github.com/livekit/psrpc"
|
||||
"github.com/pion/turn/v4"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"gopkg.in/yaml.v3"
|
||||
"os"
|
||||
)
|
||||
|
||||
import (
|
||||
_ "net/http/pprof"
|
||||
)
|
||||
|
||||
// Injectors from wire.go:
|
||||
|
||||
func InitializeServer(conf *config.Config, currentNode routing.LocalNode) (*LivekitServer, error) {
|
||||
limitConfig := getLimitConf(conf)
|
||||
apiConfig := config.DefaultAPIConfig()
|
||||
universalClient, err := createRedisClient(conf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nodeID := getNodeID(currentNode)
|
||||
messageBus := getMessageBus(universalClient)
|
||||
signalRelayConfig := getSignalRelayConfig(conf)
|
||||
signalClient, err := routing.NewSignalClient(nodeID, messageBus, signalRelayConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
psrpcConfig := getPSRPCConfig(conf)
|
||||
clientParams := getPSRPCClientParams(psrpcConfig, messageBus)
|
||||
roomConfig := getRoomConfig(conf)
|
||||
roomManagerClient, err := routing.NewRoomManagerClient(clientParams, roomConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
keepalivePubSub, err := rpc.NewKeepalivePubSub(clientParams)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
router := routing.CreateRouter(universalClient, currentNode, signalClient, roomManagerClient, keepalivePubSub)
|
||||
objectStore := createStore(universalClient)
|
||||
roomAllocator, err := NewRoomAllocator(conf, router, objectStore)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
egressClient, err := rpc.NewEgressClient(clientParams)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
egressStore := getEgressStore(objectStore)
|
||||
ingressStore := getIngressStore(objectStore)
|
||||
sipStore := getSIPStore(objectStore)
|
||||
keyProvider, err := createKeyProvider(conf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
queuedNotifier, err := createWebhookNotifier(conf, keyProvider)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
analyticsService := telemetry.NewAnalyticsService(conf, currentNode)
|
||||
telemetryService := telemetry.NewTelemetryService(queuedNotifier, analyticsService)
|
||||
ioInfoService, err := NewIOInfoService(messageBus, egressStore, ingressStore, sipStore, telemetryService)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rtcEgressLauncher := NewEgressLauncher(egressClient, ioInfoService)
|
||||
topicFormatter := rpc.NewTopicFormatter()
|
||||
roomClient, err := rpc.NewTypedRoomClient(clientParams)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
participantClient, err := rpc.NewTypedParticipantClient(clientParams)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
roomService, err := NewRoomService(limitConfig, apiConfig, router, roomAllocator, objectStore, rtcEgressLauncher, topicFormatter, roomClient, participantClient)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
agentDispatchInternalClient, err := rpc.NewTypedAgentDispatchInternalClient(clientParams)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
agentDispatchService := NewAgentDispatchService(agentDispatchInternalClient, topicFormatter, roomAllocator, router)
|
||||
egressService := NewEgressService(egressClient, rtcEgressLauncher, objectStore, ioInfoService, roomService)
|
||||
ingressConfig := getIngressConfig(conf)
|
||||
ingressClient, err := rpc.NewIngressClient(clientParams)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ingressService := NewIngressService(ingressConfig, nodeID, messageBus, ingressClient, ingressStore, ioInfoService, telemetryService)
|
||||
sipConfig := getSIPConfig(conf)
|
||||
sipClient, err := rpc.NewSIPClient(messageBus)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sipService := NewSIPService(sipConfig, nodeID, messageBus, sipClient, sipStore, roomService, telemetryService)
|
||||
rtcService := NewRTCService(conf, roomAllocator, objectStore, router, currentNode, telemetryService)
|
||||
agentService, err := NewAgentService(conf, currentNode, messageBus, keyProvider)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
clientConfigurationManager := createClientConfiguration()
|
||||
client, err := agent.NewAgentClient(messageBus)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
agentStore := getAgentStore(objectStore)
|
||||
timedVersionGenerator := utils.NewDefaultTimedVersionGenerator()
|
||||
turnAuthHandler := NewTURNAuthHandler(keyProvider)
|
||||
forwardStats := createForwardStats(conf)
|
||||
roomManager, err := NewLocalRoomManager(conf, objectStore, currentNode, router, roomAllocator, telemetryService, clientConfigurationManager, client, agentStore, rtcEgressLauncher, timedVersionGenerator, turnAuthHandler, messageBus, forwardStats)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
signalServer, err := NewDefaultSignalServer(currentNode, messageBus, signalRelayConfig, router, roomManager)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
authHandler := getTURNAuthHandlerFunc(turnAuthHandler)
|
||||
server, err := newInProcessTurnServer(conf, authHandler)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
livekitServer, err := NewLivekitServer(conf, roomService, agentDispatchService, egressService, ingressService, sipService, ioInfoService, rtcService, agentService, keyProvider, router, roomManager, signalServer, server, currentNode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return livekitServer, nil
|
||||
}
|
||||
|
||||
func InitializeRouter(conf *config.Config, currentNode routing.LocalNode) (routing.Router, error) {
|
||||
universalClient, err := createRedisClient(conf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nodeID := getNodeID(currentNode)
|
||||
messageBus := getMessageBus(universalClient)
|
||||
signalRelayConfig := getSignalRelayConfig(conf)
|
||||
signalClient, err := routing.NewSignalClient(nodeID, messageBus, signalRelayConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
psrpcConfig := getPSRPCConfig(conf)
|
||||
clientParams := getPSRPCClientParams(psrpcConfig, messageBus)
|
||||
roomConfig := getRoomConfig(conf)
|
||||
roomManagerClient, err := routing.NewRoomManagerClient(clientParams, roomConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
keepalivePubSub, err := rpc.NewKeepalivePubSub(clientParams)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
router := routing.CreateRouter(universalClient, currentNode, signalClient, roomManagerClient, keepalivePubSub)
|
||||
return router, nil
|
||||
}
|
||||
|
||||
// wire.go:
|
||||
|
||||
func getNodeID(currentNode routing.LocalNode) livekit.NodeID {
|
||||
return currentNode.NodeID()
|
||||
}
|
||||
|
||||
func createKeyProvider(conf *config.Config) (auth.KeyProvider, error) {
|
||||
|
||||
if conf.KeyFile != "" {
|
||||
var otherFilter os.FileMode = 0007
|
||||
if st, err := os.Stat(conf.KeyFile); err != nil {
|
||||
return nil, err
|
||||
} else if st.Mode().Perm()&otherFilter != 0000 {
|
||||
return nil, fmt.Errorf("key file others permissions must be set to 0")
|
||||
}
|
||||
f, err := os.Open(conf.KeyFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
_ = f.Close()
|
||||
}()
|
||||
decoder := yaml.NewDecoder(f)
|
||||
if err = decoder.Decode(conf.Keys); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if len(conf.Keys) == 0 {
|
||||
return nil, errors.New("one of key-file or keys must be provided in order to support a secure installation")
|
||||
}
|
||||
|
||||
return auth.NewFileBasedKeyProviderFromMap(conf.Keys), nil
|
||||
}
|
||||
|
||||
func createWebhookNotifier(conf *config.Config, provider auth.KeyProvider) (webhook.QueuedNotifier, error) {
|
||||
wc := conf.WebHook
|
||||
if len(wc.URLs) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
secret := provider.GetSecret(wc.APIKey)
|
||||
if secret == "" {
|
||||
return nil, ErrWebHookMissingAPIKey
|
||||
}
|
||||
|
||||
return webhook.NewDefaultNotifier(wc.APIKey, secret, wc.URLs), nil
|
||||
}
|
||||
|
||||
func createRedisClient(conf *config.Config) (redis.UniversalClient, error) {
|
||||
if !conf.Redis.IsConfigured() {
|
||||
return nil, nil
|
||||
}
|
||||
return redis2.GetRedisClient(&conf.Redis)
|
||||
}
|
||||
|
||||
func createStore(rc redis.UniversalClient) ObjectStore {
|
||||
if rc != nil {
|
||||
return NewRedisStore(rc)
|
||||
}
|
||||
return NewLocalStore()
|
||||
}
|
||||
|
||||
func getMessageBus(rc redis.UniversalClient) psrpc.MessageBus {
|
||||
if rc == nil {
|
||||
return psrpc.NewLocalMessageBus()
|
||||
}
|
||||
return psrpc.NewRedisMessageBus(rc)
|
||||
}
|
||||
|
||||
func getEgressStore(s ObjectStore) EgressStore {
|
||||
switch store := s.(type) {
|
||||
case *RedisStore:
|
||||
return store
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func getIngressStore(s ObjectStore) IngressStore {
|
||||
switch store := s.(type) {
|
||||
case *RedisStore:
|
||||
return store
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func getAgentStore(s ObjectStore) AgentStore {
|
||||
switch store := s.(type) {
|
||||
case *RedisStore:
|
||||
return store
|
||||
case *LocalStore:
|
||||
return store
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func getIngressConfig(conf *config.Config) *config.IngressConfig {
|
||||
return &conf.Ingress
|
||||
}
|
||||
|
||||
func getSIPStore(s ObjectStore) SIPStore {
|
||||
switch store := s.(type) {
|
||||
case *RedisStore:
|
||||
return store
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func getSIPConfig(conf *config.Config) *config.SIPConfig {
|
||||
return &conf.SIP
|
||||
}
|
||||
|
||||
func createClientConfiguration() clientconfiguration.ClientConfigurationManager {
|
||||
return clientconfiguration.NewStaticClientConfigurationManager(clientconfiguration.StaticConfigurations)
|
||||
}
|
||||
|
||||
func getLimitConf(config2 *config.Config) config.LimitConfig {
|
||||
return config2.Limit
|
||||
}
|
||||
|
||||
func getRoomConfig(config2 *config.Config) config.RoomConfig {
|
||||
return config2.Room
|
||||
}
|
||||
|
||||
func getSignalRelayConfig(config2 *config.Config) config.SignalRelayConfig {
|
||||
return config2.SignalRelay
|
||||
}
|
||||
|
||||
func getPSRPCConfig(config2 *config.Config) rpc.PSRPCConfig {
|
||||
return config2.PSRPC
|
||||
}
|
||||
|
||||
func getPSRPCClientParams(config2 rpc.PSRPCConfig, bus psrpc.MessageBus) rpc.ClientParams {
|
||||
return rpc.NewClientParams(config2, bus, logger.GetLogger(), rpc.PSRPCMetricsObserver{})
|
||||
}
|
||||
|
||||
func createForwardStats(conf *config.Config) *sfu.ForwardStats {
|
||||
if conf.RTC.ForwardStats.SummaryInterval == 0 || conf.RTC.ForwardStats.ReportInterval == 0 || conf.RTC.ForwardStats.ReportWindow == 0 {
|
||||
return nil
|
||||
}
|
||||
return sfu.NewForwardStats(conf.RTC.ForwardStats.SummaryInterval, conf.RTC.ForwardStats.ReportInterval, conf.RTC.ForwardStats.ReportWindow)
|
||||
}
|
||||
|
||||
func newInProcessTurnServer(conf *config.Config, authHandler turn.AuthHandler) (*turn.Server, error) {
|
||||
return NewTurnServer(conf, authHandler, false)
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
// 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 service
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"google.golang.org/protobuf/encoding/protojson"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"github.com/livekit/protocol/livekit"
|
||||
"github.com/livekit/protocol/logger"
|
||||
|
||||
"github.com/livekit/livekit-server/pkg/rtc/types"
|
||||
)
|
||||
|
||||
const (
|
||||
pingFrequency = 10 * time.Second
|
||||
pingTimeout = 2 * time.Second
|
||||
)
|
||||
|
||||
type WSSignalConnection struct {
|
||||
conn types.WebsocketClient
|
||||
mu sync.Mutex
|
||||
useJSON bool
|
||||
}
|
||||
|
||||
func NewWSSignalConnection(conn types.WebsocketClient) *WSSignalConnection {
|
||||
wsc := &WSSignalConnection{
|
||||
conn: conn,
|
||||
mu: sync.Mutex{},
|
||||
useJSON: false,
|
||||
}
|
||||
go wsc.pingWorker()
|
||||
return wsc
|
||||
}
|
||||
|
||||
func (c *WSSignalConnection) Close() error {
|
||||
return c.conn.Close()
|
||||
}
|
||||
|
||||
func (c *WSSignalConnection) SetReadDeadline(deadline time.Time) error {
|
||||
return c.conn.SetReadDeadline(deadline)
|
||||
}
|
||||
|
||||
func (c *WSSignalConnection) ReadRequest() (*livekit.SignalRequest, int, error) {
|
||||
for {
|
||||
// handle special messages and pass on the rest
|
||||
messageType, payload, err := c.conn.ReadMessage()
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
msg := &livekit.SignalRequest{}
|
||||
switch messageType {
|
||||
case websocket.BinaryMessage:
|
||||
if c.useJSON {
|
||||
c.mu.Lock()
|
||||
// switch to protobuf if client supports it
|
||||
c.useJSON = false
|
||||
c.mu.Unlock()
|
||||
}
|
||||
// protobuf encoded
|
||||
err := proto.Unmarshal(payload, msg)
|
||||
return msg, len(payload), err
|
||||
case websocket.TextMessage:
|
||||
c.mu.Lock()
|
||||
// json encoded, also write back JSON
|
||||
c.useJSON = true
|
||||
c.mu.Unlock()
|
||||
err := protojson.Unmarshal(payload, msg)
|
||||
return msg, len(payload), err
|
||||
default:
|
||||
logger.Debugw("unsupported message", "message", messageType)
|
||||
return nil, len(payload), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *WSSignalConnection) ReadWorkerMessage() (*livekit.WorkerMessage, int, error) {
|
||||
for {
|
||||
// handle special messages and pass on the rest
|
||||
messageType, payload, err := c.conn.ReadMessage()
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
msg := &livekit.WorkerMessage{}
|
||||
switch messageType {
|
||||
case websocket.BinaryMessage:
|
||||
if c.useJSON {
|
||||
c.mu.Lock()
|
||||
// switch to protobuf if client supports it
|
||||
c.useJSON = false
|
||||
c.mu.Unlock()
|
||||
}
|
||||
// protobuf encoded
|
||||
err := proto.Unmarshal(payload, msg)
|
||||
return msg, len(payload), err
|
||||
case websocket.TextMessage:
|
||||
c.mu.Lock()
|
||||
// json encoded, also write back JSON
|
||||
c.useJSON = true
|
||||
c.mu.Unlock()
|
||||
err := protojson.Unmarshal(payload, msg)
|
||||
return msg, len(payload), err
|
||||
default:
|
||||
logger.Debugw("unsupported message", "message", messageType)
|
||||
return nil, len(payload), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *WSSignalConnection) WriteResponse(msg *livekit.SignalResponse) (int, error) {
|
||||
var msgType int
|
||||
var payload []byte
|
||||
var err error
|
||||
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
if c.useJSON {
|
||||
msgType = websocket.TextMessage
|
||||
payload, err = protojson.Marshal(msg)
|
||||
} else {
|
||||
msgType = websocket.BinaryMessage
|
||||
payload, err = proto.Marshal(msg)
|
||||
}
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return len(payload), c.conn.WriteMessage(msgType, payload)
|
||||
}
|
||||
|
||||
func (c *WSSignalConnection) WriteServerMessage(msg *livekit.ServerMessage) (int, error) {
|
||||
var msgType int
|
||||
var payload []byte
|
||||
var err error
|
||||
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
if c.useJSON {
|
||||
msgType = websocket.TextMessage
|
||||
payload, err = protojson.Marshal(msg)
|
||||
} else {
|
||||
msgType = websocket.BinaryMessage
|
||||
payload, err = proto.Marshal(msg)
|
||||
}
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return len(payload), c.conn.WriteMessage(msgType, payload)
|
||||
}
|
||||
|
||||
func (c *WSSignalConnection) pingWorker() {
|
||||
ticker := time.NewTicker(pingFrequency)
|
||||
defer ticker.Stop()
|
||||
|
||||
for range ticker.C {
|
||||
err := c.conn.WriteControl(websocket.PingMessage, []byte(""), time.Now().Add(pingTimeout))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// IsWebSocketCloseError checks that error is normal/expected closure
|
||||
func IsWebSocketCloseError(err error) bool {
|
||||
return errors.Is(err, io.EOF) ||
|
||||
strings.HasSuffix(err.Error(), "use of closed network connection") ||
|
||||
strings.HasSuffix(err.Error(), "connection reset by peer") ||
|
||||
websocket.IsCloseError(
|
||||
err,
|
||||
websocket.CloseAbnormalClosure,
|
||||
websocket.CloseGoingAway,
|
||||
websocket.CloseNormalClosure,
|
||||
websocket.CloseNoStatusReceived,
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user