mirror of
https://github.com/Buriburizaem0n/nezha_domains.git
synced 2026-09-21 18:50:13 +00:00
test(agentcompat): add integration scenarios
Co-authored-by: naiba/CloudCode <hi+cloudcode@nai.ba>
This commit is contained in:
@@ -0,0 +1,176 @@
|
||||
//go:build linux
|
||||
|
||||
package scenario
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrInvalidHeldSessionPlan = errors.New("held session plan is invalid")
|
||||
ErrHeldSessionClosedBeforeLive = errors.New("held session closed before live")
|
||||
ErrHeldSessionLiveResolved = errors.New("held session live state already resolved")
|
||||
)
|
||||
|
||||
type heldSession interface {
|
||||
Plan() StressSessionPlan
|
||||
WaitLive(context.Context) error
|
||||
Close(context.Context) error
|
||||
WaitClosed(context.Context) error
|
||||
IOStreamID() (string, bool)
|
||||
Done() <-chan struct{}
|
||||
CloseResult() error
|
||||
}
|
||||
|
||||
type heldSessionState uint8
|
||||
|
||||
const (
|
||||
heldSessionConstructed heldSessionState = iota
|
||||
heldSessionLive
|
||||
heldSessionFailed
|
||||
heldSessionClosing
|
||||
heldSessionClosed
|
||||
)
|
||||
|
||||
type heldSessionLifecycle struct {
|
||||
baseContext context.Context
|
||||
plan StressSessionPlan
|
||||
ioStreamID string
|
||||
cleanupTimeout time.Duration
|
||||
|
||||
mu sync.Mutex
|
||||
state heldSessionState
|
||||
liveResult error
|
||||
liveDone chan struct{}
|
||||
closedDone chan struct{}
|
||||
closedResult error
|
||||
}
|
||||
|
||||
type heldSessionCloseOwner struct {
|
||||
lifecycle *heldSessionLifecycle
|
||||
closeOnce sync.Once
|
||||
}
|
||||
|
||||
func newHeldSessionLifecycle(baseContext context.Context, plan StressSessionPlan, ioStreamID string, cleanupTimeout time.Duration) (*heldSessionLifecycle, error) {
|
||||
if baseContext == nil || plan.ID.String() == "" || !supportedHeldSessionKind(plan.Kind) || plan.Ordinal < 1 || plan.Agent.Int() < 1 || cleanupTimeout <= 0 {
|
||||
return nil, ErrInvalidHeldSessionPlan
|
||||
}
|
||||
return &heldSessionLifecycle{
|
||||
baseContext: baseContext,
|
||||
plan: plan,
|
||||
ioStreamID: ioStreamID,
|
||||
cleanupTimeout: cleanupTimeout,
|
||||
state: heldSessionConstructed,
|
||||
liveDone: make(chan struct{}),
|
||||
closedDone: make(chan struct{}),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func supportedHeldSessionKind(kind StressSessionKind) bool {
|
||||
switch kind {
|
||||
case StressSessionTerminal, StressSessionNAT, StressSessionFM:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (lifecycle *heldSessionLifecycle) Plan() StressSessionPlan { return lifecycle.plan }
|
||||
|
||||
func (lifecycle *heldSessionLifecycle) IOStreamID() (string, bool) {
|
||||
return lifecycle.ioStreamID, lifecycle.ioStreamID != ""
|
||||
}
|
||||
|
||||
func (lifecycle *heldSessionLifecycle) setIOStreamID(streamID string) error {
|
||||
if streamID == "" {
|
||||
return errors.New("held session stream ID is empty")
|
||||
}
|
||||
lifecycle.mu.Lock()
|
||||
defer lifecycle.mu.Unlock()
|
||||
if lifecycle.state != heldSessionConstructed {
|
||||
return ErrHeldSessionLiveResolved
|
||||
}
|
||||
lifecycle.ioStreamID = streamID
|
||||
return nil
|
||||
}
|
||||
|
||||
func (lifecycle *heldSessionLifecycle) markLive(err error) error {
|
||||
lifecycle.mu.Lock()
|
||||
defer lifecycle.mu.Unlock()
|
||||
if lifecycle.state != heldSessionConstructed {
|
||||
return ErrHeldSessionLiveResolved
|
||||
}
|
||||
lifecycle.liveResult = err
|
||||
if err == nil {
|
||||
lifecycle.state = heldSessionLive
|
||||
} else {
|
||||
lifecycle.state = heldSessionFailed
|
||||
}
|
||||
close(lifecycle.liveDone)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (lifecycle *heldSessionLifecycle) WaitLive(ctx context.Context) error {
|
||||
select {
|
||||
case <-lifecycle.liveDone:
|
||||
lifecycle.mu.Lock()
|
||||
defer lifecycle.mu.Unlock()
|
||||
return lifecycle.liveResult
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
}
|
||||
|
||||
func (lifecycle *heldSessionLifecycle) beginClose() (*heldSessionCloseOwner, bool) {
|
||||
lifecycle.mu.Lock()
|
||||
defer lifecycle.mu.Unlock()
|
||||
if lifecycle.state == heldSessionConstructed {
|
||||
lifecycle.liveResult = ErrHeldSessionClosedBeforeLive
|
||||
lifecycle.state = heldSessionClosing
|
||||
close(lifecycle.liveDone)
|
||||
return &heldSessionCloseOwner{lifecycle: lifecycle}, true
|
||||
}
|
||||
if lifecycle.state == heldSessionLive || lifecycle.state == heldSessionFailed {
|
||||
lifecycle.state = heldSessionClosing
|
||||
return &heldSessionCloseOwner{lifecycle: lifecycle}, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (owner *heldSessionCloseOwner) cleanupContext() (context.Context, context.CancelFunc) {
|
||||
// The deadline only signals cancellation; it cannot forcibly terminate arbitrary cleanup work.
|
||||
return context.WithTimeout(context.WithoutCancel(owner.lifecycle.baseContext), owner.lifecycle.cleanupTimeout)
|
||||
}
|
||||
|
||||
func (owner *heldSessionCloseOwner) markClosed(err error) {
|
||||
owner.closeOnce.Do(func() {
|
||||
lifecycle := owner.lifecycle
|
||||
lifecycle.mu.Lock()
|
||||
lifecycle.closedResult = err
|
||||
lifecycle.state = heldSessionClosed
|
||||
close(lifecycle.closedDone)
|
||||
lifecycle.mu.Unlock()
|
||||
})
|
||||
}
|
||||
|
||||
func (lifecycle *heldSessionLifecycle) WaitClosed(ctx context.Context) error {
|
||||
select {
|
||||
case <-lifecycle.closedDone:
|
||||
lifecycle.mu.Lock()
|
||||
defer lifecycle.mu.Unlock()
|
||||
return lifecycle.closedResult
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
}
|
||||
|
||||
func (lifecycle *heldSessionLifecycle) Done() <-chan struct{} { return lifecycle.closedDone }
|
||||
|
||||
func (lifecycle *heldSessionLifecycle) CloseResult() error {
|
||||
lifecycle.mu.Lock()
|
||||
defer lifecycle.mu.Unlock()
|
||||
return lifecycle.closedResult
|
||||
}
|
||||
Reference in New Issue
Block a user