Skip to content

Commit 9d72679

Browse files
committed
test: new integration test TestOpenWorkspaceFromPrebuild
Signed-off-by: JenTing Hsiao <[email protected]>
1 parent 3ac0c02 commit 9d72679

File tree

2 files changed

+263
-4
lines changed

2 files changed

+263
-4
lines changed

test/pkg/integration/workspace.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -91,12 +91,12 @@ type LaunchWorkspaceDirectlyResult struct {
9191
LastStatus *wsmanapi.WorkspaceStatus
9292
}
9393

94-
type stopWorkspaceFunc = func(waitForStop bool, api *ComponentAPI) (*wsmanapi.WorkspaceStatus, error)
94+
type StopWorkspaceFunc = func(waitForStop bool, api *ComponentAPI) (*wsmanapi.WorkspaceStatus, error)
9595

9696
// LaunchWorkspaceDirectly starts a workspace pod by talking directly to ws-manager.
9797
// Whenever possible prefer this function over LaunchWorkspaceFromContextURL, because
9898
// it has fewer prerequisites.
99-
func LaunchWorkspaceDirectly(t *testing.T, ctx context.Context, api *ComponentAPI, opts ...LaunchWorkspaceDirectlyOpt) (*LaunchWorkspaceDirectlyResult, stopWorkspaceFunc, error) {
99+
func LaunchWorkspaceDirectly(t *testing.T, ctx context.Context, api *ComponentAPI, opts ...LaunchWorkspaceDirectlyOpt) (*LaunchWorkspaceDirectlyResult, StopWorkspaceFunc, error) {
100100
options := launchWorkspaceDirectlyOptions{
101101
BaseImage: "docker.io/gitpod/workspace-full:latest",
102102
}
@@ -225,7 +225,7 @@ func LaunchWorkspaceDirectly(t *testing.T, ctx context.Context, api *ComponentAP
225225
// fail the test.
226226
//
227227
// When possible, prefer the less complex LaunchWorkspaceDirectly.
228-
func LaunchWorkspaceFromContextURL(t *testing.T, ctx context.Context, contextURL string, username string, api *ComponentAPI, serverOpts ...GitpodServerOpt) (*protocol.WorkspaceInfo, stopWorkspaceFunc, error) {
228+
func LaunchWorkspaceFromContextURL(t *testing.T, ctx context.Context, contextURL string, username string, api *ComponentAPI, serverOpts ...GitpodServerOpt) (*protocol.WorkspaceInfo, StopWorkspaceFunc, error) {
229229
var defaultServerOpts []GitpodServerOpt
230230
if username != "" {
231231
defaultServerOpts = []GitpodServerOpt{WithGitpodUser(username)}
@@ -281,7 +281,7 @@ func LaunchWorkspaceFromContextURL(t *testing.T, ctx context.Context, contextURL
281281
return wi, stopWs, nil
282282
}
283283

284-
func stopWsF(t *testing.T, instanceID string, api *ComponentAPI) stopWorkspaceFunc {
284+
func stopWsF(t *testing.T, instanceID string, api *ComponentAPI) StopWorkspaceFunc {
285285
return func(waitForStop bool, api *ComponentAPI) (*wsmanapi.WorkspaceStatus, error) {
286286
sctx, scancel := context.WithTimeout(context.Background(), perCallTimeout)
287287
defer scancel()

test/tests/components/ws-manager/prebuild_test.go

+259
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ package wsmanager
77
import (
88
"context"
99
"encoding/json"
10+
"fmt"
11+
"path/filepath"
12+
"reflect"
13+
"strings"
1014
"testing"
1115
"time"
1216

@@ -15,6 +19,7 @@ import (
1519

1620
csapi "github.com/gitpod-io/gitpod/content-service/api"
1721
gitpod "github.com/gitpod-io/gitpod/gitpod-protocol"
22+
agent "github.com/gitpod-io/gitpod/test/pkg/agent/workspace/api"
1823
"github.com/gitpod-io/gitpod/test/pkg/integration"
1924
wsmanapi "github.com/gitpod-io/gitpod/ws-manager/api"
2025
)
@@ -171,3 +176,257 @@ func TestPrebuildWorkspaceTaskFail(t *testing.T) {
171176

172177
testEnv.Test(t, f)
173178
}
179+
180+
const (
181+
prebuildLogPath string = "/workspace/.gitpod"
182+
prebuildLog string = "'🤙 This task ran as a workspace prebuild'"
183+
)
184+
185+
func TestOpenWorkspaceFromPrebuild(t *testing.T) {
186+
f := features.New("prebuild").
187+
WithLabel("component", "ws-manager").
188+
Assess("it should open workspace from prebuild successfully", func(_ context.Context, t *testing.T, cfg *envconf.Config) context.Context {
189+
tests := []struct {
190+
Name string
191+
ContextURL string
192+
WorkspaceRoot string
193+
CheckoutLocation string
194+
FF []wsmanapi.WorkspaceFeatureFlag
195+
}{
196+
{
197+
Name: "classic",
198+
ContextURL: "https://github.com/gitpod-io/empty",
199+
CheckoutLocation: "empty",
200+
WorkspaceRoot: "/workspace/empty",
201+
},
202+
{
203+
Name: "pvc",
204+
ContextURL: "https://github.com/gitpod-io/empty",
205+
CheckoutLocation: "empty",
206+
WorkspaceRoot: "/workspace/empty",
207+
FF: []wsmanapi.WorkspaceFeatureFlag{wsmanapi.WorkspaceFeatureFlag_PERSISTENT_VOLUME_CLAIM},
208+
},
209+
}
210+
211+
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(10*len(tests))*time.Minute)
212+
defer cancel()
213+
214+
for _, test := range tests {
215+
t.Run(test.Name, func(t *testing.T) {
216+
api := integration.NewComponentAPI(ctx, cfg.Namespace(), kubeconfig, cfg.Client())
217+
t.Cleanup(func() {
218+
api.Done(t)
219+
})
220+
221+
// create a prebuild and stop workspace
222+
_, prebuildStopWs, err := integration.LaunchWorkspaceDirectly(t, ctx, api, integration.WithRequestModifier(func(req *wsmanapi.StartWorkspaceRequest) error {
223+
req.Type = wsmanapi.WorkspaceType_PREBUILD
224+
req.Spec.Envvars = append(req.Spec.Envvars, &wsmanapi.EnvironmentVariable{
225+
Name: "GITPOD_TASKS",
226+
Value: `[{ "init": "echo \"some output\" > someFile; sleep 60; exit 0;" }]`,
227+
})
228+
req.Spec.FeatureFlags = test.FF
229+
req.Spec.Initializer = &csapi.WorkspaceInitializer{
230+
Spec: &csapi.WorkspaceInitializer_Git{
231+
Git: &csapi.GitInitializer{
232+
RemoteUri: test.ContextURL,
233+
TargetMode: csapi.CloneTargetMode_REMOTE_BRANCH,
234+
CloneTaget: "main",
235+
CheckoutLocation: test.CheckoutLocation,
236+
Config: &csapi.GitConfig{},
237+
},
238+
},
239+
}
240+
req.Spec.WorkspaceLocation = test.CheckoutLocation
241+
return nil
242+
}))
243+
if err != nil {
244+
t.Fatalf("cannot launch a workspace: %q", err)
245+
}
246+
247+
prebuildSnapshot, vsInfo, err := stopWorkspaceAndFindSnapshot(prebuildStopWs, api)
248+
if err != nil {
249+
t.Fatalf("stop workspace and find snapshot error: %v", err)
250+
}
251+
252+
// launch the workspace from prebuild
253+
ws, stopWs, err := integration.LaunchWorkspaceDirectly(t, ctx, api, integration.WithRequestModifier(func(req *wsmanapi.StartWorkspaceRequest) error {
254+
req.Spec.FeatureFlags = test.FF
255+
req.Spec.Initializer = &csapi.WorkspaceInitializer{
256+
Spec: &csapi.WorkspaceInitializer_Prebuild{
257+
Prebuild: &csapi.PrebuildInitializer{
258+
Prebuild: &csapi.SnapshotInitializer{
259+
Snapshot: prebuildSnapshot,
260+
FromVolumeSnapshot: reflect.DeepEqual(test.FF, []wsmanapi.WorkspaceFeatureFlag{wsmanapi.WorkspaceFeatureFlag_PERSISTENT_VOLUME_CLAIM}),
261+
},
262+
Git: []*csapi.GitInitializer{
263+
{
264+
RemoteUri: test.ContextURL,
265+
TargetMode: csapi.CloneTargetMode_REMOTE_BRANCH,
266+
CloneTaget: "main",
267+
CheckoutLocation: test.CheckoutLocation,
268+
Config: &csapi.GitConfig{},
269+
},
270+
},
271+
},
272+
},
273+
}
274+
req.Spec.VolumeSnapshot = vsInfo
275+
req.Spec.WorkspaceLocation = test.CheckoutLocation
276+
return nil
277+
}))
278+
if err != nil {
279+
t.Fatalf("cannot launch a workspace: %q", err)
280+
}
281+
282+
defer func() {
283+
// stop workspace in defer function to prevent we forget to stop the workspace
284+
if err := stopWorkspace(t, cfg, stopWs); err != nil {
285+
t.Errorf("cannot stop workspace: %q", err)
286+
}
287+
}()
288+
289+
rsa, closer, err := integration.Instrument(integration.ComponentWorkspace, "workspace", cfg.Namespace(), kubeconfig, cfg.Client(),
290+
integration.WithInstanceID(ws.Req.Id),
291+
)
292+
if err != nil {
293+
t.Fatal(err)
294+
}
295+
t.Cleanup(func() {
296+
rsa.Close()
297+
})
298+
integration.DeferCloser(t, closer)
299+
300+
// check prebuild log message exists
301+
var resp agent.ExecResponse
302+
var checkPrebuildSuccess bool
303+
for i := 0; i < 10; i++ {
304+
err = rsa.Call("WorkspaceAgent.Exec", &agent.ExecRequest{
305+
Dir: prebuildLogPath,
306+
Command: "bash",
307+
Args: []string{
308+
"-c",
309+
fmt.Sprintf("grep %s *", prebuildLog),
310+
},
311+
}, &resp)
312+
if err == nil && resp.ExitCode == 0 && strings.Trim(resp.Stdout, " \t\n") != "" {
313+
checkPrebuildSuccess = true
314+
break
315+
}
316+
317+
// wait 3 seconds and check
318+
time.Sleep(3 * time.Second)
319+
}
320+
321+
if !checkPrebuildSuccess {
322+
t.Fatalf("cannot found the prebuild message %s in %s, err:%v, exitCode:%d, stdout:%s", prebuildLog, prebuildLogPath, err, resp.ExitCode, resp.Stdout)
323+
}
324+
325+
// write file foobar.txt and stop the workspace
326+
err = rsa.Call("WorkspaceAgent.WriteFile", &agent.WriteFileRequest{
327+
Path: fmt.Sprintf("%s/foobar.txt", test.WorkspaceRoot),
328+
Content: []byte("hello world"),
329+
Mode: 0644,
330+
}, &resp)
331+
if err != nil {
332+
t.Fatalf("cannot write file %s", fmt.Sprintf("%s/foobar.txt", test.WorkspaceRoot))
333+
}
334+
335+
sctx, scancel := context.WithTimeout(context.Background(), 5*time.Minute)
336+
defer scancel()
337+
338+
sapi := integration.NewComponentAPI(sctx, cfg.Namespace(), kubeconfig, cfg.Client())
339+
defer sapi.Done(t)
340+
341+
// stop workspace to get the volume snapshot information
342+
_, vsInfo, err = stopWorkspaceAndFindSnapshot(stopWs, sapi)
343+
if err != nil {
344+
t.Fatal(err)
345+
}
346+
347+
// reopen the workspace and make sure the file foobar.txt exists
348+
ws1, stopWs1, err := integration.LaunchWorkspaceDirectly(t, ctx, api, integration.WithRequestModifier(func(req *wsmanapi.StartWorkspaceRequest) error {
349+
req.ServicePrefix = ws.Req.ServicePrefix
350+
req.Metadata.MetaId = ws.Req.Metadata.MetaId
351+
req.Metadata.Owner = ws.Req.Metadata.Owner
352+
req.Spec.FeatureFlags = test.FF
353+
req.Spec.Initializer = &csapi.WorkspaceInitializer{
354+
Spec: &csapi.WorkspaceInitializer_Backup{
355+
Backup: &csapi.FromBackupInitializer{
356+
CheckoutLocation: test.CheckoutLocation,
357+
FromVolumeSnapshot: reflect.DeepEqual(test.FF, []wsmanapi.WorkspaceFeatureFlag{wsmanapi.WorkspaceFeatureFlag_PERSISTENT_VOLUME_CLAIM}),
358+
},
359+
},
360+
}
361+
req.Spec.VolumeSnapshot = vsInfo
362+
req.Spec.WorkspaceLocation = test.CheckoutLocation
363+
return nil
364+
}))
365+
if err != nil {
366+
t.Fatalf("cannot launch a workspace: %q", err)
367+
}
368+
369+
defer func() {
370+
// stop workspace in defer function to prevent we forget to stop the workspace
371+
if err := stopWorkspace(t, cfg, stopWs1); err != nil {
372+
t.Errorf("cannot stop workspace: %q", err)
373+
}
374+
}()
375+
376+
rsa, closer, err = integration.Instrument(integration.ComponentWorkspace, "workspace", cfg.Namespace(), kubeconfig, cfg.Client(),
377+
integration.WithInstanceID(ws1.Req.Id),
378+
)
379+
if err != nil {
380+
t.Fatal(err)
381+
}
382+
integration.DeferCloser(t, closer)
383+
384+
var ls agent.ListDirResponse
385+
err = rsa.Call("WorkspaceAgent.ListDir", &agent.ListDirRequest{
386+
Dir: test.WorkspaceRoot,
387+
}, &ls)
388+
if err != nil {
389+
t.Fatal(err)
390+
}
391+
rsa.Close()
392+
393+
var found bool
394+
for _, f := range ls.Files {
395+
if filepath.Base(f) == "foobar.txt" {
396+
found = true
397+
break
398+
}
399+
}
400+
if !found {
401+
t.Fatal("did not find foobar.txt from previous workspace instance")
402+
}
403+
})
404+
}
405+
return ctx
406+
}).
407+
Feature()
408+
409+
testEnv.Test(t, f)
410+
}
411+
412+
func stopWorkspaceAndFindSnapshot(StopWorkspaceFunc integration.StopWorkspaceFunc, api *integration.ComponentAPI) (string, *wsmanapi.VolumeSnapshotInfo, error) {
413+
lastStatus, err := StopWorkspaceFunc(true, api)
414+
if err != nil {
415+
return "", nil, err
416+
}
417+
if lastStatus == nil && lastStatus.Conditions == nil {
418+
return "", nil, nil
419+
}
420+
return lastStatus.Conditions.Snapshot, lastStatus.Conditions.VolumeSnapshot, nil
421+
}
422+
423+
func stopWorkspace(t *testing.T, cfg *envconf.Config, StopWorkspaceFunc integration.StopWorkspaceFunc) error {
424+
sctx, scancel := context.WithTimeout(context.Background(), 5*time.Minute)
425+
defer scancel()
426+
427+
sapi := integration.NewComponentAPI(sctx, cfg.Namespace(), kubeconfig, cfg.Client())
428+
defer sapi.Done(t)
429+
430+
_, err := StopWorkspaceFunc(true, sapi)
431+
return err
432+
}

0 commit comments

Comments
 (0)