This commit is contained in:
@@ -67,7 +67,7 @@ func run() error {
|
|||||||
providerBackend := gateway.NewNativeApolloBackend(providerManagement, providerRTSPAddress, providerRTSPURL, &http.Client{Timeout: 5 * time.Second})
|
providerBackend := gateway.NewNativeApolloBackend(providerManagement, providerRTSPAddress, providerRTSPURL, &http.Client{Timeout: 5 * time.Second})
|
||||||
provider := gateway.NewApolloAdapter(providerBackend, expectedIdentity)
|
provider := gateway.NewApolloAdapter(providerBackend, expectedIdentity)
|
||||||
capabilities := gateway.DefaultCapabilities()
|
capabilities := gateway.DefaultCapabilities()
|
||||||
server, err := gateway.NewServer(gateway.ServerConfig{ListenAddress: listen, TLSConfig: serverTLS, GatewayID: gatewayID, Capabilities: capabilities, ProviderCapabilities: capabilities, Admission: controlPlaneClient, Provider: provider, PacerKbps: 100000})
|
server, err := gateway.NewServer(gateway.ServerConfig{ListenAddress: listen, TLSConfig: serverTLS, GatewayID: gatewayID, Capabilities: capabilities, ProviderCapabilities: capabilities, Admission: controlPlaneClient, ProviderStateReporter: controlPlaneClient, Provider: provider, PacerKbps: 100000})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,6 +76,15 @@ func (c *ControlPlaneClient) Release(ctx context.Context, authority protocol.Ses
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *ControlPlaneClient) ReportProviderState(ctx context.Context, state protocol.ProviderState) error {
|
||||||
|
payload, err := protocol.EncodeProviderState(state)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = c.post(ctx, "/api/v1/gateway/provider-state", payload)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
func (c *ControlPlaneClient) post(ctx context.Context, path string, payload []byte) ([]byte, error) {
|
func (c *ControlPlaneClient) post(ctx context.Context, path string, payload []byte) ([]byte, error) {
|
||||||
if c == nil || c.HTTPClient == nil || c.BaseURL == "" {
|
if c == nil || c.HTTPClient == nil || c.BaseURL == "" {
|
||||||
return nil, errors.New("control-plane client is not configured")
|
return nil, errors.New("control-plane client is not configured")
|
||||||
|
|||||||
+26
-2
@@ -14,6 +14,7 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@@ -238,7 +239,8 @@ func TestAdmissionQUICMTLSRelayAndCleanup(t *testing.T) {
|
|||||||
fake := NewFakeApollo(FakeApolloConfig{Now: time.Date(2026, time.January, 1, 0, 0, 0, 0, time.UTC)})
|
fake := NewFakeApollo(FakeApolloConfig{Now: time.Date(2026, time.January, 1, 0, 0, 0, 0, time.UTC)})
|
||||||
authority := protocol.SessionAuthority{Version: "1", SessionID: "session-1", GatewayID: "gateway-1", Audience: "versevdi-gateway", ReconnectSequence: 0, ExpiresAt: time.Now().Add(5 * time.Second).UTC().Format(time.RFC3339Nano), Capabilities: DefaultCapabilities(), ProviderProfile: ProviderProfileApollo, ProviderIdentity: fake.config.Identity.Key()}
|
authority := protocol.SessionAuthority{Version: "1", SessionID: "session-1", GatewayID: "gateway-1", Audience: "versevdi-gateway", ReconnectSequence: 0, ExpiresAt: time.Now().Add(5 * time.Second).UTC().Format(time.RFC3339Nano), Capabilities: DefaultCapabilities(), ProviderProfile: ProviderProfileApollo, ProviderIdentity: fake.config.Identity.Key()}
|
||||||
admission := &oneTimeAdmission{authority: authority, released: make(chan struct{})}
|
admission := &oneTimeAdmission{authority: authority, released: make(chan struct{})}
|
||||||
server, err := NewServer(ServerConfig{ListenAddress: "127.0.0.1:0", TLSConfig: serverTLS, GatewayID: "gateway-1", Capabilities: DefaultCapabilities(), ProviderCapabilities: DefaultCapabilities(), Admission: admission, Provider: fake})
|
reporter := &recordingProviderStateReporter{}
|
||||||
|
server, err := NewServer(ServerConfig{ListenAddress: "127.0.0.1:0", TLSConfig: serverTLS, GatewayID: "gateway-1", Capabilities: DefaultCapabilities(), ProviderCapabilities: DefaultCapabilities(), Admission: admission, ProviderStateReporter: reporter, Provider: fake})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -246,7 +248,7 @@ func TestAdmissionQUICMTLSRelayAndCleanup(t *testing.T) {
|
|||||||
defer cancel()
|
defer cancel()
|
||||||
serveDone := make(chan error, 1)
|
serveDone := make(chan error, 1)
|
||||||
go func() { serveDone <- server.Serve(ctx) }()
|
go func() { serveDone <- server.Serve(ctx) }()
|
||||||
request := protocol.TunnelAdmissionRequest{Version: "1", SessionID: "session-1", GatewayID: "gateway-1", Audience: "versevdi-gateway", Grant: strings.Repeat("g", 64), ReconnectSequence: 0, ClientNonce: "nonce-0000000001", Capabilities: DefaultCapabilities()}
|
request := protocol.TunnelAdmissionRequest{Version: "1", SessionID: "session-1", GatewayID: "gateway-1", Audience: "versevdi-gateway", Grant: strings.Repeat("g", 64), ReconnectSequence: 0, ClientNonce: "nonce-0000000001", DeviceSignature: strings.Repeat("s", 86), Capabilities: DefaultCapabilities()}
|
||||||
client, err := Dial(context.Background(), server.Addr().String(), clientTLS, request)
|
client, err := Dial(context.Background(), server.Addr().String(), clientTLS, request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = server.Close()
|
_ = server.Close()
|
||||||
@@ -282,6 +284,10 @@ func TestAdmissionQUICMTLSRelayAndCleanup(t *testing.T) {
|
|||||||
if err := <-serveDone; err != nil {
|
if err := <-serveDone; err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
states := reporter.States()
|
||||||
|
if len(states) != 3 || states[0].State != ProviderStateStarting || states[1].State != ProviderStateReady || states[2].State != ProviderStateTerminated {
|
||||||
|
t.Fatalf("provider states = %#v", states)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testTLS(t *testing.T) (*tls.Config, *tls.Config) {
|
func testTLS(t *testing.T) (*tls.Config, *tls.Config) {
|
||||||
@@ -322,6 +328,24 @@ type oneTimeAdmission struct {
|
|||||||
released chan struct{}
|
released chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type recordingProviderStateReporter struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
states []protocol.ProviderState
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *recordingProviderStateReporter) ReportProviderState(_ context.Context, state protocol.ProviderState) error {
|
||||||
|
r.mu.Lock()
|
||||||
|
defer r.mu.Unlock()
|
||||||
|
r.states = append(r.states, state)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *recordingProviderStateReporter) States() []protocol.ProviderState {
|
||||||
|
r.mu.Lock()
|
||||||
|
defer r.mu.Unlock()
|
||||||
|
return append([]protocol.ProviderState(nil), r.states...)
|
||||||
|
}
|
||||||
|
|
||||||
func (a *oneTimeAdmission) Admit(context.Context, protocol.TunnelAdmissionRequest) (protocol.SessionAuthority, error) {
|
func (a *oneTimeAdmission) Admit(context.Context, protocol.TunnelAdmissionRequest) (protocol.SessionAuthority, error) {
|
||||||
if !a.used.CompareAndSwap(false, true) {
|
if !a.used.CompareAndSwap(false, true) {
|
||||||
return protocol.SessionAuthority{}, ErrAdmissionRejected
|
return protocol.SessionAuthority{}, ErrAdmissionRejected
|
||||||
|
|||||||
+2
-2
@@ -338,12 +338,12 @@ func (f *FakeApollo) Setup(context.Context, LaunchRequest) ([]byte, error) {
|
|||||||
return []byte("RTSP/1.0 200 OK\r\nSession: fixture-session\r\nTransport: RTP/AVP/TCP;interleaved=0-1\r\n\r\n"), nil
|
return []byte("RTSP/1.0 200 OK\r\nSession: fixture-session\r\nTransport: RTP/AVP/TCP;interleaved=0-1\r\n\r\n"), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *FakeApollo) Open(context.Context, LaunchRequest, RTSPResponse) (ProviderSession, error) {
|
func (f *FakeApollo) Open(_ context.Context, request LaunchRequest, _ RTSPResponse) (ProviderSession, error) {
|
||||||
session := &fakeSession{
|
session := &fakeSession{
|
||||||
failure: f.config.Failure,
|
failure: f.config.Failure,
|
||||||
video: make(chan []byte, 16),
|
video: make(chan []byte, 16),
|
||||||
audio: make(chan []byte, 16),
|
audio: make(chan []byte, 16),
|
||||||
state: protocol.ProviderState{Version: "1", State: ProviderStateStarting, Channels: []string{"video", "audio", "input", "feedback"}},
|
state: protocol.ProviderState{Version: "1", SessionID: request.SessionID, State: ProviderStateStarting, Channels: []string{"video", "audio", "input", "feedback"}},
|
||||||
pressed: make(map[string]struct{}),
|
pressed: make(map[string]struct{}),
|
||||||
}
|
}
|
||||||
for _, payload := range f.config.Video {
|
for _, payload := range f.config.Video {
|
||||||
|
|||||||
+58
-13
@@ -44,18 +44,23 @@ func (f AdmissionFunc) Admit(ctx context.Context, request protocol.TunnelAdmissi
|
|||||||
|
|
||||||
func (AdmissionFunc) Release(context.Context, protocol.SessionAuthority) error { return nil }
|
func (AdmissionFunc) Release(context.Context, protocol.SessionAuthority) error { return nil }
|
||||||
|
|
||||||
|
type ProviderStateReporter interface {
|
||||||
|
ReportProviderState(context.Context, protocol.ProviderState) error
|
||||||
|
}
|
||||||
|
|
||||||
type ServerConfig struct {
|
type ServerConfig struct {
|
||||||
ListenAddress string
|
ListenAddress string
|
||||||
TLSConfig *tls.Config
|
TLSConfig *tls.Config
|
||||||
QUICConfig *quic.Config
|
QUICConfig *quic.Config
|
||||||
GatewayID string
|
GatewayID string
|
||||||
Capabilities protocol.CapabilityProfile
|
Capabilities protocol.CapabilityProfile
|
||||||
ProviderCapabilities protocol.CapabilityProfile
|
ProviderCapabilities protocol.CapabilityProfile
|
||||||
Admission Admission
|
Admission Admission
|
||||||
Provider Provider
|
ProviderStateReporter ProviderStateReporter
|
||||||
ProviderProfile string
|
Provider Provider
|
||||||
ProviderIdentity string
|
ProviderProfile string
|
||||||
PacerKbps int64
|
ProviderIdentity string
|
||||||
|
PacerKbps int64
|
||||||
}
|
}
|
||||||
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
@@ -212,13 +217,26 @@ func (s *Server) handleConnection(parent context.Context, connection *quic.Conn)
|
|||||||
_ = writeStableError(stream, "no_capability_overlap", err, false)
|
_ = writeStableError(stream, "no_capability_overlap", err, false)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if err := s.reportProviderState(ctx, protocol.ProviderState{Version: "1", SessionID: request.SessionID, State: ProviderStateStarting, CleanupPending: false, Channels: []string{"video", "audio", "input", "feedback"}}); err != nil {
|
||||||
|
_ = s.config.Admission.Release(context.Background(), authority)
|
||||||
|
_ = writeStableError(stream, "provider_state_unavailable", err, true)
|
||||||
|
return
|
||||||
|
}
|
||||||
providerSession, err := s.config.Provider.Start(ctx, LaunchRequest{SessionID: request.SessionID, Capabilities: selected, ProviderProfile: authority.ProviderProfile, ProviderIdentity: authority.ProviderIdentity})
|
providerSession, err := s.config.Provider.Start(ctx, LaunchRequest{SessionID: request.SessionID, Capabilities: selected, ProviderProfile: authority.ProviderProfile, ProviderIdentity: authority.ProviderIdentity})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.metrics.ProviderErrors.Add(1)
|
s.metrics.ProviderErrors.Add(1)
|
||||||
|
_ = s.reportProviderState(context.Background(), protocol.ProviderState{Version: "1", SessionID: request.SessionID, State: ProviderStateFailed, CleanupPending: false, Channels: []string{"video", "audio", "input", "feedback"}})
|
||||||
_ = s.config.Admission.Release(context.Background(), authority)
|
_ = s.config.Admission.Release(context.Background(), authority)
|
||||||
_ = writeStableError(stream, stableProviderCode(err), err, errors.Is(err, context.DeadlineExceeded))
|
_ = writeStableError(stream, stableProviderCode(err), err, errors.Is(err, context.DeadlineExceeded))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if err := s.reportProviderState(ctx, providerSession.State()); err != nil {
|
||||||
|
_ = providerSession.ReleaseAll(context.Background())
|
||||||
|
_ = providerSession.Terminate(context.Background())
|
||||||
|
_ = s.config.Admission.Release(context.Background(), authority)
|
||||||
|
_ = writeStableError(stream, "provider_state_unavailable", err, true)
|
||||||
|
return
|
||||||
|
}
|
||||||
authority.Capabilities = selected
|
authority.Capabilities = selected
|
||||||
authorityBytes, err := protocol.EncodeSessionAuthority(authority)
|
authorityBytes, err := protocol.EncodeSessionAuthority(authority)
|
||||||
if err != nil || writeWire(stream, authorityBytes, defaultHelloLimit) != nil {
|
if err != nil || writeWire(stream, authorityBytes, defaultHelloLimit) != nil {
|
||||||
@@ -237,6 +255,16 @@ func (s *Server) handleConnection(parent context.Context, connection *quic.Conn)
|
|||||||
session.run()
|
session.run()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Server) reportProviderState(ctx context.Context, state protocol.ProviderState) error {
|
||||||
|
if s.config.ProviderStateReporter == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err := state.Validate(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return s.config.ProviderStateReporter.ReportProviderState(ctx, state)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Server) validateAuthority(authority protocol.SessionAuthority, request protocol.TunnelAdmissionRequest) error {
|
func (s *Server) validateAuthority(authority protocol.SessionAuthority, request protocol.TunnelAdmissionRequest) error {
|
||||||
if err := authority.Validate(); err != nil {
|
if err := authority.Validate(); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -467,15 +495,32 @@ func (s *gatewaySession) cleanup() {
|
|||||||
s.cancel()
|
s.cancel()
|
||||||
cleanupCtx, cancel := context.WithTimeout(context.Background(), time.Second)
|
cleanupCtx, cancel := context.WithTimeout(context.Background(), time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
if err := s.provider.ReleaseAll(cleanupCtx); err != nil {
|
releaseInputsErr := s.provider.ReleaseAll(cleanupCtx)
|
||||||
|
if releaseInputsErr != nil {
|
||||||
s.server.metrics.ProviderErrors.Add(1)
|
s.server.metrics.ProviderErrors.Add(1)
|
||||||
}
|
}
|
||||||
if err := s.provider.Terminate(cleanupCtx); err != nil {
|
terminateErr := s.provider.Terminate(cleanupCtx)
|
||||||
|
if terminateErr != nil {
|
||||||
s.server.metrics.ProviderErrors.Add(1)
|
s.server.metrics.ProviderErrors.Add(1)
|
||||||
}
|
}
|
||||||
|
state := s.provider.State()
|
||||||
|
if releaseInputsErr != nil || terminateErr != nil {
|
||||||
|
state.State = ProviderStateCleanup
|
||||||
|
state.CleanupPending = true
|
||||||
|
if err := s.server.reportProviderState(cleanupCtx, state); err != nil {
|
||||||
|
s.server.metrics.ProviderErrors.Add(1)
|
||||||
|
} else if err := s.server.config.Admission.Release(cleanupCtx, s.authority); err != nil {
|
||||||
|
s.server.metrics.ProviderErrors.Add(1)
|
||||||
|
}
|
||||||
|
_ = s.connection.CloseWithError(applicationError, "session closed")
|
||||||
|
return
|
||||||
|
}
|
||||||
if err := s.server.config.Admission.Release(cleanupCtx, s.authority); err != nil {
|
if err := s.server.config.Admission.Release(cleanupCtx, s.authority); err != nil {
|
||||||
s.server.metrics.ProviderErrors.Add(1)
|
s.server.metrics.ProviderErrors.Add(1)
|
||||||
}
|
}
|
||||||
|
if err := s.server.reportProviderState(cleanupCtx, state); err != nil {
|
||||||
|
s.server.metrics.ProviderErrors.Add(1)
|
||||||
|
}
|
||||||
_ = s.connection.CloseWithError(applicationError, "session closed")
|
_ = s.connection.CloseWithError(applicationError, "session closed")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ module git.sechmachine.io.vn/sechmachine/VerseVDI-Data-Plane
|
|||||||
go 1.26.5
|
go 1.26.5
|
||||||
|
|
||||||
require (
|
require (
|
||||||
git.sechmachine.io.vn/sechmachine/VerseVDI-Protocol v1.0.0-phase3c-gateway-rc.1
|
git.sechmachine.io.vn/sechmachine/VerseVDI-Protocol v1.0.0-phase3c-gateway-rc.3
|
||||||
github.com/quic-go/quic-go v0.61.0
|
github.com/quic-go/quic-go v0.61.0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
git.sechmachine.io.vn/sechmachine/VerseVDI-Protocol v1.0.0-phase3c-gateway-rc.1 h1:RPpbmiXBED6Ry1mU/+OQuPdCCoXfn1jxvbZQBrlIRzQ=
|
git.sechmachine.io.vn/sechmachine/VerseVDI-Protocol v1.0.0-phase3c-gateway-rc.3 h1:ZoXbg9CRwlypVbDO0EaXwHVOKTGlIfZDC7s/4JuOISE=
|
||||||
git.sechmachine.io.vn/sechmachine/VerseVDI-Protocol v1.0.0-phase3c-gateway-rc.1/go.mod h1:7PhFIDhjtr20btWoEb2GqB+7dBpzJt43olrnHVutWoc=
|
git.sechmachine.io.vn/sechmachine/VerseVDI-Protocol v1.0.0-phase3c-gateway-rc.3/go.mod h1:7PhFIDhjtr20btWoEb2GqB+7dBpzJt43olrnHVutWoc=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
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=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
|||||||
Reference in New Issue
Block a user