Skip to content

Commit

Permalink
feat: add wait flag on deploy cmd
Browse files Browse the repository at this point in the history
fixes #885
  • Loading branch information
worstell committed Feb 8, 2024
1 parent fe2ff47 commit d7ec723
Show file tree
Hide file tree
Showing 7 changed files with 275 additions and 208 deletions.
13 changes: 13 additions & 0 deletions backend/controller/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -337,11 +337,13 @@ func (s *Service) Status(ctx context.Context, req *connect.Request[ftlv1.StatusR
return out
})
s.routesMu.RUnlock()
replicas := map[string]int32{}
protoRunners, err := slices.MapErr(status.Runners, func(r dal.Runner) (*ftlv1.StatusResponse_Runner, error) {
var deployment *string
if d, ok := r.Deployment.Get(); ok {
asString := d.String()
deployment = &asString
replicas[asString]++
}
labels, err := structpb.NewStruct(r.Labels)
if err != nil {
Expand All @@ -368,6 +370,7 @@ func (s *Service) Status(ctx context.Context, req *connect.Request[ftlv1.StatusR
Language: d.Language,
Name: d.Module,
MinReplicas: int32(d.MinReplicas),
Replicas: replicas[d.Name.String()],
Schema: d.Schema.ToProto().(*schemapb.Module), //nolint:forcetypeassert
Labels: labels,
}, nil
Expand Down Expand Up @@ -551,6 +554,16 @@ func (s *Service) RegisterRunner(ctx context.Context, stream *connect.ClientStre
} else if err != nil {
return nil, err
}

routes, err := s.dal.GetRoutingTable(ctx, nil)
if errors.Is(err, dal.ErrNotFound) {
routes = map[string][]dal.Route{}
} else if err != nil {
return nil, err
}
s.routesMu.Lock()
s.routes = routes
s.routesMu.Unlock()
}
if stream.Err() != nil {
return nil, stream.Err()
Expand Down
41 changes: 41 additions & 0 deletions cmd/ftl/cmd_deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"os"
"path/filepath"
"strings"
"time"

"connectrpc.com/connect"
"golang.org/x/exp/maps"
Expand All @@ -24,6 +25,7 @@ import (
type deployCmd struct {
Replicas int32 `short:"n" help:"Number of replicas to deploy." default:"1"`
ModuleDir string `arg:"" help:"Directory containing ftl.toml" type:"existingdir" default:"."`
Wait bool `help:"Only complete the deploy command when sufficient runners have been provisioned, i.e. the deployment is online and reachable." default:"false"`
}

func (d *deployCmd) Run(ctx context.Context, client ftlv1connect.ControllerServiceClient) error {
Expand Down Expand Up @@ -100,10 +102,49 @@ func (d *deployCmd) Run(ctx context.Context, client ftlv1connect.ControllerServi
return err
}

if d.Wait {
logger.Infof("Waiting for deployment %s to become ready", resp.Msg.DeploymentName)
err = d.checkReadiness(ctx, client, resp.Msg.DeploymentName)
if err != nil {
return err
}
}

logger.Infof("Successfully created deployment %s", resp.Msg.DeploymentName)
return nil
}

func (d *deployCmd) checkReadiness(ctx context.Context, client ftlv1connect.ControllerServiceClient, deploymentName string) error {
ticker := time.NewTicker(time.Second)
defer ticker.Stop()

for {
select {
case <-ticker.C:
status, err := client.Status(ctx, connect.NewRequest(&ftlv1.StatusRequest{
AllDeployments: true,
}))
if err != nil {
return err
}

var found bool
for _, deployment := range status.Msg.Deployments {
if deployment.Key == deploymentName {
found = true
if deployment.Replicas >= d.Replicas {
return nil
}
}
}
if !found {
return fmt.Errorf("deployment %s not found: %v", deploymentName, status.Msg.Deployments)
}
case <-ctx.Done():
return ctx.Err()
}
}
}
func (d *deployCmd) loadProtoSchema(deployDir string, config moduleconfig.ModuleConfig) (*schemapb.Module, error) {
schema := filepath.Join(deployDir, config.Schema)
content, err := os.ReadFile(schema)
Expand Down
1 change: 1 addition & 0 deletions cmd/ftl/cmd_dev.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ func (d *devCmd) Run(ctx context.Context, client ftlv1connect.ControllerServiceC
deploy := deployCmd{
Replicas: 1,
ModuleDir: dir,
Wait: d.ExitAfterDeploy,
}
err = deploy.Run(ctx, client)
if err != nil {
Expand Down
6 changes: 6 additions & 0 deletions frontend/src/protos/xyz/block/ftl/v1/ftl_pb.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 5 additions & 10 deletions integration/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ func TestLifecycle(t *testing.T) {
run("ftl", rd.initOpts...),
}},
{name: fmt.Sprintf("Deploy%s", rd.testSuffix), assertions: assertions{
run("ftl", "deploy", rd.moduleRoot),
run("ftl", "deploy", "--wait", rd.moduleRoot),
deploymentExists(rd.moduleName),
}},
{name: fmt.Sprintf("Call%s", rd.testSuffix), assertions: assertions{
Expand All @@ -65,7 +65,7 @@ func TestHttpIngress(t *testing.T) {
{name: fmt.Sprintf("HttpIngress%s", rd.testSuffix), assertions: assertions{
run("ftl", rd.initOpts...),
scaffoldTestData(runtime, "httpingress", rd.modulePath),
run("ftl", "deploy", rd.moduleRoot),
run("ftl", "deploy", "--wait", rd.moduleRoot),
httpCall(rd, http.MethodGet, "/users/123/posts/456", jsonData(t, obj{}), func(t testing.TB, resp *httpResponse) {
assert.Equal(t, 200, resp.status)
assert.Equal(t, []string{"Header from FTL"}, resp.headers["Get"])
Expand Down Expand Up @@ -146,7 +146,7 @@ func TestDatabase(t *testing.T) {
setUpModuleDB(dbName),
run("ftl", rd.initOpts...),
scaffoldTestData(runtime, "database", rd.modulePath),
run("ftl", "deploy", rd.moduleRoot),
run("ftl", "deploy", "--wait", rd.moduleRoot),
call(rd.moduleName, "insert", obj{"data": requestData}, func(t testing.TB, resp obj) {}),
validateModuleDB(dbName, requestData),
}},
Expand All @@ -163,10 +163,10 @@ func TestExternalCalls(t *testing.T) {
name: fmt.Sprintf("Call%sFrom%s", strcase.ToCamel(callee), strcase.ToCamel(runtime)),
assertions: assertions{
run("ftl", calleeRd.initOpts...),
run("ftl", "deploy", calleeRd.moduleRoot),
run("ftl", "deploy", "--wait", calleeRd.moduleRoot),
run("ftl", rd.initOpts...),
scaffoldTestData(runtime, "externalcalls", rd.modulePath),
run("ftl", "deploy", rd.moduleRoot),
run("ftl", "deploy", "--wait", rd.moduleRoot),
call(rd.moduleName, "call", obj{"name": "Alice"}, func(t testing.TB, resp obj) {
message, ok := resp["message"].(string)
assert.True(t, ok, "message is not a string")
Expand Down Expand Up @@ -405,11 +405,6 @@ func httpCall(rd runtimeData, method string, path string, body []byte, onRespons
assert.NoError(t, err)
defer resp.Body.Close()

if resp.StatusCode == http.StatusNotFound {
// error here so that the test retries in case the 404 is caused by the runner not being ready
return fmt.Errorf("endpoint not found: %s", path)
}

bodyBytes, err := io.ReadAll(resp.Body)
assert.NoError(t, err)

Expand Down
Loading

0 comments on commit d7ec723

Please sign in to comment.