When a path is dynamic, and the path receives multiple describe and publish requests in parallel, describe requests might cause the path to be deleted, and this might cause pending publish requests to fail, since the path has been deleted. This patch improves the situation by checking for pending requests before deleting a path.
218 lines
5.7 KiB
Go
218 lines
5.7 KiB
Go
package core
|
|
|
|
import (
|
|
"net/http"
|
|
"regexp"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/bluenviron/gortsplib/v5"
|
|
"github.com/bluenviron/gortsplib/v5/pkg/description"
|
|
"github.com/pion/rtp"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/bluenviron/mediamtx/internal/conf"
|
|
"github.com/bluenviron/mediamtx/internal/defs"
|
|
"github.com/bluenviron/mediamtx/internal/logger"
|
|
"github.com/bluenviron/mediamtx/internal/test"
|
|
)
|
|
|
|
type dummyPublisher struct{}
|
|
|
|
func (d *dummyPublisher) Close() {}
|
|
|
|
func (d *dummyPublisher) Log(_ logger.Level, _ string, _ ...any) {}
|
|
|
|
func (d *dummyPublisher) APISourceDescribe() *defs.APIPathSource {
|
|
return nil
|
|
}
|
|
|
|
type dummyReader struct{}
|
|
|
|
func (d *dummyReader) Close() {}
|
|
|
|
func (d *dummyReader) Log(_ logger.Level, _ string, _ ...any) {}
|
|
|
|
func (d *dummyReader) APIReaderDescribe() *defs.APIPathReader {
|
|
return nil
|
|
}
|
|
|
|
func TestPathManagerDynamicPathAutoDeletion(t *testing.T) {
|
|
for _, ca := range []string{"describe", "add reader"} {
|
|
t.Run(ca, func(t *testing.T) {
|
|
pathConfs := map[string]*conf.Path{
|
|
"all_others": {
|
|
Regexp: regexp.MustCompile("^.*$"),
|
|
Name: "all_others",
|
|
Source: "publisher",
|
|
},
|
|
}
|
|
|
|
pm := &pathManager{
|
|
authManager: test.NilAuthManager,
|
|
pathConfs: pathConfs,
|
|
parent: test.NilLogger,
|
|
}
|
|
pm.initialize()
|
|
defer pm.close()
|
|
|
|
func() {
|
|
if ca == "describe" {
|
|
res := pm.Describe(defs.PathDescribeReq{
|
|
AccessRequest: defs.PathAccessRequest{
|
|
Name: "mypath",
|
|
},
|
|
})
|
|
require.EqualError(t, res.Err, "no stream is available on path 'mypath'")
|
|
} else {
|
|
_, _, err := pm.AddReader(defs.PathAddReaderReq{
|
|
Author: &dummyReader{},
|
|
AccessRequest: defs.PathAccessRequest{
|
|
Name: "mypath",
|
|
},
|
|
})
|
|
require.EqualError(t, err, "no stream is available on path 'mypath'")
|
|
}
|
|
}()
|
|
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
data, err := pm.APIPathsList()
|
|
require.NoError(t, err)
|
|
|
|
require.Empty(t, data.Items)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPathManagerDynamicPathDescribeAndPublish(t *testing.T) {
|
|
pathConfs := map[string]*conf.Path{
|
|
"all_others": {
|
|
Regexp: regexp.MustCompile("^.*$"),
|
|
Name: "all_others",
|
|
Source: "publisher",
|
|
},
|
|
}
|
|
|
|
pm := &pathManager{
|
|
authManager: test.NilAuthManager,
|
|
pathConfs: pathConfs,
|
|
parent: test.NilLogger,
|
|
}
|
|
pm.initialize()
|
|
defer pm.close()
|
|
|
|
go func() {
|
|
for range 10 {
|
|
pm.Describe(defs.PathDescribeReq{
|
|
AccessRequest: defs.PathAccessRequest{
|
|
Name: "mypath",
|
|
},
|
|
})
|
|
}
|
|
}()
|
|
|
|
_, _, err := pm.AddPublisher(defs.PathAddPublisherReq{
|
|
Author: &dummyPublisher{},
|
|
Desc: &description.Session{},
|
|
AccessRequest: defs.PathAccessRequest{
|
|
Name: "mypath",
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestPathManagerConfigHotReload(t *testing.T) {
|
|
// Start MediaMTX with basic configuration
|
|
p, ok := newInstance("api: yes\n" +
|
|
"paths:\n" +
|
|
" all:\n" +
|
|
" record: no\n")
|
|
require.Equal(t, true, ok)
|
|
defer p.Close()
|
|
|
|
// Set up HTTP client for API calls
|
|
tr := &http.Transport{}
|
|
defer tr.CloseIdleConnections()
|
|
hc := &http.Client{Transport: tr}
|
|
|
|
// Create a publisher that will use the "all" configuration
|
|
media0 := test.UniqueMediaH264()
|
|
source := gortsplib.Client{}
|
|
err := source.StartRecording(
|
|
"rtsp://localhost:8554/undefined_stream",
|
|
&description.Session{Medias: []*description.Media{media0}})
|
|
require.NoError(t, err)
|
|
defer source.Close()
|
|
|
|
// Send some data to establish the stream
|
|
err = source.WritePacketRTP(media0, &rtp.Packet{
|
|
Header: rtp.Header{
|
|
Version: 2,
|
|
PayloadType: 96,
|
|
},
|
|
Payload: []byte{5, 1, 2, 3, 4},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
// Verify the path exists and is using the "all" configuration
|
|
pathData, err := p.pathManager.APIPathsGet("undefined_stream")
|
|
require.NoError(t, err)
|
|
require.Equal(t, "undefined_stream", pathData.Name)
|
|
require.Equal(t, "all", pathData.ConfName)
|
|
|
|
// Check the current configuration via API
|
|
var allConfig map[string]any
|
|
httpRequest(t, hc, http.MethodGet, "http://localhost:9997/v3/config/paths/get/all", nil, &allConfig)
|
|
require.Equal(t, false, allConfig["record"]) // Should be false from "all" config
|
|
|
|
// Add a new specific configuration for "undefined_stream" with record enabled
|
|
httpRequest(t, hc, http.MethodPost, "http://localhost:9997/v3/config/paths/add/undefined_stream",
|
|
map[string]any{
|
|
"record": true,
|
|
}, nil)
|
|
|
|
// Give the system time to process the configuration change
|
|
time.Sleep(200 * time.Millisecond)
|
|
|
|
// Verify the path now uses the new specific configuration
|
|
pathData, err = p.pathManager.APIPathsGet("undefined_stream")
|
|
require.NoError(t, err)
|
|
require.Equal(t, "undefined_stream", pathData.Name)
|
|
require.Equal(t, "undefined_stream", pathData.ConfName) // Should now use the specific config
|
|
|
|
// Check the new configuration via API
|
|
var newConfig map[string]any
|
|
httpRequest(t, hc, http.MethodGet, "http://localhost:9997/v3/config/paths/get/undefined_stream", nil, &newConfig)
|
|
require.Equal(t, true, newConfig["record"]) // Should be true from new config
|
|
|
|
// Verify the stream is still active and working
|
|
err = source.WritePacketRTP(media0, &rtp.Packet{
|
|
Header: rtp.Header{
|
|
Version: 2,
|
|
PayloadType: 96,
|
|
SequenceNumber: 2,
|
|
},
|
|
Payload: []byte{5, 1, 2, 3, 4},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Verify the path is still ready and functional
|
|
require.Equal(t, true, pathData.Ready)
|
|
|
|
// revert configuration
|
|
httpRequest(t, hc, http.MethodDelete, "http://localhost:9997/v3/config/paths/delete/undefined_stream",
|
|
nil, nil)
|
|
|
|
// Give the system time to process the configuration change
|
|
time.Sleep(200 * time.Millisecond)
|
|
|
|
// Verify the path now uses the old configuration
|
|
pathData, err = p.pathManager.APIPathsGet("undefined_stream")
|
|
require.NoError(t, err)
|
|
require.Equal(t, "undefined_stream", pathData.Name)
|
|
require.Equal(t, "all", pathData.ConfName)
|
|
}
|