feat: durably admit client dispatches
This commit is contained in:
@@ -0,0 +1,34 @@
|
|||||||
|
package agent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
rvboxv1 "github.com/rvbox/rvbox/gen/go/rvbox/v1"
|
||||||
|
"github.com/rvbox/rvbox/internal/agentproto"
|
||||||
|
"github.com/rvbox/rvbox/internal/client/spool"
|
||||||
|
"github.com/rvbox/rvbox/internal/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PersistDispatch admits a server dispatch to the durable spool before any
|
||||||
|
// CommandAccepted reply. UUID/hash equality makes a retransmission harmless;
|
||||||
|
// a different immutable request for the same UUID is rejected by the spool.
|
||||||
|
func PersistDispatch(ctx context.Context, store *spool.Store, session Session, dispatch *rvboxv1.CommandDispatch, now time.Time, limits agentproto.Limits) (spool.Acceptance, error) {
|
||||||
|
if store == nil || dispatch == nil || session.Generation == 0 || dispatch.GetTargetSessionGeneration() != session.Generation || now.IsZero() {
|
||||||
|
return spool.Acceptance{}, ErrProtocolHandshake
|
||||||
|
}
|
||||||
|
if err := agentproto.ValidateExecutionSpec(dispatch.GetSpec(), limits, rvboxv1.Platform_PLATFORM_WINDOWS); err != nil {
|
||||||
|
return spool.Acceptance{}, err
|
||||||
|
}
|
||||||
|
issue, err := domain.ParseUUIDv7(dispatch.GetIssueUuid())
|
||||||
|
if err != nil || dispatch.GetCommandRevision() == 0 || len(dispatch.GetImmutableRequestSha256()) != 32 {
|
||||||
|
return spool.Acceptance{}, ErrProtocolHandshake
|
||||||
|
}
|
||||||
|
var immutable [32]byte
|
||||||
|
copy(immutable[:], dispatch.GetImmutableRequestSha256())
|
||||||
|
if immutable == [32]byte{} {
|
||||||
|
return spool.Acceptance{}, errors.New("empty dispatch immutable hash")
|
||||||
|
}
|
||||||
|
return store.AcceptCommand(ctx, spool.Command{IssueUUID: issue, ImmutableSHA256: immutable, Revision: dispatch.GetCommandRevision(), Phase: uint32(rvboxv1.CommandLifecycle_COMMAND_ACCEPTED)}, now)
|
||||||
|
}
|
||||||
@@ -3,11 +3,13 @@ package agent
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
rvboxv1 "github.com/rvbox/rvbox/gen/go/rvbox/v1"
|
rvboxv1 "github.com/rvbox/rvbox/gen/go/rvbox/v1"
|
||||||
"github.com/rvbox/rvbox/internal/agentproto"
|
"github.com/rvbox/rvbox/internal/agentproto"
|
||||||
|
"github.com/rvbox/rvbox/internal/client/spool"
|
||||||
"github.com/rvbox/rvbox/internal/domain"
|
"github.com/rvbox/rvbox/internal/domain"
|
||||||
"google.golang.org/protobuf/proto"
|
"google.golang.org/protobuf/proto"
|
||||||
"google.golang.org/protobuf/types/known/timestamppb"
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
@@ -82,6 +84,23 @@ func TestApplyReconcileResult_HP_SES_12(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPersistDispatchUsesImmutableHash_HP_DISPATCH_04(t *testing.T) {
|
||||||
|
store, err := spool.Open(context.Background(), spool.Options{DataDir: filepath.Join(t.TempDir(), "spool"), BusyTimeout: time.Second})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer store.Close()
|
||||||
|
dispatch := &rvboxv1.CommandDispatch{IssueUuid: "019c46f1-1d02-7000-8000-000000000064", CommandRevision: 1, TargetSessionGeneration: 7, IssueTime: timestamppb.Now(), ImmutableRequestSha256: []byte("12345678901234567890123456789012"), Spec: &rvboxv1.ExecutionSpec{ShellType: rvboxv1.ShellType_SHELL_POWERSHELL, Source: &rvboxv1.ExecutionSpec_CommandText{CommandText: "Write-Output ok"}}}
|
||||||
|
accepted, err := PersistDispatch(context.Background(), store, Session{Generation: 7}, dispatch, time.Now(), agentproto.DefaultLimits())
|
||||||
|
if err != nil || accepted.Duplicate {
|
||||||
|
t.Fatalf("PersistDispatch = %#v, %v", accepted, err)
|
||||||
|
}
|
||||||
|
duplicate, err := PersistDispatch(context.Background(), store, Session{Generation: 7}, dispatch, time.Now(), agentproto.DefaultLimits())
|
||||||
|
if err != nil || !duplicate.Duplicate {
|
||||||
|
t.Fatalf("duplicate PersistDispatch = %#v, %v", duplicate, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type fakeTransport struct {
|
type fakeTransport struct {
|
||||||
written []byte
|
written []byte
|
||||||
read []byte
|
read []byte
|
||||||
|
|||||||
Reference in New Issue
Block a user