mirror of
https://github.com/gitpod-io/gitpod.git
synced 2025-12-08 17:36:30 +00:00
505 lines
12 KiB
Go
505 lines
12 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 manager
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
ctesting "github.com/gitpod-io/gitpod/common-go/testing"
|
|
"github.com/gitpod-io/gitpod/ws-manager/api"
|
|
"github.com/gitpod-io/gitpod/ws-manager/pkg/manager/internal/grpcpool"
|
|
"google.golang.org/grpc"
|
|
corev1 "k8s.io/api/core/v1"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/labels"
|
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
|
"sigs.k8s.io/controller-runtime/pkg/client/fake"
|
|
)
|
|
|
|
func TestValidateStartWorkspaceRequest(t *testing.T) {
|
|
type fixture struct {
|
|
Req *api.StartWorkspaceRequest `json:"request"`
|
|
BloatEnv bool `json:"bloatEnv"`
|
|
}
|
|
type gold struct {
|
|
Error string `json:"error,omitempty"`
|
|
}
|
|
|
|
test := ctesting.FixtureTest{
|
|
T: t,
|
|
Path: "testdata/validateStartReq_*.json",
|
|
Fixture: func() interface{} { return &fixture{} },
|
|
Gold: func() interface{} { return &gold{} },
|
|
Test: func(t *testing.T, input interface{}) interface{} {
|
|
fixture := input.(*fixture)
|
|
if fixture.Req == nil {
|
|
t.Errorf("request is nil")
|
|
return nil
|
|
}
|
|
|
|
if fixture.BloatEnv {
|
|
fixture.Req.Spec.Envvars = append(fixture.Req.Spec.Envvars, &api.EnvironmentVariable{
|
|
Name: "BLOAT",
|
|
Value: string(make([]byte, 2*maxSecretsLength)),
|
|
})
|
|
}
|
|
|
|
err := validateStartWorkspaceRequest(fixture.Req)
|
|
if err != nil {
|
|
return &gold{Error: err.Error()}
|
|
}
|
|
|
|
return &gold{}
|
|
},
|
|
}
|
|
test.Run()
|
|
}
|
|
|
|
func TestControlPort(t *testing.T) {
|
|
type fixture struct {
|
|
PortsService *corev1.Service `json:"portsService,omitempty"`
|
|
Request api.ControlPortRequest `json:"request"`
|
|
}
|
|
type gold struct {
|
|
Error string `json:"error,omitempty"`
|
|
Response *api.ControlPortResponse `json:"response,omitempty"`
|
|
PostChangeStatus []*api.PortSpec `json:"postChangeStatus,omitempty"`
|
|
}
|
|
|
|
test := ctesting.FixtureTest{
|
|
T: t,
|
|
Path: "testdata/controlPort_*.json",
|
|
Test: func(t *testing.T, input interface{}) interface{} {
|
|
fixture := input.(*fixture)
|
|
|
|
manager := forTestingOnlyGetManager(t)
|
|
|
|
startCtx, err := forTestingOnlyCreateStartWorkspaceContext(manager, fixture.Request.Id, api.WorkspaceType_REGULAR)
|
|
if err != nil {
|
|
t.Errorf("cannot create test pod start context; this is a bug in the unit test itself: %v", err)
|
|
return nil
|
|
}
|
|
|
|
pod, err := manager.createDefiniteWorkspacePod(startCtx)
|
|
if err != nil {
|
|
t.Fatalf("cannot create test pod; this is a bug in the unit test itself: %v", err)
|
|
return nil
|
|
}
|
|
|
|
manager.Clientset.Create(context.Background(), pod)
|
|
if fixture.PortsService != nil {
|
|
err := manager.Clientset.Create(context.Background(), fixture.PortsService)
|
|
if err != nil {
|
|
t.Fatalf("cannot create test service; this is a bug in the unit test itself: %v", err)
|
|
return nil
|
|
}
|
|
}
|
|
|
|
var result gold
|
|
manager.OnChange = func(ctx context.Context, status *api.WorkspaceStatus) {
|
|
result.PostChangeStatus = status.Spec.ExposedPorts
|
|
}
|
|
|
|
resp, err := manager.ControlPort(context.Background(), &fixture.Request)
|
|
if err != nil {
|
|
result.Error = err.Error()
|
|
return &result
|
|
}
|
|
|
|
result.Response = resp
|
|
|
|
// wait for informer sync of any change introduced by ControlPort
|
|
time.Sleep(500 * time.Millisecond)
|
|
|
|
return &result
|
|
},
|
|
Fixture: func() interface{} { return &fixture{} },
|
|
Gold: func() interface{} { return &gold{} },
|
|
}
|
|
test.Run()
|
|
}
|
|
|
|
func TestGetWorkspaces(t *testing.T) {
|
|
t.Skipf("skipping flaky getWorkspaces_podOnly test")
|
|
|
|
type fixture struct {
|
|
Pods []*corev1.Pod `json:"pods"`
|
|
}
|
|
type gold struct {
|
|
Status []*api.WorkspaceStatus `json:"result"`
|
|
Error error `json:"error,omitempty"`
|
|
}
|
|
|
|
test := ctesting.FixtureTest{
|
|
T: t,
|
|
Path: "testdata/getWorkspaces_*.json",
|
|
Test: func(t *testing.T, input interface{}) interface{} {
|
|
fixture := input.(*fixture)
|
|
|
|
manager := forTestingOnlyGetManager(t)
|
|
|
|
for _, o := range fixture.Pods {
|
|
err := manager.Clientset.Create(context.Background(), o)
|
|
if err != nil {
|
|
t.Errorf("cannot create test pod start context; this is a bug in the unit test itself: %v", err)
|
|
return nil
|
|
}
|
|
}
|
|
|
|
time.Sleep(1 * time.Second)
|
|
|
|
cleanTemporalAttributes := func(workspaceStatus []*api.WorkspaceStatus) []*api.WorkspaceStatus {
|
|
if workspaceStatus == nil {
|
|
return nil
|
|
}
|
|
|
|
newStatus := []*api.WorkspaceStatus{}
|
|
for _, status := range workspaceStatus {
|
|
// skip status of pending pods
|
|
if status.Message == "pod is pending" {
|
|
continue
|
|
}
|
|
|
|
status.Metadata.StartedAt = nil
|
|
newStatus = append(newStatus, status)
|
|
}
|
|
|
|
return newStatus
|
|
}
|
|
|
|
var result gold
|
|
resp, err := manager.GetWorkspaces(context.Background(), &api.GetWorkspacesRequest{})
|
|
result.Error = err
|
|
if resp != nil {
|
|
result.Status = cleanTemporalAttributes(resp.Status)
|
|
}
|
|
|
|
return &result
|
|
},
|
|
Fixture: func() interface{} { return &fixture{} },
|
|
Gold: func() interface{} { return &gold{} },
|
|
}
|
|
test.Run()
|
|
}
|
|
|
|
func TestFindWorkspacePod(t *testing.T) {
|
|
type tpd struct {
|
|
WorkspaceID string
|
|
Type api.WorkspaceType
|
|
}
|
|
tests := []struct {
|
|
Description string
|
|
State []tpd
|
|
WorkspaceID string
|
|
ExpectedPodName string
|
|
ExpectedErr string
|
|
}{
|
|
{
|
|
"single prebuild",
|
|
[]tpd{
|
|
{"foobar", api.WorkspaceType_PREBUILD},
|
|
},
|
|
"foobar",
|
|
"prebuild-foobar",
|
|
"",
|
|
},
|
|
{
|
|
"single workspace",
|
|
[]tpd{
|
|
{"foobar", api.WorkspaceType_REGULAR},
|
|
},
|
|
"foobar",
|
|
"ws-foobar",
|
|
"",
|
|
},
|
|
{
|
|
"duplicate prebuild",
|
|
[]tpd{
|
|
{"foobar", api.WorkspaceType_PREBUILD},
|
|
{"foobar", api.WorkspaceType_REGULAR},
|
|
},
|
|
"foobar",
|
|
"",
|
|
"found 2 candidates for workspace foobar",
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.Description, func(t *testing.T) {
|
|
var objs []client.Object
|
|
manager := forTestingOnlyGetManager(t)
|
|
for _, pd := range test.State {
|
|
startCtx, err := forTestingOnlyCreateStartWorkspaceContext(manager, pd.WorkspaceID, pd.Type)
|
|
if err != nil {
|
|
t.Errorf("cannot create test pod start context; this is a bug in the unit test itself: %v", err)
|
|
return
|
|
}
|
|
|
|
pod, err := manager.createDefiniteWorkspacePod(startCtx)
|
|
if err != nil {
|
|
t.Errorf("cannot create test pod; this is a bug in the unit test itself: %v", err)
|
|
return
|
|
}
|
|
|
|
pod.Namespace = manager.Config.Namespace
|
|
objs = append(objs, pod)
|
|
}
|
|
|
|
for _, obj := range objs {
|
|
manager.Clientset.Create(context.Background(), obj)
|
|
}
|
|
|
|
p, err := manager.findWorkspacePod(context.Background(), test.WorkspaceID)
|
|
|
|
var errmsg string
|
|
if err != nil {
|
|
errmsg = err.Error()
|
|
}
|
|
if test.ExpectedErr != errmsg {
|
|
t.Errorf("unexpected error: \"%s\", expected: \"%s\"", errmsg, test.ExpectedErr)
|
|
}
|
|
|
|
var podname string
|
|
if p != nil {
|
|
podname = p.Name
|
|
}
|
|
if test.ExpectedPodName != podname {
|
|
t.Errorf("unepxected findWorkspacePod result: \"%s\", expected: \"%s\"", podname, test.ExpectedPodName)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestConnectToWorkspaceDaemon(t *testing.T) {
|
|
badNodeName := "not-matching-node"
|
|
goodNodeName := "a-matching-node"
|
|
|
|
type Args struct {
|
|
Ctx context.Context
|
|
WSO workspaceObjects
|
|
Objs []client.Object
|
|
}
|
|
tests := []struct {
|
|
Name string
|
|
Args Args
|
|
WantErr bool
|
|
ExpectedErr string
|
|
}{
|
|
{
|
|
Name: "handles empty wso",
|
|
Args: Args{
|
|
Ctx: context.Background(),
|
|
WSO: workspaceObjects{},
|
|
},
|
|
ExpectedErr: "workspace without a valid node name",
|
|
},
|
|
{
|
|
Name: "handles no endpoints",
|
|
Args: Args{
|
|
Ctx: context.Background(),
|
|
WSO: workspaceObjects{
|
|
Pod: &corev1.Pod{
|
|
Spec: corev1.PodSpec{
|
|
NodeName: "a_node_name",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
ExpectedErr: "no running ws-daemon pod found",
|
|
},
|
|
{
|
|
Name: "handles no endpoint on current node",
|
|
Args: Args{
|
|
Ctx: context.Background(),
|
|
WSO: workspaceObjects{
|
|
Pod: &corev1.Pod{
|
|
Spec: corev1.PodSpec{
|
|
NodeName: "a_node_name",
|
|
},
|
|
},
|
|
},
|
|
Objs: []client.Object{
|
|
&corev1.Pod{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "ws-daemon",
|
|
Namespace: "default",
|
|
Labels: labels.Set{
|
|
"component": "ws-daemon",
|
|
"app": "gitpod",
|
|
},
|
|
},
|
|
Spec: corev1.PodSpec{
|
|
Containers: []corev1.Container{
|
|
{
|
|
Name: "workspace",
|
|
Image: "dummy",
|
|
},
|
|
},
|
|
NodeName: badNodeName,
|
|
},
|
|
Status: corev1.PodStatus{
|
|
Phase: corev1.PodRunning,
|
|
PodIP: "10.1.2.3",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
ExpectedErr: "no running ws-daemon pod found",
|
|
},
|
|
{
|
|
Name: "finds endpoint on current node",
|
|
Args: Args{
|
|
Ctx: context.Background(),
|
|
WSO: workspaceObjects{
|
|
Pod: &corev1.Pod{
|
|
Spec: corev1.PodSpec{
|
|
NodeName: goodNodeName,
|
|
},
|
|
},
|
|
},
|
|
Objs: []client.Object{
|
|
&corev1.Pod{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "ws-daemon-endpoints",
|
|
Namespace: "default",
|
|
Labels: labels.Set{
|
|
"component": "ws-daemon",
|
|
"app": "gitpod",
|
|
},
|
|
},
|
|
Spec: corev1.PodSpec{
|
|
Containers: []corev1.Container{
|
|
{
|
|
Name: "workspace",
|
|
Image: "dummy",
|
|
},
|
|
},
|
|
NodeName: goodNodeName,
|
|
},
|
|
Status: corev1.PodStatus{
|
|
Phase: corev1.PodRunning,
|
|
PodIP: "10.1.2.3",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
manager := forTestingOnlyGetManager(t, tt.Args.Objs...)
|
|
// Add dummy daemon pool - slightly hacky but we aren't testing the actual connectivity here
|
|
manager.wsdaemonPool = grpcpool.New(func(host string) (*grpc.ClientConn, error) {
|
|
return nil, nil
|
|
}, func(checkAddress string) bool { return false })
|
|
|
|
t.Run(tt.Name, func(t *testing.T) {
|
|
got, err := manager.connectToWorkspaceDaemon(tt.Args.Ctx, tt.Args.WSO)
|
|
if (err != nil) && !strings.Contains(err.Error(), tt.ExpectedErr) {
|
|
t.Errorf("Manager.connectToWorkspaceDaemon() error = %v, wantErr %v (%v)", err, tt.WantErr, tt.ExpectedErr)
|
|
return
|
|
}
|
|
if err != nil && got != nil {
|
|
t.Errorf("Manager.connectToWorkspaceDaemon() = %v, wanted nil", got)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCheckWSDaemonEntpoint(t *testing.T) {
|
|
type Args struct {
|
|
Objs []client.Object
|
|
}
|
|
tests := []struct {
|
|
Name string
|
|
Input string
|
|
Args Args
|
|
Expected bool
|
|
}{
|
|
{
|
|
Name: "handles no endpoints",
|
|
Input: "10.1.2.3",
|
|
Args: Args{},
|
|
Expected: false,
|
|
},
|
|
{
|
|
Name: "handles no endpoint on current node",
|
|
Args: Args{
|
|
Objs: []client.Object{
|
|
&corev1.Pod{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "ws-daemon-endpoints",
|
|
Namespace: "default",
|
|
Labels: labels.Set{
|
|
"component": "ws-daemon",
|
|
"app": "gitpod",
|
|
},
|
|
},
|
|
Spec: corev1.PodSpec{
|
|
Containers: []corev1.Container{
|
|
{
|
|
Name: "workspace",
|
|
Image: "dummy",
|
|
},
|
|
},
|
|
NodeName: "nodeName",
|
|
},
|
|
Status: corev1.PodStatus{
|
|
Phase: corev1.PodRunning,
|
|
PodIP: "10.1.2.2",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Expected: false,
|
|
},
|
|
{
|
|
Name: "finds endpoint on current node",
|
|
Input: "10.1.2.3",
|
|
Args: Args{
|
|
Objs: []client.Object{
|
|
&corev1.Pod{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "ws-daemon-endpoints",
|
|
Namespace: "default",
|
|
Labels: labels.Set{
|
|
"component": "ws-daemon",
|
|
"app": "gitpod",
|
|
},
|
|
},
|
|
Spec: corev1.PodSpec{
|
|
Containers: []corev1.Container{
|
|
{
|
|
Name: "workspace",
|
|
Image: "dummy",
|
|
},
|
|
},
|
|
NodeName: "nodeName",
|
|
},
|
|
Status: corev1.PodStatus{
|
|
Phase: corev1.PodRunning,
|
|
PodIP: "10.1.2.3",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Expected: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
clientset := fake.NewClientBuilder().WithObjects(tt.Args.Objs...).Build()
|
|
|
|
t.Run(tt.Name, func(t *testing.T) {
|
|
got := checkWSDaemonEndpoint("default", clientset)(tt.Input)
|
|
if got != tt.Expected {
|
|
t.Errorf("checkWSDaemonEndpoint = %v, wanted %v", got, tt.Expected)
|
|
}
|
|
})
|
|
}
|
|
}
|