mirror of
https://github.com/livekit/livekit.git
synced 2026-10-15 19:50:07 +00:00
An unnamed deployment was addressed as "default" and that spelling leaked into scope keys, so embedders keyed endpoints differently from job dispatch. Map "_" back to "" at the URL boundary, as the agent name already does, and drop NormalizeDeployment.
118 lines
3.4 KiB
Go
118 lines
3.4 KiB
Go
// Copyright 2026 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 (
|
|
"sync"
|
|
|
|
"github.com/livekit/protocol/logger"
|
|
|
|
"github.com/livekit/livekit-server/pkg/agent/endpoint"
|
|
)
|
|
|
|
// endpointScopeKey is this server's tenancy for agent HTTP endpoints: the api
|
|
// key is the project identity. The endpoint package holds no tenancy identity of
|
|
// its own, so this is the one place that rule is written.
|
|
type endpointScopeKey struct {
|
|
apiKey string
|
|
agentName string
|
|
deployment string
|
|
}
|
|
|
|
func newEndpointScopeKey(apiKey, agentName, deployment string) endpointScopeKey {
|
|
return endpointScopeKey{apiKey, agentName, deployment}
|
|
}
|
|
|
|
// EndpointScopes maps that tenancy onto the endpoint package's scopes, one per
|
|
// key, held for as long as a worker holds it.
|
|
type EndpointScopes struct {
|
|
logger logger.Logger
|
|
|
|
mu sync.Mutex
|
|
scopes map[endpointScopeKey]*endpointScopeEntry
|
|
}
|
|
|
|
// endpointScopeEntry refcounts a scope by the registrations that hold it: a
|
|
// scope must outlive a worker that has acquired it but not yet registered, or a
|
|
// concurrent release would strand the new registration in an orphaned scope.
|
|
type endpointScopeEntry struct {
|
|
scope *endpoint.Scope
|
|
n int
|
|
}
|
|
|
|
func NewEndpointScopes() *EndpointScopes {
|
|
return &EndpointScopes{
|
|
logger: logger.GetLogger().WithComponent("agents.endpoint"),
|
|
scopes: make(map[endpointScopeKey]*endpointScopeEntry),
|
|
}
|
|
}
|
|
|
|
// acquire returns the key's scope, creating it on first use. Every acquire must
|
|
// be matched by a release.
|
|
func (s *EndpointScopes) acquire(k endpointScopeKey) *endpoint.Scope {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
e := s.scopes[k]
|
|
if e == nil {
|
|
e = &endpointScopeEntry{scope: endpoint.NewScope(s.logger.WithValues(
|
|
"apiKey", k.apiKey, "agentName", k.agentName, "deployment", k.deployment))}
|
|
s.scopes[k] = e
|
|
}
|
|
e.n++
|
|
return e.scope
|
|
}
|
|
|
|
func (s *EndpointScopes) release(k endpointScopeKey) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
e := s.scopes[k]
|
|
if e == nil {
|
|
return
|
|
}
|
|
if e.n--; e.n <= 0 {
|
|
delete(s.scopes, k)
|
|
}
|
|
}
|
|
|
|
// Scope returns the deployment's serving state, or nil when no worker holds it -
|
|
// which the front reads as "no worker for this deployment".
|
|
func (s *EndpointScopes) Scope(apiKey, agentName, deployment string) *endpoint.Scope {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
if e := s.scopes[newEndpointScopeKey(apiKey, agentName, deployment)]; e != nil {
|
|
return e.scope
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// SingleKey returns the api key when every scope on this node belongs to one -
|
|
// the resolution for unauthenticated requests to public endpoints. It is sound
|
|
// only because a guessed api key confers no access: the front still serves such
|
|
// a request only against routes marked public. ok is false when zero or multiple
|
|
// keys are present.
|
|
func (s *EndpointScopes) SingleKey() (string, bool) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
var key string
|
|
for k := range s.scopes {
|
|
if key == "" {
|
|
key = k.apiKey
|
|
} else if key != k.apiKey {
|
|
return "", false
|
|
}
|
|
}
|
|
return key, key != ""
|
|
}
|