Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions adapter/group.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ type URLOverrideSetter interface {
SetURLOverrides(overrides map[string]string)
}

// OutboundChecker is implemented by outbound groups that support on-demand URL testing.
type OutboundChecker interface {
CheckOutbounds()
}

// TaggedConn is a net.Conn tagged with the outbound tag used to create it.
type TaggedConn struct {
net.Conn
Expand Down
23 changes: 23 additions & 0 deletions adapter/groups/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,29 @@ func (m *MutableGroupManager) SetURLOverrides(group string, overrides map[string
return nil
}

// CheckOutbounds triggers an immediate URL test cycle on the specified group.
// The group must implement [adapter.OutboundChecker].
func (m *MutableGroupManager) CheckOutbounds(group string) error {
m.mu.Lock()
if m.closed.Load() {
m.mu.Unlock()
return ErrIsClosed
}
outGroup, ok := m.groups[group]
if !ok {
m.mu.Unlock()
return fmt.Errorf("group %q not found", group)
}
checker, ok := outGroup.(adapter.OutboundChecker)
if !ok {
m.mu.Unlock()
return fmt.Errorf("group %q does not support outbound checking", group)
}
m.mu.Unlock()
checker.CheckOutbounds()
return nil
}

// RemoveFromGroup removes an outbound/endpoint from the specified group.
func (m *MutableGroupManager) RemoveFromGroup(group, tag string) error {
m.mu.Lock()
Expand Down
72 changes: 71 additions & 1 deletion adapter/groups/manager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,76 @@ func TestSetURLOverrides(t *testing.T) {
})
}

func TestCheckOutbounds(t *testing.T) {
logger := log.NewNOPFactory().Logger()

t.Run("delegates to OutboundChecker group", func(t *testing.T) {
mock := &mockCheckerGroup{}
mgr := &MutableGroupManager{
groups: map[string]lbAdapter.MutableOutboundGroup{"auto-test": mock},
removalQueue: newRemovalQueue(
logger, &mockOutboundManager{}, &mockEndpointManager{}, &mockConnectionManager{},
pollInterval, forceAfter,
),
}
err := mgr.CheckOutbounds("auto-test")
require.NoError(t, err)
assert.True(t, mock.checked)
})

t.Run("error when group not found", func(t *testing.T) {
mgr := &MutableGroupManager{
groups: map[string]lbAdapter.MutableOutboundGroup{},
removalQueue: newRemovalQueue(
logger, &mockOutboundManager{}, &mockEndpointManager{}, &mockConnectionManager{},
pollInterval, forceAfter,
),
}
err := mgr.CheckOutbounds("nonexistent")
assert.Error(t, err)
assert.Contains(t, err.Error(), "not found")
})

t.Run("error when group does not implement OutboundChecker", func(t *testing.T) {
mock := &mockPlainGroup{}
mgr := &MutableGroupManager{
groups: map[string]lbAdapter.MutableOutboundGroup{"plain": mock},
removalQueue: newRemovalQueue(
logger, &mockOutboundManager{}, &mockEndpointManager{}, &mockConnectionManager{},
pollInterval, forceAfter,
),
}
err := mgr.CheckOutbounds("plain")
assert.Error(t, err)
assert.Contains(t, err.Error(), "does not support outbound checking")
})

t.Run("error when manager is closed", func(t *testing.T) {
mock := &mockCheckerGroup{}
mgr := &MutableGroupManager{
groups: map[string]lbAdapter.MutableOutboundGroup{"auto-test": mock},
removalQueue: newRemovalQueue(
logger, &mockOutboundManager{}, &mockEndpointManager{}, &mockConnectionManager{},
pollInterval, forceAfter,
),
}
mgr.closed.Store(true)
err := mgr.CheckOutbounds("auto-test")
assert.ErrorIs(t, err, ErrIsClosed)
assert.False(t, mock.checked)
})
}

// mockCheckerGroup implements both MutableOutboundGroup and OutboundChecker.
type mockCheckerGroup struct {
lbAdapter.MutableOutboundGroup
checked bool
}

func (m *mockCheckerGroup) CheckOutbounds() {
m.checked = true
}

// mockURLOverrideGroup implements both MutableOutboundGroup and URLOverrideSetter.
type mockURLOverrideGroup struct {
lbAdapter.MutableOutboundGroup
Expand All @@ -177,7 +247,7 @@ func (m *mockURLOverrideGroup) SetURLOverrides(overrides map[string]string) {
m.urlOverrides = overrides
}

// mockPlainGroup implements MutableOutboundGroup but NOT URLOverrideSetter.
// mockPlainGroup implements MutableOutboundGroup but NOT URLOverrideSetter or OutboundChecker.
type mockPlainGroup struct {
lbAdapter.MutableOutboundGroup
}
Expand Down
Loading