Client configuration (#452)

* client configuration

* fix init roommanager
This commit is contained in:
cnderrauber
2022-03-03 18:26:14 +08:00
committed by GitHub
parent 96654e164a
commit 4bc03d8435
11 changed files with 289 additions and 14 deletions
+1
View File
@@ -50,6 +50,7 @@ require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.1.1 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
github.com/d5/tengo/v2 v2.10.1
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/eapache/channels v1.1.0 // indirect
+2
View File
@@ -31,6 +31,8 @@ github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/d5/tengo/v2 v2.10.1 h1:Z7vmTAQfdoExNEB9kxgqxvoBBW9bf+8uYMiDyriX5HM=
github.com/d5/tengo/v2 v2.10.1/go.mod h1:XRGjEs5I9jYIKTxly6HCF8oiiilk5E/RYXOZ5b0DZC8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+10
View File
@@ -0,0 +1,10 @@
package clientconfiguration
// configurations for livekit-client, add more configuration to StaticConfigurations as need
var StaticConfigurations = []ConfigurationItem{
// {
// Match: &ScriptMatch{Expr: `c.protocol <= 5 || c.browser == "firefox"`},
// Configuration: &livekit.ClientConfiguration{ResumeConnection: livekit.ClientConfigSetting_DISABLED},
// Merge: false,
// },
}
+101
View File
@@ -0,0 +1,101 @@
package clientconfiguration
import (
"testing"
"github.com/livekit/protocol/livekit"
"github.com/stretchr/testify/require"
)
func TestScriptMatchConfiguration(t *testing.T) {
t.Run("no merge", func(t *testing.T) {
confs := []ConfigurationItem{
{
Match: &ScriptMatch{Expr: `c.protocol > 5 && c.browser != "firefox"`},
Configuration: &livekit.ClientConfiguration{
ResumeConnection: livekit.ClientConfigSetting_ENABLED,
},
},
}
cm := NewStaticClientConfigurationManager(confs)
conf := cm.GetConfiguration(&livekit.ClientInfo{Protocol: 4})
require.Nil(t, conf)
conf = cm.GetConfiguration(&livekit.ClientInfo{Protocol: 6, Browser: "firefox"})
require.Nil(t, conf)
conf = cm.GetConfiguration(&livekit.ClientInfo{Protocol: 6, Browser: "chrome"})
require.Equal(t, conf.ResumeConnection, livekit.ClientConfigSetting_ENABLED)
})
t.Run("merge", func(t *testing.T) {
confs := []ConfigurationItem{
{
Match: &ScriptMatch{Expr: `c.protocol > 5 && c.browser != "firefox"`},
Configuration: &livekit.ClientConfiguration{
ResumeConnection: livekit.ClientConfigSetting_ENABLED,
},
Merge: true,
},
{
Match: &ScriptMatch{Expr: `c.sdk == "ANDROID"`},
Configuration: &livekit.ClientConfiguration{
Video: &livekit.VideoConfiguration{
HardwareEncoder: livekit.ClientConfigSetting_DISABLED,
},
},
Merge: true,
},
}
cm := NewStaticClientConfigurationManager(confs)
conf := cm.GetConfiguration(&livekit.ClientInfo{Protocol: 4})
require.Nil(t, conf)
conf = cm.GetConfiguration(&livekit.ClientInfo{Protocol: 6, Browser: "firefox"})
require.Nil(t, conf)
conf = cm.GetConfiguration(&livekit.ClientInfo{Protocol: 6, Browser: "chrome", Sdk: 3})
require.Equal(t, conf.ResumeConnection, livekit.ClientConfigSetting_ENABLED)
require.Equal(t, conf.Video.HardwareEncoder, livekit.ClientConfigSetting_DISABLED)
})
}
func TestScriptMatch(t *testing.T) {
client := &livekit.ClientInfo{
Protocol: 6,
Browser: "chrome",
Sdk: 3, // android
DeviceModel: "12345",
}
type testcase struct {
name string
expr string
result bool
err bool
}
cases := []testcase{
{name: "simple match", expr: `c.protocol > 5`, result: true},
{name: "invalid expr", expr: `cc.protocol > 5`, err: true},
{name: "unexist field", expr: `c.protocols > 5`, err: true},
{name: "combined condition", expr: `c.protocol > 5 && (c.sdk=="ANDROID" || c.sdk=="IOS")`, result: true},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
match := &ScriptMatch{Expr: c.expr}
m, err := match.Match(client)
if c.err {
require.Error(t, err)
} else {
require.Equal(t, c.result, m)
}
})
}
}
+77
View File
@@ -0,0 +1,77 @@
package clientconfiguration
import (
"context"
"errors"
"github.com/d5/tengo/v2"
"github.com/livekit/protocol/livekit"
)
type Match interface {
Match(clientInfo *livekit.ClientInfo) (bool, error)
}
type ScriptMatch struct {
Expr string
}
// use result of eval script expression for match.
// expression examples:
// protocol bigger than 5 : c.protocol > 5
// browser if firefox: c.browser == "firefox"
// combined rule : c.protocol > 5 && c.browser == "firefox"
func (m *ScriptMatch) Match(clientInfo *livekit.ClientInfo) (bool, error) {
res, err := tengo.Eval(context.TODO(), m.Expr, map[string]interface{}{"c": &clientObject{info: clientInfo}})
if err != nil {
return false, err
}
if val, ok := res.(bool); ok {
return val, nil
}
return false, errors.New("invalid match expression result")
}
type clientObject struct {
tengo.ObjectImpl
info *livekit.ClientInfo
}
func (c *clientObject) TypeName() string {
return "clientObject"
}
func (c *clientObject) String() string {
return c.info.String()
}
func (c *clientObject) IndexGet(index tengo.Object) (res tengo.Object, err error) {
field, ok := index.(*tengo.String)
if !ok {
return nil, tengo.ErrInvalidIndexType
}
switch field.Value {
case "sdk":
return &tengo.String{Value: c.info.Sdk.String()}, nil
case "version":
return &tengo.String{Value: c.info.Version}, nil
case "protocol":
return &tengo.Int{Value: int64(c.info.Protocol)}, nil
case "os":
return &tengo.String{Value: c.info.Os}, nil
case "os_version":
return &tengo.String{Value: c.info.OsVersion}, nil
case "device_model":
return &tengo.String{Value: c.info.DeviceModel}, nil
case "browser":
return &tengo.String{Value: c.info.Browser}, nil
case "browser_version":
return &tengo.String{Value: c.info.BrowserVersion}, nil
case "address":
return &tengo.String{Value: c.info.Address}, nil
}
return &tengo.Undefined{}, nil
}
@@ -0,0 +1,54 @@
package clientconfiguration
import (
"fmt"
"github.com/livekit/protocol/livekit"
"github.com/livekit/protocol/logger"
"google.golang.org/protobuf/proto"
)
type ConfigurationItem struct {
Match
Configuration *livekit.ClientConfiguration
Merge bool
}
type StaticClientConfigurationManager struct {
confs []ConfigurationItem
}
func NewStaticClientConfigurationManager(confs []ConfigurationItem) *StaticClientConfigurationManager {
return &StaticClientConfigurationManager{confs: confs}
}
func (s *StaticClientConfigurationManager) GetConfiguration(clientInfo *livekit.ClientInfo) *livekit.ClientConfiguration {
var matchedConf []*livekit.ClientConfiguration
for _, c := range s.confs {
matched, err := c.Match.Match(clientInfo)
if err != nil {
logger.Errorw(fmt.Sprintf("matchrule failed, clientInfo: %s", clientInfo.String()), err)
continue
}
if !matched {
continue
}
if !c.Merge {
return c.Configuration
}
matchedConf = append(matchedConf, c.Configuration)
}
var conf *livekit.ClientConfiguration
for k, v := range matchedConf {
if k == 0 {
conf = proto.Clone(matchedConf[0]).(*livekit.ClientConfiguration)
} else {
// TODO : there is a problem use protobuf merge, we don't have flag to indicate 'no value',
// don't override default behavior or other configuration's field. So a bool value = false or
// a int value = 0 will override same field in other configuration
proto.Merge(conf, v)
}
}
return conf
}
+9
View File
@@ -0,0 +1,9 @@
package clientconfiguration
import (
"github.com/livekit/protocol/livekit"
)
type ClientConfigurationManager interface {
GetConfiguration(clientInfo *livekit.ClientInfo) *livekit.ClientConfiguration
}
+3 -1
View File
@@ -60,6 +60,7 @@ type ParticipantParams struct {
SimTracks map[uint32]SimulcastTrackInfo
Grants *auth.ClaimGrants
InitialVersion uint32
ClientConf *livekit.ClientConfiguration
}
type ParticipantImpl struct {
@@ -612,7 +613,8 @@ func (p *ParticipantImpl) SendJoinResponse(
ServerRegion: region,
IceServers: iceServers,
// indicates both server and client support subscriber as primary
SubscriberPrimary: p.SubscriberAsPrimary(),
SubscriberPrimary: p.SubscriberAsPrimary(),
ClientConfiguration: p.params.ClientConf,
},
},
})
+19 -12
View File
@@ -11,6 +11,7 @@ import (
"github.com/livekit/protocol/logger"
"github.com/livekit/protocol/utils"
"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/rtc"
@@ -29,12 +30,13 @@ const (
type RoomManager struct {
lock sync.RWMutex
config *config.Config
rtcConfig *rtc.WebRTCConfig
currentNode routing.LocalNode
router routing.Router
roomStore ObjectStore
telemetry telemetry.TelemetryService
config *config.Config
rtcConfig *rtc.WebRTCConfig
currentNode routing.LocalNode
router routing.Router
roomStore ObjectStore
telemetry telemetry.TelemetryService
clientConfManager clientconfiguration.ClientConfigurationManager
rooms map[livekit.RoomName]*rtc.Room
}
@@ -45,6 +47,7 @@ func NewLocalRoomManager(
currentNode routing.LocalNode,
router routing.Router,
telemetry telemetry.TelemetryService,
clientConfManager clientconfiguration.ClientConfigurationManager,
) (*RoomManager, error) {
rtcConf, err := rtc.NewWebRTCConfig(conf, currentNode.Ip)
@@ -53,12 +56,13 @@ func NewLocalRoomManager(
}
r := &RoomManager{
config: conf,
rtcConfig: rtcConf,
currentNode: currentNode,
router: router,
roomStore: roomStore,
telemetry: telemetry,
config: conf,
rtcConfig: rtcConf,
currentNode: currentNode,
router: router,
roomStore: roomStore,
telemetry: telemetry,
clientConfManager: clientConfManager,
rooms: make(map[livekit.RoomName]*rtc.Room),
}
@@ -228,6 +232,8 @@ func (r *RoomManager) StartSession(ctx context.Context, roomName livekit.RoomNam
"protocol", pi.Client.Protocol,
)
clientConf := r.clientConfManager.GetConfiguration(pi.Client)
pv := types.ProtocolVersion(pi.Client.Protocol)
rtcConf := *r.rtcConfig
rtcConf.SetBufferFactory(room.GetBufferFactory())
@@ -248,6 +254,7 @@ func (r *RoomManager) StartSession(ctx context.Context, roomName livekit.RoomNam
Grants: pi.Grants,
Hidden: pi.Hidden,
Logger: pLogger,
ClientConf: clientConf,
}, pi.Permission)
if err != nil {
logger.Errorw("could not create participant", err)
+6
View File
@@ -10,6 +10,7 @@ import (
"github.com/go-redis/redis/v8"
"github.com/google/wire"
"github.com/livekit/livekit-server/pkg/clientconfiguration"
"github.com/livekit/protocol/auth"
"github.com/livekit/protocol/livekit"
"github.com/livekit/protocol/logger"
@@ -33,6 +34,7 @@ func InitializeServer(conf *config.Config, currentNode routing.LocalNode) (*Live
wire.Bind(new(EgressStore), new(ObjectStore)),
createKeyProvider,
createWebhookNotifier,
createClientConfiguration,
routing.CreateRouter,
wire.Bind(new(routing.MessageRouter), new(routing.Router)),
wire.Bind(new(livekit.RoomService), new(*RoomService)),
@@ -147,3 +149,7 @@ func createStore(rc *redis.Client) ObjectStore {
}
return NewLocalStore()
}
func createClientConfiguration() clientconfiguration.ClientConfigurationManager {
return clientconfiguration.NewStaticClientConfigurationManager(clientconfiguration.StaticConfigurations)
}
+7 -1
View File
@@ -11,6 +11,7 @@ import (
"crypto/tls"
"fmt"
"github.com/go-redis/redis/v8"
"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/telemetry"
@@ -54,7 +55,8 @@ func InitializeServer(conf *config.Config, currentNode routing.LocalNode) (*Live
egressService := NewEgressService(messageBus, objectStore, roomService, telemetryService)
recordingService := NewRecordingService(messageBus, telemetryService)
rtcService := NewRTCService(conf, roomAllocator, objectStore, router, currentNode)
roomManager, err := NewLocalRoomManager(conf, objectStore, currentNode, router, telemetryService)
clientConfigurationManager := createClientConfiguration()
roomManager, err := NewLocalRoomManager(conf, objectStore, currentNode, router, telemetryService, clientConfigurationManager)
if err != nil {
return nil, err
}
@@ -167,3 +169,7 @@ func createStore(rc *redis.Client) ObjectStore {
}
return NewLocalStore()
}
func createClientConfiguration() clientconfiguration.ClientConfigurationManager {
return clientconfiguration.NewStaticClientConfigurationManager(clientconfiguration.StaticConfigurations)
}