Skip to content

Commit

Permalink
chore: integration test option to use ftl-provisioner (#2892)
Browse files Browse the repository at this point in the history
runs deployments through the provisioner if requested

This also adds a hidden CLI flags
`--use-provisioner` to deploy using the provisioner service instead of
controller
 `--provisioner-endpoint` to set the provisioner endpoint

Next, I will look how to integrate `ftl-provisioner` better with `ftl
serve`
  • Loading branch information
jvmakine authored Sep 30, 2024
1 parent b4a611d commit 13ed969
Show file tree
Hide file tree
Showing 12 changed files with 113 additions and 23 deletions.
21 changes: 21 additions & 0 deletions backend/provisioner/provisioner_integration_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//go:build integration

package provisioner_test

import (
"testing"

in "github.com/TBD54566975/ftl/internal/integration"
"github.com/alecthomas/assert/v2"
)

func TestDeploymentThroughProvisioner(t *testing.T) {
in.Run(t,
in.WithProvisioner(),
in.CopyModule("echo"),
in.Deploy("echo"),
in.Call("echo", "echo", "Bob", func(t testing.TB, response string) {
assert.Equal(t, "Hello, Bob!!!", response)
}),
)
}
1 change: 1 addition & 0 deletions backend/provisioner/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ var _ provisionerconnect.ProvisionerServiceHandler = (*Service)(nil)
func New(ctx context.Context, config Config, controllerClient ftlv1connect.ControllerServiceClient, devel bool) (*Service, error) {
return &Service{
controllerClient: controllerClient,
currentResources: map[string][]*provisioner.Resource{},
}, nil
}

Expand Down
14 changes: 14 additions & 0 deletions backend/provisioner/testdata/go/echo/echo.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// This is the echo module.
package echo

import (
"context"
"fmt"
)

// Echo returns a greeting with the current time.
//
//ftl:verb export
func Echo(ctx context.Context, req string) (string, error) {
return fmt.Sprintf("Hello, %s!!!", req), nil
}
2 changes: 2 additions & 0 deletions backend/provisioner/testdata/go/echo/ftl.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
module = "echo"
language = "go"
5 changes: 5 additions & 0 deletions backend/provisioner/testdata/go/echo/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module ftl/echo

go 1.23.0

replace github.com/TBD54566975/ftl => ./../../../../../..
Empty file.
2 changes: 1 addition & 1 deletion cmd/ftl-provisioner/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,5 @@ func main() {
kctx.FatalIfErrorf(err, "failed to initialize observability")

err = provisioner.Start(ctx, cli.ProvisionerConfig, false)
kctx.FatalIfErrorf(err)
kctx.FatalIfErrorf(err, "failed to start provisioner")
}
15 changes: 11 additions & 4 deletions frontend/cli/cmd_deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,26 @@ import (
"context"

"github.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/v1/ftlv1connect"
"github.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/v1beta1/provisioner/provisionerconnect"
"github.com/TBD54566975/ftl/internal/buildengine"
"github.com/TBD54566975/ftl/internal/projectconfig"
"github.com/TBD54566975/ftl/internal/rpc"
)

type deployCmd struct {
Replicas int32 `short:"n" help:"Number of replicas to deploy." default:"1"`
NoWait bool `help:"Do not wait for deployment to complete." default:"false"`
Build buildCmd `embed:""`
Replicas int32 `short:"n" help:"Number of replicas to deploy." default:"1"`
NoWait bool `help:"Do not wait for deployment to complete." default:"false"`
UseProvisioner bool `help:"Use the ftl-provisioner to deploy the application." default:"false" hidden:"true"`
Build buildCmd `embed:""`
}

func (d *deployCmd) Run(ctx context.Context, projConfig projectconfig.Config) error {
client := rpc.ClientFromContext[ftlv1connect.ControllerServiceClient](ctx)
var client buildengine.DeployClient
if d.UseProvisioner {
client = rpc.ClientFromContext[provisionerconnect.ProvisionerServiceClient](ctx)
} else {
client = rpc.ClientFromContext[ftlv1connect.ControllerServiceClient](ctx)
}
engine, err := buildengine.New(ctx, client, projConfig.Root(), d.Build.Dirs, buildengine.BuildEnv(d.Build.BuildEnv), buildengine.Parallelism(d.Build.Parallelism))
if err != nil {
return err
Expand Down
10 changes: 8 additions & 2 deletions frontend/cli/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (

"github.com/TBD54566975/ftl"
"github.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/v1/ftlv1connect"
"github.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/v1beta1/provisioner/provisionerconnect"
"github.com/TBD54566975/ftl/internal"
_ "github.com/TBD54566975/ftl/internal/automaxprocs" // Set GOMAXPROCS to match Linux container CPU quota.
"github.com/TBD54566975/ftl/internal/configuration"
Expand All @@ -29,8 +30,9 @@ import (
)

type InteractiveCLI struct {
Version kong.VersionFlag `help:"Show version."`
Endpoint *url.URL `default:"http://127.0.0.1:8892" help:"FTL endpoint to bind/connect to." env:"FTL_ENDPOINT"`
Version kong.VersionFlag `help:"Show version."`
Endpoint *url.URL `default:"http://127.0.0.1:8892" help:"FTL endpoint to bind/connect to." env:"FTL_ENDPOINT"`
ProvisionerEndpoint *url.URL `help:"Provisioner endpoint." env:"FTL_PROVISIONER_ENDPOINT" default:"http://127.0.0.1:8894" hidden:"true"`

Ping pingCmd `cmd:"" help:"Ping the FTL cluster."`
Status statusCmd `cmd:"" help:"Show FTL status."`
Expand Down Expand Up @@ -170,6 +172,10 @@ func makeBindContext(projectConfig projectconfig.Config, logger *log.Logger, can
ctx = rpc.ContextWithClient(ctx, controllerServiceClient)
kctx.BindTo(controllerServiceClient, (*ftlv1connect.ControllerServiceClient)(nil))

provisionerServiceClient := rpc.Dial(provisionerconnect.NewProvisionerServiceClient, cli.ProvisionerEndpoint.String(), log.Error)
ctx = rpc.ContextWithClient(ctx, provisionerServiceClient)
kctx.BindTo(provisionerServiceClient, (*provisionerconnect.ProvisionerServiceClient)(nil))

// Initialise configuration registries.
configRegistry := providers.NewRegistry[configuration.Configuration]()
configRegistry.Register(providers.NewEnvarFactory[configuration.Configuration]())
Expand Down
2 changes: 1 addition & 1 deletion internal/buildengine/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ func Deploy(ctx context.Context, module Module, replicas int32, waitForDeployOnl

gadResp, err := client.GetArtefactDiffs(ctx, connect.NewRequest(&ftlv1.GetArtefactDiffsRequest{ClientDigests: maps.Keys(filesByHash)}))
if err != nil {
return err
return fmt.Errorf("failed to get artefact diffs: %w", err)
}

moduleSchema, err := loadProtoSchema(moduleConfig, replicas)
Expand Down
11 changes: 8 additions & 3 deletions internal/integration/actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,11 +223,16 @@ func ExpectError(action Action, expectedErrorMsg ...string) Action {
func Deploy(module string) Action {
return Chain(
func(t testing.TB, ic TestContext) {
args := []string{"deploy"}
if ic.Provisioner != nil {
args = append(args, "--use-provisioner", "--provisioner-endpoint=http://localhost:8894")
}
if ic.kubeClient != nil {
Exec("ftl", "deploy", "--build-env", "GOOS=linux", "--build-env", "GOARCH=amd64", "--build-env", "CGO_ENABLED=0", module)(t, ic)
} else {
Exec("ftl", "deploy", module)(t, ic)
args = append(args, "--build-env", "GOOS=linux", "--build-env", "GOARCH=amd64", "--build-env", "CGO_ENABLED=0")
}
args = append(args, module)

Exec("ftl", args...)(t, ic)
},
Wait(module),
)
Expand Down
53 changes: 41 additions & 12 deletions internal/integration/harness.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
ftlv1 "github.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/v1"
"github.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/v1/console/pbconsoleconnect"
"github.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/v1/ftlv1connect"
"github.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/v1beta1/provisioner/provisionerconnect"
"github.com/TBD54566975/ftl/internal"
ftlexec "github.com/TBD54566975/ftl/internal/exec"
"github.com/TBD54566975/ftl/internal/log"
Expand Down Expand Up @@ -121,14 +122,25 @@ func WithoutController() Option {
}
}

// WithProvisioner is a Run* option that starts the provisioner service.
// if set, all deployments are done through the provisioner
func WithProvisioner() Option {
return func(o *options) {
o.startProvisioner = true
// provisioner always needs a controller to talk to
o.startController = true
}
}

type options struct {
languages []string
testDataDir string
ftlConfigPath string
startController bool
requireJava bool
envars map[string]string
kube bool
languages []string
testDataDir string
ftlConfigPath string
startController bool
startProvisioner bool
requireJava bool
envars map[string]string
kube bool
}

// Run an integration test.
Expand Down Expand Up @@ -194,7 +206,7 @@ func run(t *testing.T, actionsOrOptions ...ActionOrOption) {
if runtime.GOOS != "linux" || runtime.GOARCH != "amd64" {
// If we are already on linux/amd64 we don't need to rebuild, otherwise we now need a native one to interact with the kube cluster
Infof("Building FTL for native OS")
err = ftlexec.Command(ctx, log.Debug, rootDir, "just", "build", "ftl").RunBuffered(ctx)
err = ftlexec.Command(ctx, log.Debug, rootDir, "just", "build", "ftl", "ftl-provisioner").RunBuffered(ctx)
assert.NoError(t, err)
}
err = ftlexec.Command(ctx, log.Debug, filepath.Join(rootDir, "deployment"), "just", "wait-for-kube").RunBuffered(ctx)
Expand All @@ -205,7 +217,7 @@ func run(t *testing.T, actionsOrOptions ...ActionOrOption) {
assert.NoError(t, err)
} else {
Infof("Building ftl")
err = ftlexec.Command(ctx, log.Debug, rootDir, "just", "build", "ftl").RunBuffered(ctx)
err = ftlexec.Command(ctx, log.Debug, rootDir, "just", "build", "ftl", "ftl-provisioner").RunBuffered(ctx)
assert.NoError(t, err)
}
if opts.requireJava || slices.Contains(opts.languages, "java") {
Expand All @@ -224,6 +236,12 @@ func run(t *testing.T, actionsOrOptions ...ActionOrOption) {

var controller ftlv1connect.ControllerServiceClient
var console pbconsoleconnect.ConsoleServiceClient
var provisioner provisionerconnect.ProvisionerServiceClient
if opts.startProvisioner {
Infof("Starting ftl provisioner")
ctx = startProcess(ctx, t, filepath.Join(binDir, "ftl-provisioner"), "--ftl-endpoint=http://localhost:8892")
provisioner = rpc.Dial(provisionerconnect.NewProvisionerServiceClient, "http://localhost:8894", log.Debug)
}
if opts.startController {
Infof("Starting ftl cluster")
ctx = startProcess(ctx, t, filepath.Join(binDir, "ftl"), "serve", "--recreate")
Expand Down Expand Up @@ -262,6 +280,16 @@ func run(t *testing.T, actionsOrOptions ...ActionOrOption) {
})
}

if opts.startProvisioner {
ic.Provisioner = provisioner

Infof("Waiting for provisioner to be ready")
ic.AssertWithRetry(t, func(t testing.TB, ic TestContext) {
_, err := ic.Provisioner.Ping(ic, connect.NewRequest(&ftlv1.PingRequest{}))
assert.NoError(t, err)
})
}

Infof("Starting test")

for _, action := range actions {
Expand Down Expand Up @@ -317,9 +345,10 @@ type TestContext struct {
kubeClient *kubernetes.Clientset
kubeNamespace string

Controller ftlv1connect.ControllerServiceClient
Console pbconsoleconnect.ConsoleServiceClient
Verbs ftlv1connect.VerbServiceClient
Controller ftlv1connect.ControllerServiceClient
Provisioner provisionerconnect.ProvisionerServiceClient
Console pbconsoleconnect.ConsoleServiceClient
Verbs ftlv1connect.VerbServiceClient

realT *testing.T
}
Expand Down

0 comments on commit 13ed969

Please sign in to comment.