Skip to content

Commit bed765e

Browse files
removed status check, and added retries
1 parent 5f5e4d5 commit bed765e

File tree

2 files changed

+90
-32
lines changed

2 files changed

+90
-32
lines changed

bundle/direct/dresources/app.go

Lines changed: 14 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package dresources
22

33
import (
44
"context"
5+
"errors"
56
"fmt"
67
"time"
78

@@ -31,21 +32,28 @@ func (r *ResourceApp) DoRead(ctx context.Context, id string) (*apps.App, error)
3132
}
3233

3334
func (r *ResourceApp) DoCreate(ctx context.Context, config *apps.App) (string, *apps.App, error) {
34-
if err := r.waitForDeletion(ctx, config.Name); err != nil {
35-
return "", nil, err
36-
}
37-
3835
request := apps.CreateAppRequest{
3936
App: *config,
4037
NoCompute: true,
4138
ForceSendFields: nil,
4239
}
43-
waiter, err := r.client.Apps.Create(ctx, request)
40+
41+
retrier := retries.New[apps.App](retries.WithTimeout(time.Minute), retries.WithRetryFunc(shouldRetry))
42+
app, err := retrier.Run(ctx, func(ctx context.Context) (*apps.App, error) {
43+
waiter, err := r.client.Apps.Create(ctx, request)
44+
if err != nil {
45+
if errors.Is(err, apierr.ErrResourceAlreadyExists) {
46+
return nil, retries.Continues("app already exists, retrying")
47+
}
48+
return nil, retries.Halt(err)
49+
}
50+
return waiter.Response, nil
51+
})
4452
if err != nil {
4553
return "", nil, err
4654
}
4755

48-
return waiter.Response.Name, nil, nil
56+
return app.Name, nil, nil
4957
}
5058

5159
func (r *ResourceApp) DoUpdate(ctx context.Context, id string, config *apps.App, _ *Changes) (*apps.App, error) {
@@ -80,32 +88,6 @@ func (r *ResourceApp) WaitAfterCreate(ctx context.Context, config *apps.App) (*a
8088
return r.waitForApp(ctx, r.client, config.Name)
8189
}
8290

83-
func (r *ResourceApp) waitForDeletion(ctx context.Context, name string) error {
84-
retrier := retries.New[struct{}](retries.WithTimeout(10*time.Minute), retries.WithRetryFunc(shouldRetry))
85-
_, err := retrier.Run(ctx, func(ctx context.Context) (*struct{}, error) {
86-
app, err := r.client.Apps.GetByName(ctx, name)
87-
if err != nil {
88-
if apierr.IsMissing(err) {
89-
return nil, nil
90-
}
91-
return nil, retries.Halt(err)
92-
}
93-
94-
if app.ComputeStatus == nil {
95-
return nil, retries.Continues("waiting for compute status")
96-
}
97-
98-
switch app.ComputeStatus.State {
99-
case apps.ComputeStateDeleting:
100-
return nil, retries.Continues("app is deleting")
101-
default:
102-
err := fmt.Errorf("app %s already exists", name)
103-
return nil, retries.Halt(err)
104-
}
105-
})
106-
return err
107-
}
108-
10991
// waitForApp waits for the app to reach the target state. The target state is either ACTIVE or STOPPED.
11092
// Apps with no_compute set to true will reach the STOPPED state, otherwise they will reach the ACTIVE state.
11193
// We can't use the default waiter from SDK because it only waits on ACTIVE state but we need also STOPPED state.
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package dresources
2+
3+
import (
4+
"context"
5+
"testing"
6+
"time"
7+
8+
"github.com/databricks/databricks-sdk-go/apierr"
9+
"github.com/databricks/databricks-sdk-go/experimental/mocks"
10+
"github.com/databricks/databricks-sdk-go/service/apps"
11+
"github.com/stretchr/testify/assert"
12+
"github.com/stretchr/testify/mock"
13+
"github.com/stretchr/testify/require"
14+
)
15+
16+
func TestDoCreate_RetriesOnAlreadyExists(t *testing.T) {
17+
ctx := context.Background()
18+
m := mocks.NewMockWorkspaceClient(t)
19+
appsAPI := m.GetMockAppsAPI()
20+
21+
callCount := 0
22+
appsAPI.EXPECT().Create(mock.Anything, mock.Anything).RunAndReturn(
23+
func(ctx context.Context, req apps.CreateAppRequest) (*apps.WaitGetAppActive[apps.App], error) {
24+
callCount++
25+
if callCount == 1 {
26+
return nil, apierr.ErrResourceAlreadyExists
27+
}
28+
return &apps.WaitGetAppActive[apps.App]{Response: &apps.App{Name: "test-app"}}, nil
29+
},
30+
)
31+
32+
r := (&ResourceApp{}).New(m.WorkspaceClient)
33+
name, _, err := r.DoCreate(ctx, &apps.App{Name: "test-app"})
34+
35+
require.NoError(t, err)
36+
assert.Equal(t, "test-app", name)
37+
assert.Equal(t, 2, callCount, "expected Create to be called twice (1 retry)")
38+
}
39+
40+
func TestDoCreate_FailsAfterTimeout(t *testing.T) {
41+
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
42+
defer cancel()
43+
44+
m := mocks.NewMockWorkspaceClient(t)
45+
appsAPI := m.GetMockAppsAPI()
46+
47+
callCount := 0
48+
appsAPI.EXPECT().Create(mock.Anything, mock.Anything).RunAndReturn(
49+
func(ctx context.Context, req apps.CreateAppRequest) (*apps.WaitGetAppActive[apps.App], error) {
50+
callCount++
51+
return nil, apierr.ErrResourceAlreadyExists
52+
},
53+
)
54+
55+
r := (&ResourceApp{}).New(m.WorkspaceClient)
56+
_, _, err := r.DoCreate(ctx, &apps.App{Name: "test-app"})
57+
58+
require.Error(t, err)
59+
assert.Greater(t, callCount, 1, "expected Create to be called multiple times before timeout")
60+
}
61+
62+
func TestDoCreate_FailsImmediatelyOnOtherErrors(t *testing.T) {
63+
ctx := context.Background()
64+
m := mocks.NewMockWorkspaceClient(t)
65+
appsAPI := m.GetMockAppsAPI()
66+
67+
// Return a different error - should not retry
68+
appsAPI.EXPECT().Create(mock.Anything, mock.Anything).
69+
Return(nil, apierr.ErrPermissionDenied).Once()
70+
71+
r := (&ResourceApp{}).New(m.WorkspaceClient)
72+
_, _, err := r.DoCreate(ctx, &apps.App{Name: "test-app"})
73+
74+
require.Error(t, err)
75+
assert.ErrorIs(t, err, apierr.ErrPermissionDenied)
76+
}

0 commit comments

Comments
 (0)