Merge commit '0da97ebd2149ec2a0412a273b17270f540431d81' as 'livekit-server'

This commit is contained in:
2026-06-25 14:35:28 +09:00
339 changed files with 114111 additions and 0 deletions
@@ -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)
}
+540
View File
@@ -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
}
+225
View File
@@ -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())
}
+74
View File
@@ -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)
}
+31
View File
@@ -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)
}
}
+75
View File
@@ -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
}
+80
View File
@@ -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
}
+281
View File
@@ -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
}
+45
View File
@@ -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")
)
+409
View File
@@ -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
}
+109
View File
@@ -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
}
+170
View File
@@ -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
}
+155
View File
@@ -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)
})
}
}
+283
View File
@@ -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)
}
+246
View File
@@ -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
+392
View File
@@ -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
}
+584
View File
@@ -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
}
}
}
+404
View File
@@ -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
+210
View File
@@ -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
}
+156
View File
@@ -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))
})
}
+568
View File
@@ -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
}
+192
View File
@@ -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
}
+414
View File
@@ -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
}
// --------------------------------------------------------------------------
+34
View File
@@ -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())
})
}
+84
View File
@@ -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
}
+77
View File
@@ -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)
}
}
+271
View File
@@ -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)
}
+331
View File
@@ -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)
}
+199
View File
@@ -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,
)
}