mirror of https://github.com/hashicorp/terraform
Merge pull request #35247 from hashicorp/alisdair/rpcapi-stop
rpcapi: Allow stopping long-running operationspull/35262/head
commit
c8eec89c3d
@ -0,0 +1,85 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package rpcapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/internal/rpcapi/terraform1"
|
||||
)
|
||||
|
||||
func TestSetupServer_Handshake(t *testing.T) {
|
||||
called := 0
|
||||
server := newSetupServer(func(ctx context.Context, req *terraform1.Handshake_Request, stopper *stopper) (*terraform1.ServerCapabilities, error) {
|
||||
called++
|
||||
if got, want := req.Config.Credentials["localterraform.com"].Token, "boop"; got != want {
|
||||
t.Fatalf("incorrect token. got %q, want %q", got, want)
|
||||
}
|
||||
return &terraform1.ServerCapabilities{}, nil
|
||||
})
|
||||
|
||||
req := &terraform1.Handshake_Request{
|
||||
Capabilities: &terraform1.ClientCapabilities{},
|
||||
Config: &terraform1.Config{
|
||||
Credentials: map[string]*terraform1.HostCredential{
|
||||
"localterraform.com": {
|
||||
Token: "boop",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
_, err := server.Handshake(context.Background(), req)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
if called != 1 {
|
||||
t.Errorf("unexpected initOthers call count %d, want 1", called)
|
||||
}
|
||||
|
||||
_, err = server.Handshake(context.Background(), req)
|
||||
if err == nil || !strings.Contains(err.Error(), "handshake already completed") {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
if called != 1 {
|
||||
t.Errorf("unexpected initOthers call count %d, want 1", called)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetupServer_Stop(t *testing.T) {
|
||||
var s *stopper
|
||||
server := newSetupServer(func(ctx context.Context, req *terraform1.Handshake_Request, stopper *stopper) (*terraform1.ServerCapabilities, error) {
|
||||
s = stopper
|
||||
return &terraform1.ServerCapabilities{}, nil
|
||||
})
|
||||
_, err := server.Handshake(context.Background(), nil)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
if s == nil {
|
||||
t.Fatal("stopper not passed to initOthers")
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
|
||||
var stops []stopChan
|
||||
for range 2 {
|
||||
stops = append(stops, s.add())
|
||||
wg.Add(1)
|
||||
}
|
||||
|
||||
for _, stop := range stops {
|
||||
stop := stop
|
||||
go func() {
|
||||
<-stop
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
|
||||
server.Stop(context.Background(), nil)
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
@ -0,0 +1,57 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package rpcapi
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
type stopChan chan struct{}
|
||||
|
||||
// stopper allows the RPC API to stop in-progress long-running operations. Each
|
||||
// operation must add a new stop to the stopper, and remove it if the operation
|
||||
// completes successfully. If a Stop RPC is received while the operation is
|
||||
// running, the stops will all be processed, signalling to each operation that
|
||||
// it should abort.
|
||||
//
|
||||
// Each stop is represented by a channel, which is closed to indicate that the
|
||||
// operation should stop.
|
||||
type stopper struct {
|
||||
stops map[stopChan]struct{}
|
||||
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func newStopper() *stopper {
|
||||
return &stopper{
|
||||
stops: make(map[stopChan]struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *stopper) add() stopChan {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
stop := make(chan struct{})
|
||||
s.stops[stop] = struct{}{}
|
||||
|
||||
return stop
|
||||
}
|
||||
|
||||
func (s *stopper) remove(stop stopChan) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
delete(s.stops, stop)
|
||||
}
|
||||
|
||||
func (s *stopper) stop() {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
for stop := range s.stops {
|
||||
close(stop)
|
||||
delete(s.stops, stop)
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Loading…
Reference in new issue