test: cover worker control plane bootstrap flow
This commit is contained in:
parent
dd7e31ce07
commit
9edf1a9bab
@ -4,7 +4,9 @@ package bootstrap
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/sha256"
|
||||||
"errors"
|
"errors"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"os"
|
"os"
|
||||||
@ -22,7 +24,14 @@ import (
|
|||||||
"proxy-pool/internal/controller/pool"
|
"proxy-pool/internal/controller/pool"
|
||||||
"proxy-pool/internal/controller/provider"
|
"proxy-pool/internal/controller/provider"
|
||||||
controllerRuntime "proxy-pool/internal/controller/runtime"
|
controllerRuntime "proxy-pool/internal/controller/runtime"
|
||||||
|
controllerWorker "proxy-pool/internal/controller/worker"
|
||||||
|
"proxy-pool/internal/domain/workerruntime"
|
||||||
"proxy-pool/internal/platform/credentials"
|
"proxy-pool/internal/platform/credentials"
|
||||||
|
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/credentials/insecure"
|
||||||
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
|
controlplanev1 "proxy-pool/gen/controlplane/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestProductionBootstrapOpensBothStoresAndCommitsStartupConfiguration(t *testing.T) {
|
func TestProductionBootstrapOpensBothStoresAndCommitsStartupConfiguration(t *testing.T) {
|
||||||
@ -80,6 +89,95 @@ func TestProductionBootstrapOpensBothStoresAndCommitsStartupConfiguration(t *tes
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestProductionBootstrapServesWorkerControlPlane(t *testing.T) {
|
||||||
|
postgresURL := os.Getenv("PROXY_POOL_TEST_POSTGRES_URL")
|
||||||
|
redisURL := os.Getenv("PROXY_POOL_TEST_REDIS_URL")
|
||||||
|
if postgresURL == "" || redisURL == "" {
|
||||||
|
t.Skip("PROXY_POOL_TEST_POSTGRES_URL and PROXY_POOL_TEST_REDIS_URL are required")
|
||||||
|
}
|
||||||
|
providerServer := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, _ *http.Request) {
|
||||||
|
_, _ = writer.Write([]byte("http://192.0.2.10:8080"))
|
||||||
|
}))
|
||||||
|
defer providerServer.Close()
|
||||||
|
namespace := "controller-worker-it-" + strconv.FormatInt(time.Now().UnixNano(), 10)
|
||||||
|
source := strings.ReplaceAll(bootstrapTestConfig, "postgres://fixture", postgresURL)
|
||||||
|
source = strings.ReplaceAll(source, "redis://fixture", redisURL)
|
||||||
|
source = strings.ReplaceAll(source, "https://provider.invalid/proxies", providerServer.URL)
|
||||||
|
source += `
|
||||||
|
controlPlane:
|
||||||
|
enabled: true
|
||||||
|
listen: 127.0.0.1:0
|
||||||
|
protocolVersion: 1
|
||||||
|
heartbeatInterval: 10s
|
||||||
|
sessionTTL: 30s
|
||||||
|
maxStaleAge: 10s
|
||||||
|
maxMessageBytes: 1048576
|
||||||
|
maxRuntimeCounters: 100
|
||||||
|
maxConcurrentStreams: 10
|
||||||
|
tls: {mode: disabled}
|
||||||
|
`
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
workerFactory := &integrationWorkerRuntimeFactory{ready: make(chan struct{})}
|
||||||
|
result := make(chan error, 1)
|
||||||
|
go func() {
|
||||||
|
result <- runWithWorkerFactory(ctx, Options{
|
||||||
|
ConfigPath: "controller.yaml", Resolver: &memoryResolver{files: map[string][]byte{"controller.yaml": []byte(source)}},
|
||||||
|
Now: time.Now, FingerprintKey: bootstrapTestFingerprintKey,
|
||||||
|
}, &productionInfrastructure{namespace: namespace}, integrationBlockingRuntimeFactory{}, workerFactory)
|
||||||
|
}()
|
||||||
|
select {
|
||||||
|
case <-workerFactory.ready:
|
||||||
|
case <-time.After(5 * time.Second):
|
||||||
|
t.Fatal("worker control plane did not start")
|
||||||
|
}
|
||||||
|
|
||||||
|
connection, err := grpc.NewClient(workerFactory.address, grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("grpc.NewClient(): %v", err)
|
||||||
|
}
|
||||||
|
defer connection.Close()
|
||||||
|
client := controlplanev1.NewWorkerControlPlaneClient(connection)
|
||||||
|
requestCtx, requestCancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer requestCancel()
|
||||||
|
registration, err := client.RegisterWorker(requestCtx, &controlplanev1.RegisterWorkerRequest{
|
||||||
|
WorkerId: "worker-a", InstanceId: "instance-a", Zone: "zone-a", SupportedProtocolVersion: 1,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("RegisterWorker(): %v", err)
|
||||||
|
}
|
||||||
|
store := newIntegrationWorkerStore(t, redisURL, namespace)
|
||||||
|
checksum := sha256.Sum256([]byte("snapshot-1"))
|
||||||
|
reference := workerruntime.SnapshotReference{
|
||||||
|
WorkerID: "worker-a", Version: 1, OwnershipEpoch: registration.GetOwnershipEpoch(), Checksum: checksum,
|
||||||
|
}
|
||||||
|
if err := store.RecordIssuedSnapshot(requestCtx, reference, time.Minute); err != nil {
|
||||||
|
t.Fatalf("RecordIssuedSnapshot(): %v", err)
|
||||||
|
}
|
||||||
|
if _, err := client.AcknowledgeSnapshot(requestCtx, &controlplanev1.AcknowledgeSnapshotRequest{
|
||||||
|
WorkerId: "worker-a", SessionId: registration.GetSessionId(), Version: 1,
|
||||||
|
OwnershipEpoch: registration.GetOwnershipEpoch(), Checksum: checksum[:], Applied: true,
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatalf("AcknowledgeSnapshot(): %v", err)
|
||||||
|
}
|
||||||
|
runtime, err := client.ReportRuntime(requestCtx, &controlplanev1.ReportRuntimeRequest{
|
||||||
|
WorkerId: "worker-a", SessionId: registration.GetSessionId(), SnapshotVersion: 1,
|
||||||
|
OwnershipEpoch: registration.GetOwnershipEpoch(), ReportSequence: 1, ObservedAt: timestamppb.Now(),
|
||||||
|
})
|
||||||
|
if err != nil || runtime.GetRequireFullSnapshot() || runtime.GetAcceptedOwnershipEpoch() != registration.GetOwnershipEpoch() {
|
||||||
|
t.Fatalf("ReportRuntime() = %+v, %v", runtime, err)
|
||||||
|
}
|
||||||
|
cancel()
|
||||||
|
select {
|
||||||
|
case err := <-result:
|
||||||
|
if !errors.Is(err, context.Canceled) {
|
||||||
|
t.Fatalf("run() error = %v, want context cancellation", err)
|
||||||
|
}
|
||||||
|
case <-time.After(5 * time.Second):
|
||||||
|
t.Fatal("controller did not stop")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type integrationRuntimeFactory struct {
|
type integrationRuntimeFactory struct {
|
||||||
status admin.Status
|
status admin.Status
|
||||||
readyStatus int
|
readyStatus int
|
||||||
@ -206,3 +304,62 @@ type integrationRunner struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (runner integrationRunner) Run(ctx context.Context) error { return runner.run(ctx) }
|
func (runner integrationRunner) Run(ctx context.Context) error { return runner.run(ctx) }
|
||||||
|
|
||||||
|
type integrationBlockingRuntimeFactory struct{}
|
||||||
|
|
||||||
|
func (integrationBlockingRuntimeFactory) New(
|
||||||
|
_ *config.Config,
|
||||||
|
_ controllerRuntime.Dependencies,
|
||||||
|
_ controllerRuntime.Options,
|
||||||
|
) (controllerRunner, error) {
|
||||||
|
return integrationRunner{run: func(ctx context.Context) error {
|
||||||
|
<-ctx.Done()
|
||||||
|
return ctx.Err()
|
||||||
|
}}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type integrationWorkerRuntimeFactory struct {
|
||||||
|
address string
|
||||||
|
ready chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (factory *integrationWorkerRuntimeFactory) New(
|
||||||
|
controlPlane config.ControlPlane,
|
||||||
|
service controllerWorker.Service,
|
||||||
|
) (controllerRunner, error) {
|
||||||
|
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
server, err := controllerWorker.NewServer(controlPlane, service, controllerWorker.DefaultServerOptions())
|
||||||
|
if err != nil {
|
||||||
|
_ = listener.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
factory.address = listener.Addr().String()
|
||||||
|
close(factory.ready)
|
||||||
|
return integrationRunner{run: func(ctx context.Context) error { return server.Serve(ctx, listener) }}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newIntegrationWorkerStore(t *testing.T, redisURL, namespace string) *redisactivity.Adapter {
|
||||||
|
t.Helper()
|
||||||
|
options, err := redis.ParseURL(redisURL)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("redis.ParseURL(): %v", err)
|
||||||
|
}
|
||||||
|
client := redis.NewClient(options)
|
||||||
|
t.Cleanup(func() { _ = client.Close() })
|
||||||
|
credentialStore, err := credentials.NewMemoryStore(100)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("credentials.NewMemoryStore(): %v", err)
|
||||||
|
}
|
||||||
|
store, err := redisactivity.New(client, redisactivity.Options{
|
||||||
|
Namespace: namespace, Credentials: credentialStore, OperationTTL: redisOperationTTL,
|
||||||
|
MaxCandidateScan: redisMinimumScan, MaxRuntimeCounters: 100, MaxInventoryScan: 100,
|
||||||
|
CleanupLimit: redisCleanupLimit,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("redisactivity.New(): %v", err)
|
||||||
|
}
|
||||||
|
return store
|
||||||
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user