mirror of
https://github.com/gitpod-io/gitpod.git
synced 2025-12-08 17:36:30 +00:00
427 lines
13 KiB
Go
427 lines
13 KiB
Go
// Copyright (c) 2020 Gitpod GmbH. All rights reserved.
|
|
// Licensed under the GNU Affero General Public License (AGPL).
|
|
// See License-AGPL.txt in the project root for license information.
|
|
|
|
package wsmanager
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"path/filepath"
|
|
"reflect"
|
|
"testing"
|
|
"time"
|
|
|
|
"sigs.k8s.io/e2e-framework/pkg/envconf"
|
|
"sigs.k8s.io/e2e-framework/pkg/features"
|
|
|
|
csapi "github.com/gitpod-io/gitpod/content-service/api"
|
|
agent "github.com/gitpod-io/gitpod/test/pkg/agent/workspace/api"
|
|
"github.com/gitpod-io/gitpod/test/pkg/integration"
|
|
wsmanapi "github.com/gitpod-io/gitpod/ws-manager/api"
|
|
)
|
|
|
|
// TestBackup tests a basic start/modify/restart cycle
|
|
func TestBackup(t *testing.T) {
|
|
f := features.New("backup").
|
|
WithLabel("component", "ws-manager").
|
|
Assess("it should start a workspace, create a file and successfully create a backup", func(_ context.Context, t *testing.T, cfg *envconf.Config) context.Context {
|
|
tests := []struct {
|
|
Name string
|
|
ContextURL string
|
|
WorkspaceRoot string
|
|
CheckoutLocation string
|
|
FF []wsmanapi.WorkspaceFeatureFlag
|
|
}{
|
|
{
|
|
Name: "classic",
|
|
ContextURL: "https://github.com/gitpod-io/empty",
|
|
WorkspaceRoot: "/workspace/empty",
|
|
CheckoutLocation: "empty",
|
|
},
|
|
{
|
|
Name: "pvc",
|
|
ContextURL: "https://github.com/gitpod-io/empty",
|
|
WorkspaceRoot: "/workspace/empty",
|
|
CheckoutLocation: "empty",
|
|
FF: []wsmanapi.WorkspaceFeatureFlag{wsmanapi.WorkspaceFeatureFlag_PERSISTENT_VOLUME_CLAIM},
|
|
},
|
|
{
|
|
Name: "pvc-non-gitpodified",
|
|
ContextURL: "https://github.com/gitpod-io/non-gitpodified-repo",
|
|
WorkspaceRoot: "/workspace/non-gitpodified-repo",
|
|
CheckoutLocation: "non-gitpodified-repo",
|
|
FF: []wsmanapi.WorkspaceFeatureFlag{wsmanapi.WorkspaceFeatureFlag_PERSISTENT_VOLUME_CLAIM},
|
|
},
|
|
}
|
|
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Minute)
|
|
defer cancel()
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.Name, func(t *testing.T) {
|
|
api := integration.NewComponentAPI(ctx, cfg.Namespace(), kubeconfig, cfg.Client())
|
|
t.Cleanup(func() {
|
|
api.Done(t)
|
|
})
|
|
|
|
// TODO: change to use server API to launch the workspace, so we could run the integration test as the user code flow
|
|
// which is client -> server -> ws-manager rather than client -> ws-manager directly
|
|
ws1, stopWs1, err := integration.LaunchWorkspaceDirectly(t, ctx, api, integration.WithRequestModifier(func(w *wsmanapi.StartWorkspaceRequest) error {
|
|
w.Spec.FeatureFlags = test.FF
|
|
w.Spec.Initializer = &csapi.WorkspaceInitializer{
|
|
Spec: &csapi.WorkspaceInitializer_Git{
|
|
Git: &csapi.GitInitializer{
|
|
RemoteUri: test.ContextURL,
|
|
CheckoutLocation: test.CheckoutLocation,
|
|
Config: &csapi.GitConfig{},
|
|
},
|
|
},
|
|
}
|
|
w.Spec.WorkspaceLocation = test.CheckoutLocation
|
|
return nil
|
|
}))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
rsa, closer, err := integration.Instrument(integration.ComponentWorkspace, "workspace", cfg.Namespace(), kubeconfig, cfg.Client(),
|
|
integration.WithInstanceID(ws1.Req.Id),
|
|
integration.WithContainer("workspace"),
|
|
integration.WithWorkspacekitLift(true),
|
|
)
|
|
if err != nil {
|
|
if _, err := stopWs1(true, api); err != nil {
|
|
t.Errorf("cannot stop workspace: %q", err)
|
|
}
|
|
t.Fatal(err)
|
|
}
|
|
integration.DeferCloser(t, closer)
|
|
|
|
var resp agent.WriteFileResponse
|
|
err = rsa.Call("WorkspaceAgent.WriteFile", &agent.WriteFileRequest{
|
|
Path: fmt.Sprintf("%s/foobar.txt", test.WorkspaceRoot),
|
|
Content: []byte("hello world"),
|
|
Mode: 0644,
|
|
}, &resp)
|
|
rsa.Close()
|
|
if err != nil {
|
|
if _, err = stopWs1(true, api); err != nil {
|
|
t.Errorf("cannot stop workspace: %q", err)
|
|
}
|
|
t.Fatal(err)
|
|
}
|
|
|
|
lastStatusWs1, err := stopWs1(true, api)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
ws2, stopWs2, err := integration.LaunchWorkspaceDirectly(t, ctx, api,
|
|
integration.WithRequestModifier(func(w *wsmanapi.StartWorkspaceRequest) error {
|
|
w.ServicePrefix = ws1.Req.ServicePrefix
|
|
w.Metadata.MetaId = ws1.Req.Metadata.MetaId
|
|
w.Metadata.Owner = ws1.Req.Metadata.Owner
|
|
w.Spec.FeatureFlags = ws1.Req.Spec.FeatureFlags
|
|
w.Spec.Initializer = ws1.Req.Spec.Initializer
|
|
w.Spec.WorkspaceLocation = ws1.Req.Spec.WorkspaceLocation
|
|
|
|
if !reflect.DeepEqual(w.Spec.FeatureFlags, []wsmanapi.WorkspaceFeatureFlag{wsmanapi.WorkspaceFeatureFlag_PERSISTENT_VOLUME_CLAIM}) {
|
|
return nil
|
|
}
|
|
|
|
if lastStatusWs1 != nil && lastStatusWs1.Conditions != nil && lastStatusWs1.Conditions.VolumeSnapshot != nil {
|
|
w.Spec.VolumeSnapshot = lastStatusWs1.Conditions.VolumeSnapshot
|
|
}
|
|
return nil
|
|
}),
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer func() {
|
|
sctx, scancel := context.WithTimeout(context.Background(), 5*time.Minute)
|
|
defer scancel()
|
|
|
|
sapi := integration.NewComponentAPI(sctx, cfg.Namespace(), kubeconfig, cfg.Client())
|
|
defer sapi.Done(t)
|
|
|
|
_, err = stopWs2(true, sapi)
|
|
if err != nil {
|
|
t.Errorf("cannot stop workspace: %q", err)
|
|
}
|
|
}()
|
|
|
|
rsa, closer, err = integration.Instrument(integration.ComponentWorkspace, "workspace", cfg.Namespace(), kubeconfig, cfg.Client(),
|
|
integration.WithInstanceID(ws2.Req.Id),
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
integration.DeferCloser(t, closer)
|
|
|
|
var ls agent.ListDirResponse
|
|
err = rsa.Call("WorkspaceAgent.ListDir", &agent.ListDirRequest{
|
|
Dir: test.WorkspaceRoot,
|
|
}, &ls)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
rsa.Close()
|
|
|
|
var found bool
|
|
for _, f := range ls.Files {
|
|
if filepath.Base(f) == "foobar.txt" {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
t.Fatal("did not find foobar.txt from previous workspace instance")
|
|
}
|
|
})
|
|
}
|
|
return ctx
|
|
}).
|
|
Feature()
|
|
|
|
testEnv.Test(t, f)
|
|
|
|
}
|
|
|
|
// TestExistingWorkspaceEnablePVC tests enable PVC feature flag on the existing workspace
|
|
func TestExistingWorkspaceEnablePVC(t *testing.T) {
|
|
f := features.New("backup").
|
|
WithLabel("component", "ws-manager").
|
|
Assess("it should enable PVC feature flag to the existing workspace without data loss", func(_ context.Context, t *testing.T, cfg *envconf.Config) context.Context {
|
|
tests := []struct {
|
|
Name string
|
|
ContextURL string
|
|
WorkspaceRoot string
|
|
CheckoutLocation string
|
|
}{
|
|
{
|
|
Name: "pvc",
|
|
ContextURL: "http://github.com/gitpod-io/template-golang-cli",
|
|
WorkspaceRoot: "/workspace/template-golang-cli",
|
|
CheckoutLocation: "template-golang-cli",
|
|
},
|
|
}
|
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
|
|
defer cancel()
|
|
|
|
for _, test := range tests {
|
|
api := integration.NewComponentAPI(ctx, cfg.Namespace(), kubeconfig, cfg.Client())
|
|
t.Cleanup(func() {
|
|
api.Done(t)
|
|
})
|
|
|
|
// Create a new workspace without the PVC feature flag
|
|
// TODO: change to use server API to launch the workspace, so we could run the integration test as the user code flow
|
|
// which is client -> server -> ws-manager rather than client -> ws-manager directly
|
|
ws1, stopWs1, err := integration.LaunchWorkspaceDirectly(t, ctx, api, integration.WithRequestModifier(func(w *wsmanapi.StartWorkspaceRequest) error {
|
|
w.Spec.Initializer = &csapi.WorkspaceInitializer{
|
|
Spec: &csapi.WorkspaceInitializer_Git{
|
|
Git: &csapi.GitInitializer{
|
|
RemoteUri: test.ContextURL,
|
|
CheckoutLocation: test.CheckoutLocation,
|
|
Config: &csapi.GitConfig{},
|
|
},
|
|
},
|
|
}
|
|
w.Spec.WorkspaceLocation = test.CheckoutLocation
|
|
return nil
|
|
}))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
rsa, closer, err := integration.Instrument(integration.ComponentWorkspace, "workspace", cfg.Namespace(), kubeconfig, cfg.Client(),
|
|
integration.WithInstanceID(ws1.Req.Id),
|
|
integration.WithContainer("workspace"),
|
|
integration.WithWorkspacekitLift(true),
|
|
)
|
|
if err != nil {
|
|
if _, err := stopWs1(true, api); err != nil {
|
|
t.Errorf("cannot stop workspace: %q", err)
|
|
}
|
|
t.Fatal(err)
|
|
}
|
|
integration.DeferCloser(t, closer)
|
|
|
|
var resp agent.WriteFileResponse
|
|
err = rsa.Call("WorkspaceAgent.WriteFile", &agent.WriteFileRequest{
|
|
Path: fmt.Sprintf("%s/foobar.txt", test.WorkspaceRoot),
|
|
Content: []byte("hello world"),
|
|
Mode: 0644,
|
|
}, &resp)
|
|
rsa.Close()
|
|
if err != nil {
|
|
if _, err := stopWs1(true, api); err != nil {
|
|
t.Errorf("cannot stop workspace: %q", err)
|
|
}
|
|
t.Fatal(err)
|
|
}
|
|
|
|
_, err = stopWs1(true, api)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Relaunch the workspace and enable the PVC feature flag
|
|
ws2, stopWs2, err := integration.LaunchWorkspaceDirectly(t, ctx, api,
|
|
integration.WithRequestModifier(func(w *wsmanapi.StartWorkspaceRequest) error {
|
|
w.ServicePrefix = ws1.Req.ServicePrefix
|
|
w.Metadata.MetaId = ws1.Req.Metadata.MetaId
|
|
w.Metadata.Owner = ws1.Req.Metadata.Owner
|
|
w.Spec.FeatureFlags = []wsmanapi.WorkspaceFeatureFlag{wsmanapi.WorkspaceFeatureFlag_PERSISTENT_VOLUME_CLAIM}
|
|
w.Spec.Initializer = ws1.Req.Spec.Initializer
|
|
w.Spec.WorkspaceLocation = ws1.Req.Spec.WorkspaceLocation
|
|
return nil
|
|
}),
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(func() {
|
|
sctx, scancel := context.WithTimeout(context.Background(), 5*time.Minute)
|
|
defer scancel()
|
|
|
|
sapi := integration.NewComponentAPI(sctx, cfg.Namespace(), kubeconfig, cfg.Client())
|
|
defer sapi.Done(t)
|
|
_, err = stopWs2(true, sapi)
|
|
if err != nil {
|
|
t.Errorf("cannot stop workspace: %q", err)
|
|
}
|
|
})
|
|
|
|
rsa, closer, err = integration.Instrument(integration.ComponentWorkspace, "workspace", cfg.Namespace(), kubeconfig, cfg.Client(),
|
|
integration.WithInstanceID(ws2.Req.Id),
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
integration.DeferCloser(t, closer)
|
|
|
|
var ls agent.ListDirResponse
|
|
err = rsa.Call("WorkspaceAgent.ListDir", &agent.ListDirRequest{
|
|
Dir: test.WorkspaceRoot,
|
|
}, &ls)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
rsa.Close()
|
|
|
|
var found bool
|
|
for _, f := range ls.Files {
|
|
if filepath.Base(f) == "foobar.txt" {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
t.Fatal("did not find foobar.txt from previous workspace instance")
|
|
}
|
|
}
|
|
return ctx
|
|
}).
|
|
Feature()
|
|
|
|
testEnv.Test(t, f)
|
|
}
|
|
|
|
// TestMissingBackup ensures workspaces fail if they should have a backup but don't have one
|
|
func TestMissingBackup(t *testing.T) {
|
|
f := features.New("CreateWorkspace").
|
|
WithLabel("component", "ws-manager").
|
|
Assess("it ensures workspace fail if they should have a backup but don't have one", func(_ context.Context, t *testing.T, cfg *envconf.Config) context.Context {
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
|
|
defer cancel()
|
|
|
|
api := integration.NewComponentAPI(ctx, cfg.Namespace(), kubeconfig, cfg.Client())
|
|
t.Cleanup(func() {
|
|
api.Done(t)
|
|
})
|
|
|
|
ws, stopWs, err := integration.LaunchWorkspaceDirectly(t, ctx, api)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
wsm, err := api.WorkspaceManager()
|
|
if err != nil {
|
|
if _, err := stopWs(true, api); err != nil {
|
|
t.Errorf("cannot stop workspace: %q", err)
|
|
}
|
|
t.Fatal(err)
|
|
}
|
|
|
|
_, err = wsm.StopWorkspace(ctx, &wsmanapi.StopWorkspaceRequest{Id: ws.Req.Id})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
_, err = integration.WaitForWorkspaceStop(ctx, api, ws.Req.Id)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
contentSvc, err := api.ContentService()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
_, err = contentSvc.DeleteWorkspace(ctx, &csapi.DeleteWorkspaceRequest{
|
|
OwnerId: ws.Req.Metadata.Owner,
|
|
WorkspaceId: ws.Req.Metadata.MetaId,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
tests := []struct {
|
|
Name string
|
|
FF []wsmanapi.WorkspaceFeatureFlag
|
|
}{
|
|
{Name: "classic"},
|
|
{Name: "fwb", FF: []wsmanapi.WorkspaceFeatureFlag{wsmanapi.WorkspaceFeatureFlag_FULL_WORKSPACE_BACKUP}},
|
|
{Name: "pvc", FF: []wsmanapi.WorkspaceFeatureFlag{wsmanapi.WorkspaceFeatureFlag_PERSISTENT_VOLUME_CLAIM}},
|
|
}
|
|
for _, test := range tests {
|
|
t.Run(test.Name+"_backup_init", func(t *testing.T) {
|
|
testws, stopWs, err := integration.LaunchWorkspaceDirectly(t, ctx, api, integration.WithRequestModifier(func(w *wsmanapi.StartWorkspaceRequest) error {
|
|
w.ServicePrefix = ws.Req.ServicePrefix
|
|
w.Metadata.MetaId = ws.Req.Metadata.MetaId
|
|
w.Metadata.Owner = ws.Req.Metadata.Owner
|
|
w.Spec.Initializer = &csapi.WorkspaceInitializer{
|
|
Spec: &csapi.WorkspaceInitializer_Backup{
|
|
Backup: &csapi.FromBackupInitializer{},
|
|
},
|
|
}
|
|
w.Spec.FeatureFlags = test.FF
|
|
return nil
|
|
}), integration.WithWaitWorkspaceForOpts(integration.WorkspaceCanFail))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if testws.LastStatus == nil {
|
|
t.Fatal("did not receive a last status")
|
|
return
|
|
}
|
|
if testws.LastStatus.Conditions.Failed == "" {
|
|
_, err = stopWs(true, api)
|
|
if err != nil {
|
|
t.Errorf("cannot stop workspace: %q", err)
|
|
}
|
|
t.Errorf("restarted workspace did not fail despite missing backup, %v", testws)
|
|
}
|
|
})
|
|
}
|
|
return ctx
|
|
}).
|
|
Feature()
|
|
|
|
testEnv.Test(t, f)
|
|
}
|