Skip to content

Commit 55bb4ae

Browse files
authored
fix: wait for Blaxel sandboxes to finish deploying (#596)
* fix: wait for Blaxel sandboxes to finish deploying * fix: back off Blaxel deployment polling
1 parent 5d26040 commit 55bb4ae

2 files changed

Lines changed: 107 additions & 3 deletions

File tree

‎internal/machinepool/providers/blaxel/provider.go‎

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"errors"
66
"fmt"
77
"maps"
8+
"math/rand/v2"
89
"slices"
910
"strconv"
1011
"strings"
@@ -24,6 +25,7 @@ const (
2425
processStatusRunning sandboxProcessStatus = "running"
2526

2627
// Blaxel sandbox states follow https://docs.blaxel.ai/api-reference/compute/get-sandbox.
28+
sandboxDeploymentDeploying sandboxDeploymentStatus = "DEPLOYING"
2729
sandboxDeploymentDeployed sandboxDeploymentStatus = "DEPLOYED"
2830
sandboxDeploymentDeactivating sandboxDeploymentStatus = "DEACTIVATING"
2931
sandboxDeploymentDeleting sandboxDeploymentStatus = "DELETING"
@@ -34,12 +36,14 @@ const (
3436
sandboxRuntimeRunning sandboxRuntimeState = "RUNNING"
3537
sandboxRuntimeStandby sandboxRuntimeState = "STANDBY"
3638

37-
provisioningTimeout = 15 * time.Second
39+
provisioningTimeout = time.Minute
3840
initialAwakeProcessPollInterval = 100 * time.Millisecond
3941
installationLabel = "omnara-installation"
4042
machineLabel = "omnara-machine"
4143
)
4244

45+
var sandboxDeploymentPollInterval = time.Second
46+
4347
type sandboxDeploymentStatus string
4448
type sandboxRuntimeState string
4549
type sandboxProcessStatus string
@@ -171,6 +175,23 @@ func (p *provider) ProvisionMachine(
171175
)
172176
}
173177
result := providers.ProvisionMachineResult{ProviderResourceID: name}
178+
pollInterval := sandboxDeploymentPollInterval
179+
for normalizeSandboxDeploymentStatus(target.Status) == sandboxDeploymentDeploying {
180+
select {
181+
case <-ctx.Done():
182+
return result, fmt.Errorf("wait for blaxel sandbox %q to deploy: %w", name, ctx.Err())
183+
case <-time.After(pollInterval - rand.N(pollInterval)/4):
184+
}
185+
pollInterval = min(2*pollInterval, 5*time.Second)
186+
refreshed, found, err := api.GetSandbox(ctx, name)
187+
if err != nil {
188+
return result, err
189+
}
190+
if !found {
191+
return result, fmt.Errorf("blaxel sandbox %q disappeared while deploying", name)
192+
}
193+
target = refreshed
194+
}
174195
if sandboxDeploymentTerminal(target.Status) {
175196
if err := api.DeleteSandbox(ctx, name); err != nil {
176197
return result, err

‎internal/machinepool/providers/blaxel/provider_test.go‎

Lines changed: 85 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ func TestBlaxelProviderProvisionEnablesSleepWithInitialAwakeProcess(t *testing.T
9898
if result.SandboxURL == "" {
9999
t.Fatal("sleeping blaxel machine omitted sandbox url")
100100
}
101-
if provider.ProvisioningTimeout() != 15*time.Second {
101+
if provider.ProvisioningTimeout() != time.Minute {
102102
t.Fatalf("provisioning timeout = %s", provider.ProvisioningTimeout())
103103
}
104104
env := sandboxEnvMap(api.createRequests[0].Spec.Runtime.Envs)
@@ -432,6 +432,85 @@ func TestBlaxelProviderProvisionReplacesUnusableSandbox(t *testing.T) {
432432
}
433433
}
434434

435+
func TestBlaxelProviderProvisionWaitsForDeployingSandbox(t *testing.T) {
436+
savedPollInterval := sandboxDeploymentPollInterval
437+
sandboxDeploymentPollInterval = time.Millisecond
438+
t.Cleanup(func() { sandboxDeploymentPollInterval = savedPollInterval })
439+
machineID := uuid.New()
440+
sandboxName, err := providers.MachineAllocationName(testInstallationID(), machineID)
441+
if err != nil {
442+
t.Fatalf("blaxel sandbox name: %v", err)
443+
}
444+
for _, test := range []struct {
445+
name string
446+
nextStatuses []sandboxDeploymentStatus
447+
timeout time.Duration
448+
wantErr error
449+
wantResourceID string
450+
wantDeleted []string
451+
wantProcesses int
452+
}{
453+
{
454+
name: "deployed",
455+
nextStatuses: []sandboxDeploymentStatus{"DEPLOYING", "DEPLOYED"},
456+
wantResourceID: sandboxName,
457+
wantProcesses: 1,
458+
},
459+
{
460+
name: "failed",
461+
nextStatuses: []sandboxDeploymentStatus{"FAILED"},
462+
wantErr: providers.ErrResourceReplaced,
463+
wantDeleted: []string{sandboxName},
464+
},
465+
{
466+
name: "timeout",
467+
timeout: 10 * time.Millisecond,
468+
wantErr: context.DeadlineExceeded,
469+
wantResourceID: sandboxName,
470+
},
471+
} {
472+
t.Run(test.name, func(t *testing.T) {
473+
api := newFakeAPI()
474+
api.nextStatuses = test.nextStatuses
475+
api.sandboxesByName[sandboxName] = sandbox{
476+
Metadata: resourceMetadata{
477+
Name: sandboxName, URL: "https://sbx-deploying.test.bl.run",
478+
Labels: mustSandboxOwnershipLabels(t, testInstallationID(), machineID),
479+
},
480+
Status: "DEPLOYING",
481+
}
482+
ctx := context.Background()
483+
if test.timeout > 0 {
484+
var cancel context.CancelFunc
485+
ctx, cancel = context.WithTimeout(ctx, test.timeout)
486+
defer cancel()
487+
}
488+
489+
result, err := newTestProvider(api).ProvisionMachine(
490+
ctx,
491+
testInstallationID(),
492+
machineID,
493+
testMachineProvisioning(t, nil),
494+
"machine-token",
495+
nil,
496+
true,
497+
)
498+
if !errors.Is(err, test.wantErr) || result.ProviderResourceID != test.wantResourceID ||
499+
len(api.nextStatuses) != 0 || !slices.Equal(api.deletedNames, test.wantDeleted) ||
500+
len(api.processRequests) != test.wantProcesses {
501+
t.Fatalf(
502+
"provision error = %v resource = %q unread statuses = %v deleted = %v process requests = %d",
503+
err,
504+
result.ProviderResourceID,
505+
api.nextStatuses,
506+
api.deletedNames,
507+
len(api.processRequests),
508+
)
509+
}
510+
})
511+
}
512+
}
513+
435514
func TestBlaxelProviderProvisionRejectsNonReadySandbox(t *testing.T) {
436515
for _, test := range []struct {
437516
name string
@@ -440,7 +519,6 @@ func TestBlaxelProviderProvisionRejectsNonReadySandbox(t *testing.T) {
440519
{name: "uploading", status: "UPLOADING"},
441520
{name: "building", status: "BUILDING"},
442521
{name: "built", status: "BUILT"},
443-
{name: "deploying", status: "DEPLOYING"},
444522
{name: "deleting", status: "DELETING"},
445523
{name: "deactivating", status: "DEACTIVATING"},
446524
{name: "empty", status: ""},
@@ -685,6 +763,7 @@ type fakeAPI struct {
685763
createRequests []createSandboxRequest
686764
processRequests []processRequest
687765
wakeTarget sandbox
766+
nextStatuses []sandboxDeploymentStatus
688767
deletedNames []string
689768
createErr error
690769
startProcessErr error
@@ -738,6 +817,10 @@ func (f *fakeAPI) GetSandbox(
738817
name string,
739818
) (sandbox, bool, error) {
740819
target, found := f.sandboxesByName[name]
820+
if found && len(f.nextStatuses) > 0 {
821+
target.Status, f.nextStatuses = f.nextStatuses[0], f.nextStatuses[1:]
822+
f.sandboxesByName[name] = target
823+
}
741824
return target, found, nil
742825
}
743826

0 commit comments

Comments
 (0)