// 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/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/protocol/utils/protojson" "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)) }) }