pulumi/pkg/resource/deploy/source_query_test.go
Alex Clemmer 34093b1361 Allow provider loading in query mode
This commit will introduce the ability to load providers in `query`
mode.

Previously, `query` mode has been effectively a stand-alone execution
environment for language hosts, running without (e.g.) the
`StepExecutor` and similar engine facilities, but with some minimal
constructs hooked up, notably the ability to retrieve stack snapshots
from the backend for querying.

This commit extends this functionality somewhat by allowing `query` to
load Pulumi resource providers, and to run `Invoke` on them. This will
allow us, in the future, to "query" resource providers in the same way
we can query stack snapshots.
2019-10-23 15:14:56 -07:00

199 lines
5.2 KiB
Go

// Copyright 2016-2018, Pulumi Corporation.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package deploy
import (
"context"
"testing"
pbempty "github.com/golang/protobuf/ptypes/empty"
"github.com/pulumi/pulumi/pkg/util/result"
pulumirpc "github.com/pulumi/pulumi/sdk/proto/go"
"github.com/stretchr/testify/assert"
)
func TestQuerySource_Trivial_Wait(t *testing.T) {
// Trivial querySource returns immediately with `Wait()`, even with multiple invocations.
// Success case.
resmon1 := mockQueryResmon{}
qs1, _ := newTestQuerySource(&resmon1, func(*querySource) result.Result {
return nil
})
qs1.forkRun()
res := qs1.Wait()
assert.Nil(t, res)
assert.False(t, resmon1.cancelled)
res = qs1.Wait()
assert.Nil(t, res)
assert.False(t, resmon1.cancelled)
// Failure case.
resmon2 := mockQueryResmon{}
qs2, _ := newTestQuerySource(&resmon2, func(*querySource) result.Result {
return result.Error("failed")
})
qs2.forkRun()
res = qs2.Wait()
assert.False(t, res.IsBail())
assert.NotNil(t, res.Error())
assert.False(t, resmon2.cancelled)
res = qs2.Wait()
assert.False(t, res.IsBail())
assert.NotNil(t, res.Error())
assert.False(t, resmon2.cancelled)
}
func TestQuerySource_Async_Wait(t *testing.T) {
// `Wait()` executes asynchronously.
// Success case.
//
// test blocks until querySource signals execution has started
// -> querySource blocks until test acknowledges querySource's signal
// -> test blocks on `Wait()` until querySource completes.
qs1Start, qs1StartAck := make(chan interface{}), make(chan interface{})
resmon1 := mockQueryResmon{}
qs1, _ := newTestQuerySource(&resmon1, func(*querySource) result.Result {
qs1Start <- struct{}{}
<-qs1StartAck
return nil
})
qs1.forkRun()
// Wait until querySource starts, then acknowledge starting.
<-qs1Start
go func() {
qs1StartAck <- struct{}{}
}()
// Wait for querySource to complete.
res := qs1.Wait()
assert.Nil(t, res)
assert.False(t, resmon1.cancelled)
res = qs1.Wait()
assert.Nil(t, res)
assert.False(t, resmon1.cancelled)
// Cancellation case.
//
// test blocks until querySource signals execution has started
// -> querySource blocks until test acknowledges querySource's signal
// -> test blocks on `Wait()` until querySource completes.
qs2Start, qs2StartAck := make(chan interface{}), make(chan interface{})
resmon2 := mockQueryResmon{}
qs2, cancelQs2 := newTestQuerySource(&resmon2, func(*querySource) result.Result {
qs2Start <- struct{}{}
// Block forever.
<-qs2StartAck
return nil
})
qs2.forkRun()
// Wait until querySource starts, then cancel.
<-qs2Start
go func() {
cancelQs2()
}()
// Wait for querySource to complete.
res = qs2.Wait()
assert.Nil(t, res)
assert.True(t, resmon2.cancelled)
res = qs2.Wait()
assert.Nil(t, res)
assert.True(t, resmon2.cancelled)
}
func TestQueryResourceMonitor_UnsupportedOperations(t *testing.T) {
rm := &queryResmon{}
_, err := rm.ReadResource(context.TODO(), nil)
assert.Error(t, err)
assert.Equal(t, "Query mode does not support reading resources", err.Error())
_, err = rm.RegisterResource(context.TODO(), nil)
assert.Error(t, err)
assert.Equal(t, "Query mode does not support creating, updating, or deleting resources", err.Error())
_, err = rm.RegisterResourceOutputs(context.TODO(), nil)
assert.Error(t, err)
assert.Equal(t, "Query mode does not support registering resource operations", err.Error())
}
//
// Test querySoruce constructor.
//
func newTestQuerySource(mon SourceResourceMonitor,
runLangPlugin func(*querySource) result.Result) (*querySource, context.CancelFunc) {
cancel, cancelFunc := context.WithCancel(context.Background())
return &querySource{
mon: mon,
runLangPlugin: runLangPlugin,
langPluginFinChan: make(chan result.Result),
cancel: cancel,
}, cancelFunc
}
//
// Mock resource monitor.
//
type mockQueryResmon struct {
cancelled bool
}
var _ SourceResourceMonitor = (*mockQueryResmon)(nil)
func (rm *mockQueryResmon) Address() string {
panic("not implemented")
}
func (rm *mockQueryResmon) Cancel() error {
rm.cancelled = true
return nil
}
func (rm *mockQueryResmon) Invoke(ctx context.Context,
req *pulumirpc.InvokeRequest) (*pulumirpc.InvokeResponse, error) {
panic("not implemented")
}
func (rm *mockQueryResmon) ReadResource(ctx context.Context,
req *pulumirpc.ReadResourceRequest) (*pulumirpc.ReadResourceResponse, error) {
panic("not implemented")
}
func (rm *mockQueryResmon) RegisterResource(ctx context.Context,
req *pulumirpc.RegisterResourceRequest) (*pulumirpc.RegisterResourceResponse, error) {
panic("not implemented")
}
func (rm *mockQueryResmon) RegisterResourceOutputs(ctx context.Context,
req *pulumirpc.RegisterResourceOutputsRequest) (*pbempty.Empty, error) {
panic("not implemented")
}