// 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 manager import ( "encoding/json" "os" "path/filepath" "strings" "testing" "time" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ctesting "github.com/gitpod-io/gitpod/common-go/testing" "github.com/gitpod-io/gitpod/common-go/util" "github.com/gitpod-io/gitpod/ws-manager/api" config "github.com/gitpod-io/gitpod/ws-manager/api/config" "github.com/gitpod-io/gitpod/ws-manager/pkg/clock" "github.com/google/go-cmp/cmp" ) func TestIsWorkspaceTimedout(t *testing.T) { type fixture struct { Activity string `json:"activity,omitempty"` WSO workspaceObjects `json:"wso"` CreationDelta string `json:"creationDelta,omitempty"` StoppingSinceDelta string `json:"stoppingSinceDelta,omitempty"` } type gold struct { Reason string `json:"reason,omitempty"` Error string `json:"error,omitempty"` } test := ctesting.FixtureTest{ T: t, Path: "testdata/timeout*.json", Test: func(t *testing.T, input interface{}) interface{} { fixture := input.(*fixture) manager := Manager{ clock: clock.LogicalOnly(), Config: config.Configuration{ Timeouts: config.WorkspaceTimeoutConfiguration{ AfterClose: util.Duration(1 * time.Minute), Initialization: util.Duration(30 * time.Minute), TotalStartup: util.Duration(45 * time.Minute), RegularWorkspace: util.Duration(60 * time.Minute), MaxLifetime: util.Duration(36 * time.Hour), HeadlessWorkspace: util.Duration(90 * time.Minute), Stopping: util.Duration(60 * time.Minute), ContentFinalization: util.Duration(55 * time.Minute), Interrupted: util.Duration(5 * time.Minute), }, }, } if fixture.Activity != "" { dt, err := time.ParseDuration(fixture.Activity) if err != nil { t.Errorf("cannot parse fixture's Activity: %v", err) return nil } workspaceID, ok := fixture.WSO.WorkspaceID() if !ok { t.Errorf("fixture pod has no %s annotation", workspaceIDAnnotation) return nil } delta := time.Now().Add(-dt) manager.activity.Store(workspaceID, &delta) } if fixture.CreationDelta != "" && fixture.WSO.Pod != nil { dt, err := time.ParseDuration(fixture.CreationDelta) if err != nil { t.Errorf("cannot parse fixture's CreationDelta: %v", err) return nil } fixture.WSO.Pod.ObjectMeta.CreationTimestamp = metav1.Time{Time: time.Now().Add(-dt)} } reason, serr := manager.isWorkspaceTimedOut(fixture.WSO) result := gold{Reason: reason} if serr != nil { result.Error = serr.Error() } return &result }, Fixture: func() interface{} { return &fixture{} }, Gold: func() interface{} { return &gold{} }, } test.Run() } func TestGetWorkspaceStatusWithFixtures(t *testing.T) { type statusTestResult struct { Status *api.WorkspaceStatus `json:"status"` Error string `json:"error,omitempty"` } test := ctesting.FixtureTest{ T: t, Path: "testdata/status_*.json", Test: func(t *testing.T, input interface{}) interface{} { fixture := input.(*workspaceObjects) manager := Manager{ clock: clock.LogicalOnly(), } status, serr := manager.getWorkspaceStatus(*fixture) result := statusTestResult{Status: status} if serr != nil { result.Error = serr.Error() } return &result }, Fixture: func() interface{} { return &workspaceObjects{} }, Gold: func() interface{} { return &statusTestResult{} }, } test.Run() } func BenchmarkGetStatus(b *testing.B) { fs, err := filepath.Glob("testdata/status_*.json") if err != nil { b.Fatal(err) } for _, f := range fs { if strings.Contains(f, "degenerate") { continue } b.Run(f, func(b *testing.B) { b.ReportAllocs() buf, err := os.ReadFile(f) if err != nil { b.Fatal(err) } var wso workspaceObjects err = json.Unmarshal(buf, &wso) if err != nil { b.Fatal(err) } manager := Manager{} b.ResetTimer() for n := 0; n < b.N; n++ { _, err := manager.getWorkspaceStatus(wso) if err != nil { b.Fatal(err) } } }) } } func TestGetNodeName(t *testing.T) { tests := []struct { Name string WSO workspaceObjects Expectation string }{ { Name: "no nodeName", }, { Name: "spec", WSO: workspaceObjects{ Pod: &v1.Pod{ Spec: v1.PodSpec{ NodeName: "foobar", }, }, }, Expectation: "foobar", }, { Name: "annotation", WSO: workspaceObjects{ Pod: &v1.Pod{ ObjectMeta: metav1.ObjectMeta{ Annotations: map[string]string{ nodeNameAnnotation: "from-anno", }, }, }, }, Expectation: "from-anno", }, { Name: "spec and annotation", WSO: workspaceObjects{ Pod: &v1.Pod{ ObjectMeta: metav1.ObjectMeta{ Annotations: map[string]string{ nodeNameAnnotation: "from-anno", }, }, Spec: v1.PodSpec{ NodeName: "from-spec", }, }, }, Expectation: "from-spec", }, } for _, test := range tests { t.Run(test.Name, func(t *testing.T) { act := test.WSO.NodeName() if diff := cmp.Diff(test.Expectation, act); diff != "" { t.Errorf("unexpected nodeName (-want +got):\n%s", diff) } }) } }