2022-04-11 21:06:15 +00:00
|
|
|
package coderd_test
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2023-06-21 22:02:05 +00:00
|
|
|
"encoding/json"
|
2022-10-11 15:10:02 +00:00
|
|
|
"fmt"
|
2022-10-06 12:38:22 +00:00
|
|
|
"net"
|
2022-10-25 00:46:24 +00:00
|
|
|
"net/http"
|
2022-05-02 15:27:34 +00:00
|
|
|
"runtime"
|
2022-10-06 12:38:22 +00:00
|
|
|
"strconv"
|
2022-04-29 22:30:10 +00:00
|
|
|
"strings"
|
2023-07-26 16:21:04 +00:00
|
|
|
"sync/atomic"
|
2022-04-11 21:06:15 +00:00
|
|
|
"testing"
|
2022-05-18 17:06:17 +00:00
|
|
|
"time"
|
2022-04-11 21:06:15 +00:00
|
|
|
|
|
|
|
"github.com/google/uuid"
|
2022-10-25 00:46:24 +00:00
|
|
|
"github.com/stretchr/testify/assert"
|
2022-04-11 21:06:15 +00:00
|
|
|
"github.com/stretchr/testify/require"
|
2023-07-26 16:21:04 +00:00
|
|
|
"tailscale.com/tailcfg"
|
2022-04-11 21:06:15 +00:00
|
|
|
|
|
|
|
"cdr.dev/slog"
|
|
|
|
"cdr.dev/slog/sloggers/slogtest"
|
2023-08-18 18:55:43 +00:00
|
|
|
"github.com/coder/coder/v2/agent"
|
|
|
|
"github.com/coder/coder/v2/coderd/coderdtest"
|
|
|
|
"github.com/coder/coder/v2/coderd/database"
|
2023-09-01 16:50:12 +00:00
|
|
|
"github.com/coder/coder/v2/coderd/database/dbtime"
|
2023-08-18 18:55:43 +00:00
|
|
|
"github.com/coder/coder/v2/codersdk"
|
|
|
|
"github.com/coder/coder/v2/codersdk/agentsdk"
|
|
|
|
"github.com/coder/coder/v2/provisioner/echo"
|
|
|
|
"github.com/coder/coder/v2/provisionersdk/proto"
|
|
|
|
"github.com/coder/coder/v2/tailnet/tailnettest"
|
|
|
|
"github.com/coder/coder/v2/testutil"
|
2022-04-11 21:06:15 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
func TestWorkspaceAgent(t *testing.T) {
|
|
|
|
t.Parallel()
|
2022-05-02 15:27:34 +00:00
|
|
|
t.Run("Connect", func(t *testing.T) {
|
|
|
|
t.Parallel()
|
2022-06-27 18:50:52 +00:00
|
|
|
client := coderdtest.New(t, &coderdtest.Options{
|
2022-09-04 16:28:09 +00:00
|
|
|
IncludeProvisionerDaemon: true,
|
2022-06-27 18:50:52 +00:00
|
|
|
})
|
2022-05-02 15:27:34 +00:00
|
|
|
user := coderdtest.CreateFirstUser(t, client)
|
|
|
|
authToken := uuid.NewString()
|
2022-07-25 13:55:06 +00:00
|
|
|
tmpDir := t.TempDir()
|
2022-05-02 15:27:34 +00:00
|
|
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
2022-11-11 22:45:58 +00:00
|
|
|
Parse: echo.ParseComplete,
|
2023-08-25 06:10:15 +00:00
|
|
|
ProvisionPlan: echo.PlanComplete,
|
|
|
|
ProvisionApply: []*proto.Response{{
|
|
|
|
Type: &proto.Response_Apply{
|
|
|
|
Apply: &proto.ApplyComplete{
|
2022-05-02 15:27:34 +00:00
|
|
|
Resources: []*proto.Resource{{
|
|
|
|
Name: "example",
|
|
|
|
Type: "aws_instance",
|
|
|
|
Agents: []*proto.Agent{{
|
|
|
|
Id: uuid.NewString(),
|
2022-07-25 13:55:06 +00:00
|
|
|
Directory: tmpDir,
|
2022-05-02 15:27:34 +00:00
|
|
|
Auth: &proto.Agent_Token{
|
|
|
|
Token: authToken,
|
|
|
|
},
|
|
|
|
}},
|
2022-04-11 21:06:15 +00:00
|
|
|
}},
|
2022-05-02 15:27:34 +00:00
|
|
|
},
|
2022-04-11 21:06:15 +00:00
|
|
|
},
|
2022-05-02 15:27:34 +00:00
|
|
|
}},
|
|
|
|
})
|
|
|
|
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
|
|
|
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
|
|
|
|
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
|
|
|
|
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
|
2022-04-11 21:06:15 +00:00
|
|
|
|
2022-08-09 17:17:00 +00:00
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
|
|
|
defer cancel()
|
|
|
|
|
2022-10-03 21:01:13 +00:00
|
|
|
workspace, err := client.Workspace(ctx, workspace.ID)
|
2022-05-02 15:27:34 +00:00
|
|
|
require.NoError(t, err)
|
2022-10-03 21:01:13 +00:00
|
|
|
require.Equal(t, tmpDir, workspace.LatestBuild.Resources[0].Agents[0].Directory)
|
|
|
|
_, err = client.WorkspaceAgent(ctx, workspace.LatestBuild.Resources[0].Agents[0].ID)
|
2022-05-02 15:27:34 +00:00
|
|
|
require.NoError(t, err)
|
2023-07-10 09:40:11 +00:00
|
|
|
require.True(t, workspace.LatestBuild.Resources[0].Agents[0].Health.Healthy)
|
2022-05-02 15:27:34 +00:00
|
|
|
})
|
2022-11-16 10:53:02 +00:00
|
|
|
t.Run("HasFallbackTroubleshootingURL", func(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
client := coderdtest.New(t, &coderdtest.Options{
|
|
|
|
IncludeProvisionerDaemon: true,
|
|
|
|
})
|
|
|
|
user := coderdtest.CreateFirstUser(t, client)
|
|
|
|
authToken := uuid.NewString()
|
|
|
|
tmpDir := t.TempDir()
|
|
|
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
|
|
|
Parse: echo.ParseComplete,
|
2023-08-25 06:10:15 +00:00
|
|
|
ProvisionPlan: echo.PlanComplete,
|
|
|
|
ProvisionApply: []*proto.Response{{
|
|
|
|
Type: &proto.Response_Apply{
|
|
|
|
Apply: &proto.ApplyComplete{
|
2022-11-16 10:53:02 +00:00
|
|
|
Resources: []*proto.Resource{{
|
|
|
|
Name: "example",
|
|
|
|
Type: "aws_instance",
|
|
|
|
Agents: []*proto.Agent{{
|
|
|
|
Id: uuid.NewString(),
|
|
|
|
Directory: tmpDir,
|
|
|
|
Auth: &proto.Agent_Token{
|
|
|
|
Token: authToken,
|
|
|
|
},
|
|
|
|
}},
|
|
|
|
}},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}},
|
|
|
|
})
|
|
|
|
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
|
|
|
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
|
|
|
|
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
|
|
|
|
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
|
|
|
|
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitMedium)
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
workspace, err := client.Workspace(ctx, workspace.ID)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.NotEmpty(t, workspace.LatestBuild.Resources[0].Agents[0].TroubleshootingURL)
|
|
|
|
t.Log(workspace.LatestBuild.Resources[0].Agents[0].TroubleshootingURL)
|
|
|
|
})
|
2022-11-09 15:27:05 +00:00
|
|
|
t.Run("Timeout", func(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
client := coderdtest.New(t, &coderdtest.Options{
|
|
|
|
IncludeProvisionerDaemon: true,
|
|
|
|
})
|
|
|
|
user := coderdtest.CreateFirstUser(t, client)
|
|
|
|
authToken := uuid.NewString()
|
|
|
|
tmpDir := t.TempDir()
|
2022-11-16 10:53:02 +00:00
|
|
|
|
|
|
|
wantTroubleshootingURL := "https://example.com/troubleshoot"
|
|
|
|
|
2022-11-09 15:27:05 +00:00
|
|
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
2022-11-11 22:45:58 +00:00
|
|
|
Parse: echo.ParseComplete,
|
2023-08-25 06:10:15 +00:00
|
|
|
ProvisionPlan: echo.PlanComplete,
|
|
|
|
ProvisionApply: []*proto.Response{{
|
|
|
|
Type: &proto.Response_Apply{
|
|
|
|
Apply: &proto.ApplyComplete{
|
2022-11-09 15:27:05 +00:00
|
|
|
Resources: []*proto.Resource{{
|
|
|
|
Name: "example",
|
|
|
|
Type: "aws_instance",
|
|
|
|
Agents: []*proto.Agent{{
|
|
|
|
Id: uuid.NewString(),
|
|
|
|
Directory: tmpDir,
|
|
|
|
Auth: &proto.Agent_Token{
|
|
|
|
Token: authToken,
|
|
|
|
},
|
|
|
|
ConnectionTimeoutSeconds: 1,
|
2022-11-16 10:53:02 +00:00
|
|
|
TroubleshootingUrl: wantTroubleshootingURL,
|
2022-11-09 15:27:05 +00:00
|
|
|
}},
|
|
|
|
}},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}},
|
|
|
|
})
|
|
|
|
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
|
|
|
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
|
|
|
|
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
|
|
|
|
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
|
|
|
|
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitMedium)
|
|
|
|
defer cancel()
|
|
|
|
|
2022-11-16 10:53:02 +00:00
|
|
|
var err error
|
2022-11-09 15:27:05 +00:00
|
|
|
testutil.Eventually(ctx, t, func(ctx context.Context) (done bool) {
|
2022-11-16 10:53:02 +00:00
|
|
|
workspace, err = client.Workspace(ctx, workspace.ID)
|
2022-11-09 15:27:05 +00:00
|
|
|
if !assert.NoError(t, err) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
return workspace.LatestBuild.Resources[0].Agents[0].Status == codersdk.WorkspaceAgentTimeout
|
|
|
|
}, testutil.IntervalMedium, "agent status timeout")
|
2022-11-16 10:53:02 +00:00
|
|
|
|
|
|
|
require.Equal(t, wantTroubleshootingURL, workspace.LatestBuild.Resources[0].Agents[0].TroubleshootingURL)
|
2023-07-10 09:40:11 +00:00
|
|
|
require.False(t, workspace.LatestBuild.Resources[0].Agents[0].Health.Healthy)
|
|
|
|
require.NotEmpty(t, workspace.LatestBuild.Resources[0].Agents[0].Health.Reason)
|
2022-11-09 15:27:05 +00:00
|
|
|
})
|
2023-08-30 19:53:42 +00:00
|
|
|
|
|
|
|
t.Run("DisplayApps", func(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
client := coderdtest.New(t, &coderdtest.Options{
|
|
|
|
IncludeProvisionerDaemon: true,
|
|
|
|
})
|
|
|
|
user := coderdtest.CreateFirstUser(t, client)
|
|
|
|
authToken := uuid.NewString()
|
|
|
|
tmpDir := t.TempDir()
|
|
|
|
apps := &proto.DisplayApps{
|
|
|
|
Vscode: true,
|
|
|
|
VscodeInsiders: true,
|
|
|
|
WebTerminal: true,
|
|
|
|
PortForwardingHelper: true,
|
|
|
|
SshHelper: true,
|
|
|
|
}
|
|
|
|
|
|
|
|
echoResp := &echo.Responses{
|
|
|
|
Parse: echo.ParseComplete,
|
|
|
|
ProvisionPlan: echo.PlanComplete,
|
|
|
|
ProvisionApply: []*proto.Response{{
|
|
|
|
Type: &proto.Response_Apply{
|
|
|
|
Apply: &proto.ApplyComplete{
|
|
|
|
Resources: []*proto.Resource{
|
|
|
|
{
|
|
|
|
Name: "example",
|
|
|
|
Type: "aws_instance",
|
|
|
|
Agents: []*proto.Agent{
|
|
|
|
{
|
|
|
|
Id: uuid.NewString(),
|
|
|
|
Directory: tmpDir,
|
|
|
|
Auth: &proto.Agent_Token{
|
|
|
|
Token: authToken,
|
|
|
|
},
|
|
|
|
DisplayApps: apps,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}},
|
|
|
|
}
|
|
|
|
|
|
|
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, echoResp)
|
|
|
|
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
|
|
|
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
|
|
|
|
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
|
|
|
|
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
|
|
|
|
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
workspace, err := client.Workspace(ctx, workspace.ID)
|
|
|
|
require.NoError(t, err)
|
|
|
|
agent, err := client.WorkspaceAgent(ctx, workspace.LatestBuild.Resources[0].Agents[0].ID)
|
|
|
|
require.NoError(t, err)
|
|
|
|
expectedApps := []codersdk.DisplayApp{
|
|
|
|
codersdk.DisplayAppPortForward,
|
|
|
|
codersdk.DisplayAppSSH,
|
|
|
|
codersdk.DisplayAppVSCodeDesktop,
|
|
|
|
codersdk.DisplayAppVSCodeInsiders,
|
|
|
|
codersdk.DisplayAppWebTerminal,
|
|
|
|
}
|
|
|
|
require.ElementsMatch(t, expectedApps, agent.DisplayApps)
|
|
|
|
|
|
|
|
// Flips all the apps to false.
|
|
|
|
apps.PortForwardingHelper = false
|
|
|
|
apps.Vscode = false
|
|
|
|
apps.VscodeInsiders = false
|
|
|
|
apps.SshHelper = false
|
|
|
|
apps.WebTerminal = false
|
|
|
|
|
|
|
|
version = coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, echoResp,
|
|
|
|
func(req *codersdk.CreateTemplateVersionRequest) {
|
|
|
|
req.TemplateID = template.ID
|
|
|
|
})
|
|
|
|
|
|
|
|
err = client.UpdateActiveTemplateVersion(ctx, template.ID, codersdk.UpdateActiveTemplateVersion{
|
|
|
|
ID: version.ID,
|
|
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
|
|
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
|
|
|
|
// Creating another workspace is just easier.
|
|
|
|
workspace = coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
|
|
|
|
build := coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
|
|
|
|
require.NoError(t, err)
|
|
|
|
agent, err = client.WorkspaceAgent(ctx, build.Resources[0].Agents[0].ID)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Len(t, agent.DisplayApps, 0)
|
|
|
|
})
|
2022-04-11 21:06:15 +00:00
|
|
|
}
|
|
|
|
|
2023-03-23 19:09:13 +00:00
|
|
|
func TestWorkspaceAgentStartupLogs(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
t.Run("Success", func(t *testing.T) {
|
|
|
|
t.Parallel()
|
2023-03-23 22:42:20 +00:00
|
|
|
ctx := testutil.Context(t, testutil.WaitMedium)
|
2023-03-23 19:09:13 +00:00
|
|
|
client := coderdtest.New(t, &coderdtest.Options{
|
|
|
|
IncludeProvisionerDaemon: true,
|
|
|
|
})
|
|
|
|
user := coderdtest.CreateFirstUser(t, client)
|
|
|
|
authToken := uuid.NewString()
|
|
|
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
|
|
|
Parse: echo.ParseComplete,
|
2023-08-25 06:10:15 +00:00
|
|
|
ProvisionPlan: echo.PlanComplete,
|
|
|
|
ProvisionApply: []*proto.Response{{
|
|
|
|
Type: &proto.Response_Apply{
|
|
|
|
Apply: &proto.ApplyComplete{
|
2023-03-23 19:09:13 +00:00
|
|
|
Resources: []*proto.Resource{{
|
|
|
|
Name: "example",
|
|
|
|
Type: "aws_instance",
|
|
|
|
Agents: []*proto.Agent{{
|
|
|
|
Id: uuid.NewString(),
|
|
|
|
Auth: &proto.Agent_Token{
|
|
|
|
Token: authToken,
|
|
|
|
},
|
|
|
|
}},
|
|
|
|
}},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}},
|
|
|
|
})
|
|
|
|
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
|
|
|
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
|
|
|
|
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
|
|
|
|
build := coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
|
|
|
|
|
|
|
|
agentClient := agentsdk.New(client.URL)
|
|
|
|
agentClient.SetSessionToken(authToken)
|
2023-07-28 15:57:23 +00:00
|
|
|
err := agentClient.PatchLogs(ctx, agentsdk.PatchLogs{
|
|
|
|
Logs: []agentsdk.Log{
|
2023-06-16 14:14:22 +00:00
|
|
|
{
|
2023-09-01 16:50:12 +00:00
|
|
|
CreatedAt: dbtime.Now(),
|
2023-06-16 14:14:22 +00:00
|
|
|
Output: "testing",
|
|
|
|
},
|
|
|
|
{
|
2023-09-01 16:50:12 +00:00
|
|
|
CreatedAt: dbtime.Now(),
|
2023-06-16 14:14:22 +00:00
|
|
|
Output: "testing2",
|
|
|
|
},
|
|
|
|
},
|
2023-03-23 19:09:13 +00:00
|
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
2023-07-28 15:57:23 +00:00
|
|
|
logs, closer, err := client.WorkspaceAgentLogsAfter(ctx, build.Resources[0].Agents[0].ID, 0, true)
|
2023-03-23 19:09:13 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
defer func() {
|
|
|
|
_ = closer.Close()
|
|
|
|
}()
|
2023-07-28 15:57:23 +00:00
|
|
|
var logChunk []codersdk.WorkspaceAgentLog
|
2023-03-23 19:09:13 +00:00
|
|
|
select {
|
|
|
|
case <-ctx.Done():
|
|
|
|
case logChunk = <-logs:
|
|
|
|
}
|
|
|
|
require.NoError(t, ctx.Err())
|
2023-06-16 14:14:22 +00:00
|
|
|
require.Len(t, logChunk, 2) // No EOF.
|
2023-03-23 19:09:13 +00:00
|
|
|
require.Equal(t, "testing", logChunk[0].Output)
|
2023-06-16 14:14:22 +00:00
|
|
|
require.Equal(t, "testing2", logChunk[1].Output)
|
2023-03-23 19:09:13 +00:00
|
|
|
})
|
2023-08-28 19:46:42 +00:00
|
|
|
t.Run("Close logs on outdated build", func(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
ctx := testutil.Context(t, testutil.WaitMedium)
|
|
|
|
client := coderdtest.New(t, &coderdtest.Options{
|
|
|
|
IncludeProvisionerDaemon: true,
|
|
|
|
})
|
|
|
|
user := coderdtest.CreateFirstUser(t, client)
|
|
|
|
authToken := uuid.NewString()
|
|
|
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
|
|
|
Parse: echo.ParseComplete,
|
|
|
|
ProvisionPlan: echo.PlanComplete,
|
|
|
|
ProvisionApply: []*proto.Response{{
|
|
|
|
Type: &proto.Response_Apply{
|
|
|
|
Apply: &proto.ApplyComplete{
|
|
|
|
Resources: []*proto.Resource{{
|
|
|
|
Name: "example",
|
|
|
|
Type: "aws_instance",
|
|
|
|
Agents: []*proto.Agent{{
|
|
|
|
Id: uuid.NewString(),
|
|
|
|
Auth: &proto.Agent_Token{
|
|
|
|
Token: authToken,
|
|
|
|
},
|
|
|
|
}},
|
|
|
|
}},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}},
|
|
|
|
})
|
|
|
|
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
|
|
|
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
|
|
|
|
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
|
|
|
|
build := coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
|
|
|
|
|
|
|
|
agentClient := agentsdk.New(client.URL)
|
|
|
|
agentClient.SetSessionToken(authToken)
|
|
|
|
err := agentClient.PatchLogs(ctx, agentsdk.PatchLogs{
|
|
|
|
Logs: []agentsdk.Log{
|
|
|
|
{
|
2023-09-01 16:50:12 +00:00
|
|
|
CreatedAt: dbtime.Now(),
|
2023-08-28 19:46:42 +00:00
|
|
|
Output: "testing",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
logs, closer, err := client.WorkspaceAgentLogsAfter(ctx, build.Resources[0].Agents[0].ID, 0, true)
|
|
|
|
require.NoError(t, err)
|
|
|
|
defer func() {
|
|
|
|
_ = closer.Close()
|
|
|
|
}()
|
|
|
|
|
|
|
|
first := make(chan struct{})
|
|
|
|
go func() {
|
|
|
|
select {
|
|
|
|
case <-ctx.Done():
|
|
|
|
assert.Fail(t, "context done while waiting in goroutine")
|
|
|
|
case <-logs:
|
|
|
|
close(first)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
select {
|
|
|
|
case <-ctx.Done():
|
|
|
|
require.FailNow(t, "context done while waiting for first log")
|
|
|
|
case <-first:
|
|
|
|
}
|
|
|
|
|
|
|
|
_ = coderdtest.CreateWorkspaceBuild(t, client, workspace, database.WorkspaceTransitionStart)
|
|
|
|
|
|
|
|
// Send a new log message to trigger a re-check.
|
|
|
|
err = agentClient.PatchLogs(ctx, agentsdk.PatchLogs{
|
|
|
|
Logs: []agentsdk.Log{
|
|
|
|
{
|
2023-09-01 16:50:12 +00:00
|
|
|
CreatedAt: dbtime.Now(),
|
2023-08-28 19:46:42 +00:00
|
|
|
Output: "testing2",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
select {
|
|
|
|
case <-ctx.Done():
|
|
|
|
require.FailNow(t, "context done while waiting for logs close")
|
|
|
|
case <-logs:
|
|
|
|
}
|
|
|
|
})
|
2023-03-23 19:09:13 +00:00
|
|
|
t.Run("PublishesOnOverflow", func(t *testing.T) {
|
|
|
|
t.Parallel()
|
2023-03-23 22:42:20 +00:00
|
|
|
ctx := testutil.Context(t, testutil.WaitMedium)
|
2023-03-23 19:09:13 +00:00
|
|
|
client := coderdtest.New(t, &coderdtest.Options{
|
|
|
|
IncludeProvisionerDaemon: true,
|
|
|
|
})
|
|
|
|
user := coderdtest.CreateFirstUser(t, client)
|
|
|
|
authToken := uuid.NewString()
|
|
|
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
|
|
|
Parse: echo.ParseComplete,
|
2023-08-25 06:10:15 +00:00
|
|
|
ProvisionPlan: echo.PlanComplete,
|
|
|
|
ProvisionApply: []*proto.Response{{
|
|
|
|
Type: &proto.Response_Apply{
|
|
|
|
Apply: &proto.ApplyComplete{
|
2023-03-23 19:09:13 +00:00
|
|
|
Resources: []*proto.Resource{{
|
|
|
|
Name: "example",
|
|
|
|
Type: "aws_instance",
|
|
|
|
Agents: []*proto.Agent{{
|
|
|
|
Id: uuid.NewString(),
|
|
|
|
Auth: &proto.Agent_Token{
|
|
|
|
Token: authToken,
|
|
|
|
},
|
|
|
|
}},
|
|
|
|
}},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}},
|
|
|
|
})
|
|
|
|
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
|
|
|
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
|
|
|
|
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
|
|
|
|
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
|
|
|
|
|
|
|
|
updates, err := client.WatchWorkspace(ctx, workspace.ID)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
agentClient := agentsdk.New(client.URL)
|
|
|
|
agentClient.SetSessionToken(authToken)
|
2023-07-28 15:57:23 +00:00
|
|
|
err = agentClient.PatchLogs(ctx, agentsdk.PatchLogs{
|
|
|
|
Logs: []agentsdk.Log{{
|
2023-09-01 16:50:12 +00:00
|
|
|
CreatedAt: dbtime.Now(),
|
2023-03-23 19:09:13 +00:00
|
|
|
Output: strings.Repeat("a", (1<<20)+1),
|
|
|
|
}},
|
|
|
|
})
|
|
|
|
var apiError *codersdk.Error
|
|
|
|
require.ErrorAs(t, err, &apiError)
|
|
|
|
require.Equal(t, http.StatusRequestEntityTooLarge, apiError.StatusCode())
|
|
|
|
|
2023-03-30 15:05:45 +00:00
|
|
|
// It's possible we have multiple updates queued, but that's alright, we just
|
|
|
|
// wait for the one where it overflows.
|
|
|
|
for {
|
|
|
|
var update codersdk.Workspace
|
|
|
|
select {
|
|
|
|
case <-ctx.Done():
|
|
|
|
t.FailNow()
|
|
|
|
case update = <-updates:
|
|
|
|
}
|
2023-07-28 15:57:23 +00:00
|
|
|
if update.LatestBuild.Resources[0].Agents[0].LogsOverflowed {
|
2023-03-30 15:05:45 +00:00
|
|
|
break
|
|
|
|
}
|
2023-03-23 19:09:13 +00:00
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2022-04-11 21:06:15 +00:00
|
|
|
func TestWorkspaceAgentListen(t *testing.T) {
|
|
|
|
t.Parallel()
|
2022-06-02 20:03:01 +00:00
|
|
|
|
|
|
|
t.Run("Connect", func(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
2022-06-27 18:50:52 +00:00
|
|
|
client := coderdtest.New(t, &coderdtest.Options{
|
2022-09-04 16:28:09 +00:00
|
|
|
IncludeProvisionerDaemon: true,
|
2022-06-27 18:50:52 +00:00
|
|
|
})
|
2022-06-02 20:03:01 +00:00
|
|
|
user := coderdtest.CreateFirstUser(t, client)
|
|
|
|
authToken := uuid.NewString()
|
|
|
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
2023-03-21 18:03:38 +00:00
|
|
|
Parse: echo.ParseComplete,
|
2023-08-25 06:10:15 +00:00
|
|
|
ProvisionPlan: echo.PlanComplete,
|
2023-03-21 18:03:38 +00:00
|
|
|
ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
|
2022-06-02 20:03:01 +00:00
|
|
|
})
|
|
|
|
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
|
|
|
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
|
|
|
|
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
|
|
|
|
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
|
|
|
|
|
2023-01-29 21:47:24 +00:00
|
|
|
agentClient := agentsdk.New(client.URL)
|
2022-11-09 13:31:24 +00:00
|
|
|
agentClient.SetSessionToken(authToken)
|
2022-09-01 01:09:44 +00:00
|
|
|
agentCloser := agent.New(agent.Options{
|
2022-10-24 03:35:08 +00:00
|
|
|
Client: agentClient,
|
|
|
|
Logger: slogtest.Make(t, nil).Named("agent").Leveled(slog.LevelDebug),
|
2022-06-02 20:03:01 +00:00
|
|
|
})
|
2022-07-25 16:22:02 +00:00
|
|
|
defer func() {
|
2022-06-02 20:03:01 +00:00
|
|
|
_ = agentCloser.Close()
|
2022-07-25 16:22:02 +00:00
|
|
|
}()
|
2022-08-09 17:17:00 +00:00
|
|
|
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
|
|
|
defer cancel()
|
|
|
|
|
2022-10-03 21:01:13 +00:00
|
|
|
resources := coderdtest.AwaitWorkspaceAgents(t, client, workspace.ID)
|
2022-10-17 13:43:30 +00:00
|
|
|
conn, err := client.DialWorkspaceAgent(ctx, resources[0].Agents[0].ID, nil)
|
2022-06-02 20:03:01 +00:00
|
|
|
require.NoError(t, err)
|
2022-07-25 16:22:02 +00:00
|
|
|
defer func() {
|
2022-06-02 20:03:01 +00:00
|
|
|
_ = conn.Close()
|
2022-07-25 16:22:02 +00:00
|
|
|
}()
|
2022-11-13 17:33:05 +00:00
|
|
|
conn.AwaitReachable(ctx)
|
2022-04-11 21:06:15 +00:00
|
|
|
})
|
2022-06-02 20:03:01 +00:00
|
|
|
|
|
|
|
t.Run("FailNonLatestBuild", func(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
2022-06-27 18:50:52 +00:00
|
|
|
client := coderdtest.New(t, &coderdtest.Options{
|
2022-09-04 16:28:09 +00:00
|
|
|
IncludeProvisionerDaemon: true,
|
2022-06-27 18:50:52 +00:00
|
|
|
})
|
2022-06-02 20:03:01 +00:00
|
|
|
|
2022-06-27 18:50:52 +00:00
|
|
|
user := coderdtest.CreateFirstUser(t, client)
|
2022-06-02 20:03:01 +00:00
|
|
|
authToken := uuid.NewString()
|
|
|
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
2023-03-21 18:03:38 +00:00
|
|
|
Parse: echo.ParseComplete,
|
2023-08-25 06:10:15 +00:00
|
|
|
ProvisionPlan: echo.PlanComplete,
|
2023-03-21 18:03:38 +00:00
|
|
|
ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
|
2022-06-02 20:03:01 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
|
|
|
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
|
|
|
|
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
|
|
|
|
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
|
|
|
|
|
|
|
|
version = coderdtest.UpdateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
2022-11-11 22:45:58 +00:00
|
|
|
Parse: echo.ParseComplete,
|
2023-08-25 06:10:15 +00:00
|
|
|
ProvisionPlan: echo.PlanComplete,
|
|
|
|
ProvisionApply: []*proto.Response{{
|
|
|
|
Type: &proto.Response_Apply{
|
|
|
|
Apply: &proto.ApplyComplete{
|
2022-06-02 20:03:01 +00:00
|
|
|
Resources: []*proto.Resource{{
|
|
|
|
Name: "example",
|
|
|
|
Type: "aws_instance",
|
|
|
|
Agents: []*proto.Agent{{
|
|
|
|
Id: uuid.NewString(),
|
|
|
|
Auth: &proto.Agent_Token{
|
|
|
|
Token: uuid.NewString(),
|
|
|
|
},
|
|
|
|
}},
|
|
|
|
}},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}},
|
|
|
|
}, template.ID)
|
|
|
|
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
|
|
|
|
|
2022-08-09 17:17:00 +00:00
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
stopBuild, err := client.CreateWorkspaceBuild(ctx, workspace.ID, codersdk.CreateWorkspaceBuildRequest{
|
2022-06-02 20:03:01 +00:00
|
|
|
TemplateVersionID: version.ID,
|
|
|
|
Transition: codersdk.WorkspaceTransitionStop,
|
|
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
|
|
coderdtest.AwaitWorkspaceBuildJob(t, client, stopBuild.ID)
|
|
|
|
|
2023-01-29 21:47:24 +00:00
|
|
|
agentClient := agentsdk.New(client.URL)
|
2022-11-09 13:31:24 +00:00
|
|
|
agentClient.SetSessionToken(authToken)
|
2022-06-02 20:03:01 +00:00
|
|
|
|
2023-01-29 21:47:24 +00:00
|
|
|
_, err = agentClient.Listen(ctx)
|
2022-06-02 20:03:01 +00:00
|
|
|
require.Error(t, err)
|
2023-08-21 14:49:26 +00:00
|
|
|
var sdkErr *codersdk.Error
|
|
|
|
require.ErrorAs(t, err, &sdkErr)
|
|
|
|
require.Equal(t, http.StatusForbidden, sdkErr.StatusCode())
|
2022-04-11 21:06:15 +00:00
|
|
|
})
|
2022-04-18 22:40:25 +00:00
|
|
|
}
|
|
|
|
|
2022-09-01 01:09:44 +00:00
|
|
|
func TestWorkspaceAgentTailnet(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
client, daemonCloser := coderdtest.NewWithProvisionerCloser(t, nil)
|
|
|
|
user := coderdtest.CreateFirstUser(t, client)
|
|
|
|
authToken := uuid.NewString()
|
|
|
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
2023-03-21 18:03:38 +00:00
|
|
|
Parse: echo.ParseComplete,
|
2023-08-25 06:10:15 +00:00
|
|
|
ProvisionPlan: echo.PlanComplete,
|
2023-03-21 18:03:38 +00:00
|
|
|
ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
|
2022-09-01 01:09:44 +00:00
|
|
|
})
|
|
|
|
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
|
|
|
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
|
|
|
|
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
|
|
|
|
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
|
|
|
|
daemonCloser.Close()
|
|
|
|
|
2023-01-29 21:47:24 +00:00
|
|
|
agentClient := agentsdk.New(client.URL)
|
2022-11-09 13:31:24 +00:00
|
|
|
agentClient.SetSessionToken(authToken)
|
2022-09-01 01:09:44 +00:00
|
|
|
agentCloser := agent.New(agent.Options{
|
2022-10-24 03:35:08 +00:00
|
|
|
Client: agentClient,
|
|
|
|
Logger: slogtest.Make(t, nil).Named("agent").Leveled(slog.LevelDebug),
|
2022-09-01 01:09:44 +00:00
|
|
|
})
|
|
|
|
defer agentCloser.Close()
|
2022-10-03 21:01:13 +00:00
|
|
|
resources := coderdtest.AwaitWorkspaceAgents(t, client, workspace.ID)
|
2022-09-01 01:09:44 +00:00
|
|
|
|
|
|
|
ctx, cancelFunc := context.WithCancel(context.Background())
|
|
|
|
defer cancelFunc()
|
2022-10-17 13:43:30 +00:00
|
|
|
conn, err := client.DialWorkspaceAgent(ctx, resources[0].Agents[0].ID, &codersdk.DialWorkspaceAgentOptions{
|
|
|
|
Logger: slogtest.Make(t, nil).Named("client").Leveled(slog.LevelDebug),
|
|
|
|
})
|
2022-09-01 01:09:44 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
defer conn.Close()
|
2022-11-08 22:10:48 +00:00
|
|
|
sshClient, err := conn.SSHClient(ctx)
|
2022-09-01 01:09:44 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
session, err := sshClient.NewSession()
|
|
|
|
require.NoError(t, err)
|
|
|
|
output, err := session.CombinedOutput("echo test")
|
|
|
|
require.NoError(t, err)
|
|
|
|
_ = session.Close()
|
|
|
|
_ = sshClient.Close()
|
|
|
|
_ = conn.Close()
|
|
|
|
require.Equal(t, "test", strings.TrimSpace(string(output)))
|
|
|
|
}
|
|
|
|
|
2023-06-21 22:02:05 +00:00
|
|
|
func TestWorkspaceAgentTailnetDirectDisabled(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
dv := coderdtest.DeploymentValues(t)
|
|
|
|
err := dv.DERP.Config.BlockDirect.Set("true")
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.True(t, dv.DERP.Config.BlockDirect.Value())
|
|
|
|
|
|
|
|
client, daemonCloser := coderdtest.NewWithProvisionerCloser(t, &coderdtest.Options{
|
|
|
|
DeploymentValues: dv,
|
|
|
|
})
|
|
|
|
user := coderdtest.CreateFirstUser(t, client)
|
|
|
|
authToken := uuid.NewString()
|
|
|
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
|
|
|
Parse: echo.ParseComplete,
|
2023-08-25 06:10:15 +00:00
|
|
|
ProvisionPlan: echo.PlanComplete,
|
2023-06-21 22:02:05 +00:00
|
|
|
ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
|
|
|
|
})
|
|
|
|
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
|
|
|
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
|
|
|
|
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
|
|
|
|
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
|
|
|
|
daemonCloser.Close()
|
|
|
|
|
|
|
|
ctx := testutil.Context(t, testutil.WaitLong)
|
|
|
|
|
|
|
|
// Verify that the manifest has DisableDirectConnections set to true.
|
|
|
|
agentClient := agentsdk.New(client.URL)
|
|
|
|
agentClient.SetSessionToken(authToken)
|
|
|
|
manifest, err := agentClient.Manifest(ctx)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.True(t, manifest.DisableDirectConnections)
|
|
|
|
|
|
|
|
agentCloser := agent.New(agent.Options{
|
|
|
|
Client: agentClient,
|
|
|
|
Logger: slogtest.Make(t, nil).Named("agent").Leveled(slog.LevelDebug),
|
|
|
|
})
|
|
|
|
defer agentCloser.Close()
|
|
|
|
resources := coderdtest.AwaitWorkspaceAgents(t, client, workspace.ID)
|
|
|
|
agentID := resources[0].Agents[0].ID
|
|
|
|
|
|
|
|
// Verify that the connection data has no STUN ports and
|
|
|
|
// DisableDirectConnections set to true.
|
|
|
|
res, err := client.Request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/workspaceagents/%s/connection", agentID), nil)
|
|
|
|
require.NoError(t, err)
|
|
|
|
defer res.Body.Close()
|
|
|
|
require.Equal(t, http.StatusOK, res.StatusCode)
|
|
|
|
var connInfo codersdk.WorkspaceAgentConnectionInfo
|
|
|
|
err = json.NewDecoder(res.Body).Decode(&connInfo)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.True(t, connInfo.DisableDirectConnections)
|
|
|
|
for _, region := range connInfo.DERPMap.Regions {
|
|
|
|
t.Logf("region %s (%v)", region.RegionCode, region.EmbeddedRelay)
|
|
|
|
for _, node := range region.Nodes {
|
|
|
|
t.Logf(" node %s (stun %d)", node.Name, node.STUNPort)
|
|
|
|
require.EqualValues(t, -1, node.STUNPort)
|
|
|
|
// tailnet.NewDERPMap() will create nodes with "stun" in the name,
|
|
|
|
// but not if direct is disabled.
|
|
|
|
require.NotContains(t, node.Name, "stun")
|
|
|
|
require.False(t, node.STUNOnly)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
conn, err := client.DialWorkspaceAgent(ctx, resources[0].Agents[0].ID, &codersdk.DialWorkspaceAgentOptions{
|
|
|
|
Logger: slogtest.Make(t, nil).Named("client").Leveled(slog.LevelDebug),
|
|
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
|
|
defer conn.Close()
|
|
|
|
require.True(t, conn.BlockEndpoints())
|
|
|
|
|
|
|
|
require.True(t, conn.AwaitReachable(ctx))
|
|
|
|
_, p2p, _, err := conn.Ping(ctx)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.False(t, p2p)
|
|
|
|
}
|
|
|
|
|
2022-10-06 12:38:22 +00:00
|
|
|
func TestWorkspaceAgentListeningPorts(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
2022-10-11 15:10:02 +00:00
|
|
|
setup := func(t *testing.T, apps []*proto.App) (*codersdk.Client, uint16, uuid.UUID) {
|
2023-02-24 11:11:28 +00:00
|
|
|
t.Helper()
|
|
|
|
|
2022-10-11 15:10:02 +00:00
|
|
|
client := coderdtest.New(t, &coderdtest.Options{
|
|
|
|
IncludeProvisionerDaemon: true,
|
|
|
|
})
|
|
|
|
coderdPort, err := strconv.Atoi(client.URL.Port())
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
user := coderdtest.CreateFirstUser(t, client)
|
|
|
|
authToken := uuid.NewString()
|
|
|
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
2022-11-11 22:45:58 +00:00
|
|
|
Parse: echo.ParseComplete,
|
2023-08-25 06:10:15 +00:00
|
|
|
ProvisionPlan: echo.PlanComplete,
|
|
|
|
ProvisionApply: []*proto.Response{{
|
|
|
|
Type: &proto.Response_Apply{
|
|
|
|
Apply: &proto.ApplyComplete{
|
2022-10-11 15:10:02 +00:00
|
|
|
Resources: []*proto.Resource{{
|
|
|
|
Name: "example",
|
|
|
|
Type: "aws_instance",
|
|
|
|
Agents: []*proto.Agent{{
|
|
|
|
Id: uuid.NewString(),
|
|
|
|
Auth: &proto.Agent_Token{
|
|
|
|
Token: authToken,
|
|
|
|
},
|
|
|
|
Apps: apps,
|
|
|
|
}},
|
2022-10-06 12:38:22 +00:00
|
|
|
}},
|
2022-10-11 15:10:02 +00:00
|
|
|
},
|
2022-10-06 12:38:22 +00:00
|
|
|
},
|
2022-10-11 15:10:02 +00:00
|
|
|
}},
|
|
|
|
})
|
|
|
|
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
|
|
|
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
|
|
|
|
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
|
|
|
|
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
|
2022-10-06 12:38:22 +00:00
|
|
|
|
2023-01-29 21:47:24 +00:00
|
|
|
agentClient := agentsdk.New(client.URL)
|
2022-11-09 13:31:24 +00:00
|
|
|
agentClient.SetSessionToken(authToken)
|
2022-10-11 15:10:02 +00:00
|
|
|
agentCloser := agent.New(agent.Options{
|
2022-10-24 03:35:08 +00:00
|
|
|
Client: agentClient,
|
|
|
|
Logger: slogtest.Make(t, nil).Named("agent").Leveled(slog.LevelDebug),
|
2022-10-11 15:10:02 +00:00
|
|
|
})
|
|
|
|
t.Cleanup(func() {
|
|
|
|
_ = agentCloser.Close()
|
|
|
|
})
|
|
|
|
resources := coderdtest.AwaitWorkspaceAgents(t, client, workspace.ID)
|
|
|
|
|
|
|
|
return client, uint16(coderdPort), resources[0].Agents[0].ID
|
|
|
|
}
|
|
|
|
|
|
|
|
willFilterPort := func(port int) bool {
|
2023-01-29 21:47:24 +00:00
|
|
|
if port < codersdk.WorkspaceAgentMinimumListeningPort || port > 65535 {
|
2022-10-11 15:10:02 +00:00
|
|
|
return true
|
|
|
|
}
|
2023-01-29 21:47:24 +00:00
|
|
|
if _, ok := codersdk.WorkspaceAgentIgnoredListeningPorts[uint16(port)]; ok {
|
2022-10-11 15:10:02 +00:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
generateUnfilteredPort := func(t *testing.T) (net.Listener, uint16) {
|
|
|
|
var (
|
|
|
|
l net.Listener
|
|
|
|
port uint16
|
|
|
|
)
|
|
|
|
require.Eventually(t, func() bool {
|
|
|
|
var err error
|
|
|
|
l, err = net.Listen("tcp", "localhost:0")
|
|
|
|
if err != nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
tcpAddr, _ := l.Addr().(*net.TCPAddr)
|
|
|
|
if willFilterPort(tcpAddr.Port) {
|
|
|
|
_ = l.Close()
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
t.Cleanup(func() {
|
|
|
|
_ = l.Close()
|
|
|
|
})
|
|
|
|
|
|
|
|
port = uint16(tcpAddr.Port)
|
|
|
|
return true
|
|
|
|
}, testutil.WaitShort, testutil.IntervalFast)
|
|
|
|
|
|
|
|
return l, port
|
|
|
|
}
|
|
|
|
|
|
|
|
generateFilteredPort := func(t *testing.T) (net.Listener, uint16) {
|
|
|
|
var (
|
|
|
|
l net.Listener
|
|
|
|
port uint16
|
|
|
|
)
|
|
|
|
require.Eventually(t, func() bool {
|
2023-01-29 21:47:24 +00:00
|
|
|
for ignoredPort := range codersdk.WorkspaceAgentIgnoredListeningPorts {
|
2022-10-11 15:10:02 +00:00
|
|
|
if ignoredPort < 1024 || ignoredPort == 5432 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
var err error
|
|
|
|
l, err = net.Listen("tcp", fmt.Sprintf("localhost:%d", ignoredPort))
|
|
|
|
if err != nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
t.Cleanup(func() {
|
|
|
|
_ = l.Close()
|
|
|
|
})
|
|
|
|
|
|
|
|
port = ignoredPort
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
return false
|
|
|
|
}, testutil.WaitShort, testutil.IntervalFast)
|
|
|
|
|
|
|
|
return l, port
|
|
|
|
}
|
2022-10-06 12:38:22 +00:00
|
|
|
|
|
|
|
t.Run("LinuxAndWindows", func(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
if runtime.GOOS != "linux" && runtime.GOOS != "windows" {
|
|
|
|
t.Skip("only runs on linux and windows")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-10-11 15:10:02 +00:00
|
|
|
t.Run("OK", func(t *testing.T) {
|
|
|
|
t.Parallel()
|
2022-10-06 12:38:22 +00:00
|
|
|
|
2022-10-11 15:10:02 +00:00
|
|
|
client, coderdPort, agentID := setup(t, nil)
|
2022-10-06 12:38:22 +00:00
|
|
|
|
2022-10-11 15:10:02 +00:00
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
|
|
|
defer cancel()
|
2022-10-06 12:38:22 +00:00
|
|
|
|
2022-10-11 15:10:02 +00:00
|
|
|
// Generate a random unfiltered port.
|
|
|
|
l, lPort := generateUnfilteredPort(t)
|
|
|
|
|
|
|
|
// List ports and ensure that the port we expect to see is there.
|
|
|
|
res, err := client.WorkspaceAgentListeningPorts(ctx, agentID)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
2023-02-19 00:32:09 +00:00
|
|
|
expected := map[uint16]bool{
|
|
|
|
// expect the listener we made
|
|
|
|
lPort: false,
|
|
|
|
// expect the coderdtest server
|
|
|
|
coderdPort: false,
|
|
|
|
}
|
2022-10-11 15:10:02 +00:00
|
|
|
for _, port := range res.Ports {
|
2023-01-29 21:47:24 +00:00
|
|
|
if port.Network == "tcp" {
|
2022-10-11 15:10:02 +00:00
|
|
|
if val, ok := expected[port.Port]; ok {
|
|
|
|
if val {
|
|
|
|
t.Fatalf("expected to find TCP port %d only once in response", port.Port)
|
|
|
|
}
|
2022-10-06 12:38:22 +00:00
|
|
|
}
|
2022-10-11 15:10:02 +00:00
|
|
|
expected[port.Port] = true
|
2022-10-06 12:38:22 +00:00
|
|
|
}
|
|
|
|
}
|
2022-10-11 15:10:02 +00:00
|
|
|
for port, found := range expected {
|
|
|
|
if !found {
|
|
|
|
t.Fatalf("expected to find TCP port %d in response", port)
|
|
|
|
}
|
2022-10-06 12:38:22 +00:00
|
|
|
}
|
|
|
|
|
2022-10-11 15:10:02 +00:00
|
|
|
// Close the listener and check that the port is no longer in the response.
|
|
|
|
require.NoError(t, l.Close())
|
|
|
|
time.Sleep(2 * time.Second) // avoid cache
|
|
|
|
res, err = client.WorkspaceAgentListeningPorts(ctx, agentID)
|
|
|
|
require.NoError(t, err)
|
2022-10-06 12:38:22 +00:00
|
|
|
|
2022-10-11 15:10:02 +00:00
|
|
|
for _, port := range res.Ports {
|
2023-01-29 21:47:24 +00:00
|
|
|
if port.Network == "tcp" && port.Port == lPort {
|
2022-10-11 15:10:02 +00:00
|
|
|
t.Fatalf("expected to not find TCP port %d in response", lPort)
|
|
|
|
}
|
2022-10-06 12:38:22 +00:00
|
|
|
}
|
2022-10-11 15:10:02 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("Filter", func(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
// Generate an unfiltered port that we will create an app for and
|
|
|
|
// should not exist in the response.
|
|
|
|
_, appLPort := generateUnfilteredPort(t)
|
|
|
|
app := &proto.App{
|
2022-11-07 23:35:01 +00:00
|
|
|
Slug: "test-app",
|
|
|
|
Url: fmt.Sprintf("http://localhost:%d", appLPort),
|
2022-10-11 15:10:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Generate a filtered port that should not exist in the response.
|
|
|
|
_, filteredLPort := generateFilteredPort(t)
|
|
|
|
|
|
|
|
client, coderdPort, agentID := setup(t, []*proto.App{app})
|
|
|
|
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
res, err := client.WorkspaceAgentListeningPorts(ctx, agentID)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
sawCoderdPort := false
|
|
|
|
for _, port := range res.Ports {
|
2023-01-29 21:47:24 +00:00
|
|
|
if port.Network == "tcp" {
|
2022-10-11 15:10:02 +00:00
|
|
|
if port.Port == appLPort {
|
|
|
|
t.Fatalf("expected to not find TCP port (app port) %d in response", appLPort)
|
|
|
|
}
|
|
|
|
if port.Port == filteredLPort {
|
|
|
|
t.Fatalf("expected to not find TCP port (filtered port) %d in response", filteredLPort)
|
|
|
|
}
|
|
|
|
if port.Port == coderdPort {
|
|
|
|
sawCoderdPort = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !sawCoderdPort {
|
|
|
|
t.Fatalf("expected to find TCP port (coderd port) %d in response", coderdPort)
|
|
|
|
}
|
|
|
|
})
|
2022-10-06 12:38:22 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("Darwin", func(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
if runtime.GOOS != "darwin" {
|
|
|
|
t.Skip("only runs on darwin")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-10-11 15:10:02 +00:00
|
|
|
client, _, agentID := setup(t, nil)
|
|
|
|
|
2022-10-06 12:38:22 +00:00
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
// Create a TCP listener on a random port.
|
|
|
|
l, err := net.Listen("tcp", "localhost:0")
|
|
|
|
require.NoError(t, err)
|
|
|
|
defer l.Close()
|
|
|
|
|
|
|
|
// List ports and ensure that the list is empty because we're on darwin.
|
2022-10-11 15:10:02 +00:00
|
|
|
res, err := client.WorkspaceAgentListeningPorts(ctx, agentID)
|
2022-10-06 12:38:22 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
require.Len(t, res.Ports, 0)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2022-09-23 19:51:04 +00:00
|
|
|
func TestWorkspaceAgentAppHealth(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
client := coderdtest.New(t, &coderdtest.Options{
|
|
|
|
IncludeProvisionerDaemon: true,
|
|
|
|
})
|
|
|
|
user := coderdtest.CreateFirstUser(t, client)
|
|
|
|
authToken := uuid.NewString()
|
|
|
|
apps := []*proto.App{
|
|
|
|
{
|
2022-11-07 23:35:01 +00:00
|
|
|
Slug: "code-server",
|
|
|
|
Command: "some-command",
|
|
|
|
Url: "http://localhost:3000",
|
|
|
|
Icon: "/code.svg",
|
2022-09-23 19:51:04 +00:00
|
|
|
},
|
|
|
|
{
|
2022-10-28 17:41:31 +00:00
|
|
|
Slug: "code-server-2",
|
|
|
|
DisplayName: "code-server-2",
|
|
|
|
Command: "some-command",
|
|
|
|
Url: "http://localhost:3000",
|
|
|
|
Icon: "/code.svg",
|
2022-09-23 19:51:04 +00:00
|
|
|
Healthcheck: &proto.Healthcheck{
|
|
|
|
Url: "http://localhost:3000",
|
|
|
|
Interval: 5,
|
|
|
|
Threshold: 6,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
|
|
|
Parse: echo.ParseComplete,
|
2023-08-25 06:10:15 +00:00
|
|
|
ProvisionApply: []*proto.Response{{
|
|
|
|
Type: &proto.Response_Apply{
|
|
|
|
Apply: &proto.ApplyComplete{
|
2022-09-23 19:51:04 +00:00
|
|
|
Resources: []*proto.Resource{{
|
|
|
|
Name: "example",
|
|
|
|
Type: "aws_instance",
|
|
|
|
Agents: []*proto.Agent{{
|
|
|
|
Id: uuid.NewString(),
|
|
|
|
Auth: &proto.Agent_Token{
|
|
|
|
Token: authToken,
|
|
|
|
},
|
|
|
|
Apps: apps,
|
|
|
|
}},
|
|
|
|
}},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}},
|
|
|
|
})
|
|
|
|
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
|
|
|
|
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
|
|
|
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
|
|
|
|
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
|
|
|
|
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
|
|
|
defer cancel()
|
|
|
|
|
2023-01-29 21:47:24 +00:00
|
|
|
agentClient := agentsdk.New(client.URL)
|
2022-11-09 13:31:24 +00:00
|
|
|
agentClient.SetSessionToken(authToken)
|
2022-09-23 19:51:04 +00:00
|
|
|
|
2023-03-31 20:26:19 +00:00
|
|
|
manifest, err := agentClient.Manifest(ctx)
|
2022-09-23 19:51:04 +00:00
|
|
|
require.NoError(t, err)
|
2023-03-31 20:26:19 +00:00
|
|
|
require.EqualValues(t, codersdk.WorkspaceAppHealthDisabled, manifest.Apps[0].Health)
|
|
|
|
require.EqualValues(t, codersdk.WorkspaceAppHealthInitializing, manifest.Apps[1].Health)
|
2023-01-29 21:47:24 +00:00
|
|
|
err = agentClient.PostAppHealth(ctx, agentsdk.PostAppHealthsRequest{})
|
2022-09-23 19:51:04 +00:00
|
|
|
require.Error(t, err)
|
|
|
|
// empty
|
2023-01-29 21:47:24 +00:00
|
|
|
err = agentClient.PostAppHealth(ctx, agentsdk.PostAppHealthsRequest{})
|
2022-09-23 19:51:04 +00:00
|
|
|
require.Error(t, err)
|
2022-11-07 23:35:01 +00:00
|
|
|
// healthcheck disabled
|
2023-01-29 21:47:24 +00:00
|
|
|
err = agentClient.PostAppHealth(ctx, agentsdk.PostAppHealthsRequest{
|
2022-11-07 23:35:01 +00:00
|
|
|
Healths: map[uuid.UUID]codersdk.WorkspaceAppHealth{
|
2023-03-31 20:26:19 +00:00
|
|
|
manifest.Apps[0].ID: codersdk.WorkspaceAppHealthInitializing,
|
2022-09-23 19:51:04 +00:00
|
|
|
},
|
|
|
|
})
|
|
|
|
require.Error(t, err)
|
|
|
|
// invalid value
|
2023-01-29 21:47:24 +00:00
|
|
|
err = agentClient.PostAppHealth(ctx, agentsdk.PostAppHealthsRequest{
|
2022-11-07 23:35:01 +00:00
|
|
|
Healths: map[uuid.UUID]codersdk.WorkspaceAppHealth{
|
2023-03-31 20:26:19 +00:00
|
|
|
manifest.Apps[1].ID: codersdk.WorkspaceAppHealth("bad-value"),
|
2022-09-23 19:51:04 +00:00
|
|
|
},
|
|
|
|
})
|
|
|
|
require.Error(t, err)
|
|
|
|
// update to healthy
|
2023-01-29 21:47:24 +00:00
|
|
|
err = agentClient.PostAppHealth(ctx, agentsdk.PostAppHealthsRequest{
|
2022-11-07 23:35:01 +00:00
|
|
|
Healths: map[uuid.UUID]codersdk.WorkspaceAppHealth{
|
2023-03-31 20:26:19 +00:00
|
|
|
manifest.Apps[1].ID: codersdk.WorkspaceAppHealthHealthy,
|
2022-09-23 19:51:04 +00:00
|
|
|
},
|
|
|
|
})
|
|
|
|
require.NoError(t, err)
|
2023-03-31 20:26:19 +00:00
|
|
|
manifest, err = agentClient.Manifest(ctx)
|
2022-09-23 19:51:04 +00:00
|
|
|
require.NoError(t, err)
|
2023-03-31 20:26:19 +00:00
|
|
|
require.EqualValues(t, codersdk.WorkspaceAppHealthHealthy, manifest.Apps[1].Health)
|
2022-09-23 19:51:04 +00:00
|
|
|
// update to unhealthy
|
2023-01-29 21:47:24 +00:00
|
|
|
err = agentClient.PostAppHealth(ctx, agentsdk.PostAppHealthsRequest{
|
2022-11-07 23:35:01 +00:00
|
|
|
Healths: map[uuid.UUID]codersdk.WorkspaceAppHealth{
|
2023-03-31 20:26:19 +00:00
|
|
|
manifest.Apps[1].ID: codersdk.WorkspaceAppHealthUnhealthy,
|
2022-09-23 19:51:04 +00:00
|
|
|
},
|
|
|
|
})
|
|
|
|
require.NoError(t, err)
|
2023-03-31 20:26:19 +00:00
|
|
|
manifest, err = agentClient.Manifest(ctx)
|
2022-09-23 19:51:04 +00:00
|
|
|
require.NoError(t, err)
|
2023-03-31 20:26:19 +00:00
|
|
|
require.EqualValues(t, codersdk.WorkspaceAppHealthUnhealthy, manifest.Apps[1].Health)
|
2022-09-23 19:51:04 +00:00
|
|
|
}
|
2022-10-25 00:46:24 +00:00
|
|
|
|
2022-11-18 22:46:53 +00:00
|
|
|
func TestWorkspaceAgentReportStats(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
t.Run("OK", func(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
client := coderdtest.New(t, &coderdtest.Options{
|
|
|
|
IncludeProvisionerDaemon: true,
|
|
|
|
})
|
|
|
|
user := coderdtest.CreateFirstUser(t, client)
|
|
|
|
authToken := uuid.NewString()
|
|
|
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
2023-03-21 18:03:38 +00:00
|
|
|
Parse: echo.ParseComplete,
|
2023-08-25 06:10:15 +00:00
|
|
|
ProvisionPlan: echo.PlanComplete,
|
2023-03-21 18:03:38 +00:00
|
|
|
ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
|
2022-11-18 22:46:53 +00:00
|
|
|
})
|
|
|
|
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
|
|
|
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
|
|
|
|
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
|
|
|
|
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
|
|
|
|
|
2023-01-29 21:47:24 +00:00
|
|
|
agentClient := agentsdk.New(client.URL)
|
2022-11-18 22:46:53 +00:00
|
|
|
agentClient.SetSessionToken(authToken)
|
|
|
|
|
2023-01-29 21:47:24 +00:00
|
|
|
_, err := agentClient.PostStats(context.Background(), &agentsdk.Stats{
|
2023-03-02 14:06:00 +00:00
|
|
|
ConnectionsByProto: map[string]int64{"TCP": 1},
|
|
|
|
ConnectionCount: 1,
|
|
|
|
RxPackets: 1,
|
|
|
|
RxBytes: 1,
|
|
|
|
TxPackets: 1,
|
|
|
|
TxBytes: 1,
|
|
|
|
SessionCountVSCode: 1,
|
|
|
|
SessionCountJetBrains: 1,
|
|
|
|
SessionCountReconnectingPTY: 1,
|
|
|
|
SessionCountSSH: 1,
|
|
|
|
ConnectionMedianLatencyMS: 10,
|
2022-11-18 22:46:53 +00:00
|
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
newWorkspace, err := client.Workspace(context.Background(), workspace.ID)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
assert.True(t,
|
|
|
|
newWorkspace.LastUsedAt.After(workspace.LastUsedAt),
|
|
|
|
"%s is not after %s", newWorkspace.LastUsedAt, workspace.LastUsedAt,
|
|
|
|
)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2023-01-24 12:24:27 +00:00
|
|
|
func TestWorkspaceAgent_LifecycleState(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
t.Run("Set", func(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
client := coderdtest.New(t, &coderdtest.Options{
|
|
|
|
IncludeProvisionerDaemon: true,
|
|
|
|
})
|
|
|
|
user := coderdtest.CreateFirstUser(t, client)
|
|
|
|
authToken := uuid.NewString()
|
|
|
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
2023-03-21 18:03:38 +00:00
|
|
|
Parse: echo.ParseComplete,
|
2023-08-25 06:10:15 +00:00
|
|
|
ProvisionPlan: echo.PlanComplete,
|
2023-03-21 18:03:38 +00:00
|
|
|
ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
|
2023-01-24 12:24:27 +00:00
|
|
|
})
|
|
|
|
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
|
|
|
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
|
|
|
|
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
|
|
|
|
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
|
|
|
|
|
|
|
|
for _, res := range workspace.LatestBuild.Resources {
|
|
|
|
for _, a := range res.Agents {
|
|
|
|
require.Equal(t, codersdk.WorkspaceAgentLifecycleCreated, a.LifecycleState)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-29 21:47:24 +00:00
|
|
|
agentClient := agentsdk.New(client.URL)
|
2023-01-24 12:24:27 +00:00
|
|
|
agentClient.SetSessionToken(authToken)
|
|
|
|
|
|
|
|
tests := []struct {
|
|
|
|
state codersdk.WorkspaceAgentLifecycle
|
|
|
|
wantErr bool
|
|
|
|
}{
|
|
|
|
{codersdk.WorkspaceAgentLifecycleCreated, false},
|
|
|
|
{codersdk.WorkspaceAgentLifecycleStarting, false},
|
|
|
|
{codersdk.WorkspaceAgentLifecycleStartTimeout, false},
|
|
|
|
{codersdk.WorkspaceAgentLifecycleStartError, false},
|
|
|
|
{codersdk.WorkspaceAgentLifecycleReady, false},
|
2023-03-06 19:34:00 +00:00
|
|
|
{codersdk.WorkspaceAgentLifecycleShuttingDown, false},
|
|
|
|
{codersdk.WorkspaceAgentLifecycleShutdownTimeout, false},
|
|
|
|
{codersdk.WorkspaceAgentLifecycleShutdownError, false},
|
|
|
|
{codersdk.WorkspaceAgentLifecycleOff, false},
|
2023-01-24 12:24:27 +00:00
|
|
|
{codersdk.WorkspaceAgentLifecycle("nonexistent_state"), true},
|
|
|
|
{codersdk.WorkspaceAgentLifecycle(""), true},
|
|
|
|
}
|
|
|
|
//nolint:paralleltest // No race between setting the state and getting the workspace.
|
|
|
|
for _, tt := range tests {
|
|
|
|
tt := tt
|
|
|
|
t.Run(string(tt.state), func(t *testing.T) {
|
2023-03-23 22:42:20 +00:00
|
|
|
ctx := testutil.Context(t, testutil.WaitLong)
|
2023-01-24 12:24:27 +00:00
|
|
|
|
2023-01-29 21:47:24 +00:00
|
|
|
err := agentClient.PostLifecycle(ctx, agentsdk.PostLifecycleRequest{
|
2023-06-20 11:41:55 +00:00
|
|
|
State: tt.state,
|
|
|
|
ChangedAt: time.Now(),
|
2023-01-24 12:24:27 +00:00
|
|
|
})
|
|
|
|
if tt.wantErr {
|
|
|
|
require.Error(t, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
require.NoError(t, err, "post lifecycle state %q", tt.state)
|
|
|
|
|
|
|
|
workspace, err = client.Workspace(ctx, workspace.ID)
|
|
|
|
require.NoError(t, err, "get workspace")
|
|
|
|
|
|
|
|
for _, res := range workspace.LatestBuild.Resources {
|
|
|
|
for _, agent := range res.Agents {
|
|
|
|
require.Equal(t, tt.state, agent.LifecycleState)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
2023-03-31 20:26:19 +00:00
|
|
|
|
|
|
|
func TestWorkspaceAgent_Metadata(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
client := coderdtest.New(t, &coderdtest.Options{
|
|
|
|
IncludeProvisionerDaemon: true,
|
|
|
|
})
|
|
|
|
user := coderdtest.CreateFirstUser(t, client)
|
|
|
|
authToken := uuid.NewString()
|
|
|
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
|
|
|
Parse: echo.ParseComplete,
|
2023-08-25 06:10:15 +00:00
|
|
|
ProvisionPlan: echo.PlanComplete,
|
|
|
|
ProvisionApply: []*proto.Response{{
|
|
|
|
Type: &proto.Response_Apply{
|
|
|
|
Apply: &proto.ApplyComplete{
|
2023-03-31 20:26:19 +00:00
|
|
|
Resources: []*proto.Resource{{
|
|
|
|
Name: "example",
|
|
|
|
Type: "aws_instance",
|
|
|
|
Agents: []*proto.Agent{{
|
|
|
|
Metadata: []*proto.Agent_Metadata{
|
|
|
|
{
|
|
|
|
DisplayName: "First Meta",
|
|
|
|
Key: "foo1",
|
|
|
|
Script: "echo hi",
|
|
|
|
Interval: 10,
|
|
|
|
Timeout: 3,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
DisplayName: "Second Meta",
|
|
|
|
Key: "foo2",
|
|
|
|
Script: "echo howdy",
|
|
|
|
Interval: 10,
|
|
|
|
Timeout: 3,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
DisplayName: "TooLong",
|
|
|
|
Key: "foo3",
|
|
|
|
Script: "echo howdy",
|
|
|
|
Interval: 10,
|
|
|
|
Timeout: 3,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Id: uuid.NewString(),
|
|
|
|
Auth: &proto.Agent_Token{
|
|
|
|
Token: authToken,
|
|
|
|
},
|
|
|
|
}},
|
|
|
|
}},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}},
|
|
|
|
})
|
|
|
|
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
|
|
|
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
|
|
|
|
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
|
|
|
|
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
|
|
|
|
|
|
|
|
for _, res := range workspace.LatestBuild.Resources {
|
|
|
|
for _, a := range res.Agents {
|
|
|
|
require.Equal(t, codersdk.WorkspaceAgentLifecycleCreated, a.LifecycleState)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
agentClient := agentsdk.New(client.URL)
|
|
|
|
agentClient.SetSessionToken(authToken)
|
|
|
|
|
|
|
|
ctx := testutil.Context(t, testutil.WaitMedium)
|
|
|
|
|
|
|
|
manifest, err := agentClient.Manifest(ctx)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
// Verify manifest API response.
|
|
|
|
require.Equal(t, "First Meta", manifest.Metadata[0].DisplayName)
|
|
|
|
require.Equal(t, "foo1", manifest.Metadata[0].Key)
|
|
|
|
require.Equal(t, "echo hi", manifest.Metadata[0].Script)
|
|
|
|
require.EqualValues(t, 10, manifest.Metadata[0].Interval)
|
|
|
|
require.EqualValues(t, 3, manifest.Metadata[0].Timeout)
|
|
|
|
|
|
|
|
post := func(key string, mr codersdk.WorkspaceAgentMetadataResult) {
|
|
|
|
err := agentClient.PostMetadata(ctx, key, mr)
|
|
|
|
require.NoError(t, err, "post metadata", t)
|
|
|
|
}
|
|
|
|
|
|
|
|
workspace, err = client.Workspace(ctx, workspace.ID)
|
|
|
|
require.NoError(t, err, "get workspace")
|
|
|
|
|
|
|
|
agentID := workspace.LatestBuild.Resources[0].Agents[0].ID
|
|
|
|
|
|
|
|
var update []codersdk.WorkspaceAgentMetadata
|
|
|
|
|
|
|
|
wantMetadata1 := codersdk.WorkspaceAgentMetadataResult{
|
|
|
|
CollectedAt: time.Now(),
|
|
|
|
Value: "bar",
|
|
|
|
}
|
|
|
|
|
|
|
|
// Initial post must come before the Watch is established.
|
|
|
|
post("foo1", wantMetadata1)
|
|
|
|
|
|
|
|
updates, errors := client.WatchWorkspaceAgentMetadata(ctx, agentID)
|
|
|
|
|
|
|
|
recvUpdate := func() []codersdk.WorkspaceAgentMetadata {
|
|
|
|
select {
|
2023-06-13 12:21:06 +00:00
|
|
|
case <-ctx.Done():
|
|
|
|
t.Fatalf("context done: %v", ctx.Err())
|
2023-03-31 20:26:19 +00:00
|
|
|
case err := <-errors:
|
|
|
|
t.Fatalf("error watching metadata: %v", err)
|
|
|
|
case update := <-updates:
|
|
|
|
return update
|
|
|
|
}
|
2023-06-13 12:21:06 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
check := func(want codersdk.WorkspaceAgentMetadataResult, got codersdk.WorkspaceAgentMetadata, retry bool) {
|
|
|
|
// We can't trust the order of the updates due to timers and debounces,
|
|
|
|
// so let's check a few times more.
|
|
|
|
for i := 0; retry && i < 2 && (want.Value != got.Result.Value || want.Error != got.Result.Error); i++ {
|
|
|
|
update = recvUpdate()
|
|
|
|
for _, m := range update {
|
|
|
|
if m.Description.Key == got.Description.Key {
|
|
|
|
got = m
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ok1 := assert.Equal(t, want.Value, got.Result.Value)
|
|
|
|
ok2 := assert.Equal(t, want.Error, got.Result.Error)
|
|
|
|
if !ok1 || !ok2 {
|
|
|
|
require.FailNow(t, "check failed")
|
|
|
|
}
|
2023-03-31 20:26:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
update = recvUpdate()
|
|
|
|
require.Len(t, update, 3)
|
2023-06-13 12:21:06 +00:00
|
|
|
check(wantMetadata1, update[0], false)
|
2023-03-31 20:26:19 +00:00
|
|
|
// The second metadata result is not yet posted.
|
|
|
|
require.Zero(t, update[1].Result.CollectedAt)
|
|
|
|
|
|
|
|
wantMetadata2 := wantMetadata1
|
|
|
|
post("foo2", wantMetadata2)
|
|
|
|
update = recvUpdate()
|
|
|
|
require.Len(t, update, 3)
|
2023-06-13 12:21:06 +00:00
|
|
|
check(wantMetadata1, update[0], true)
|
|
|
|
check(wantMetadata2, update[1], true)
|
2023-03-31 20:26:19 +00:00
|
|
|
|
|
|
|
wantMetadata1.Error = "error"
|
|
|
|
post("foo1", wantMetadata1)
|
|
|
|
update = recvUpdate()
|
|
|
|
require.Len(t, update, 3)
|
2023-06-13 12:21:06 +00:00
|
|
|
check(wantMetadata1, update[0], true)
|
2023-03-31 20:26:19 +00:00
|
|
|
|
2023-08-24 20:18:42 +00:00
|
|
|
const maxValueLen = 2048
|
2023-03-31 20:26:19 +00:00
|
|
|
tooLongValueMetadata := wantMetadata1
|
|
|
|
tooLongValueMetadata.Value = strings.Repeat("a", maxValueLen*2)
|
|
|
|
tooLongValueMetadata.Error = ""
|
|
|
|
tooLongValueMetadata.CollectedAt = time.Now()
|
|
|
|
post("foo3", tooLongValueMetadata)
|
|
|
|
got := recvUpdate()[2]
|
2023-06-13 12:21:06 +00:00
|
|
|
for i := 0; i < 2 && len(got.Result.Value) != maxValueLen; i++ {
|
|
|
|
got = recvUpdate()[2]
|
|
|
|
}
|
2023-03-31 20:26:19 +00:00
|
|
|
require.Len(t, got.Result.Value, maxValueLen)
|
|
|
|
require.NotEmpty(t, got.Result.Error)
|
|
|
|
|
|
|
|
unknownKeyMetadata := wantMetadata1
|
|
|
|
err = agentClient.PostMetadata(ctx, "unknown", unknownKeyMetadata)
|
|
|
|
require.NoError(t, err)
|
|
|
|
}
|
2023-05-18 03:49:25 +00:00
|
|
|
|
|
|
|
func TestWorkspaceAgent_Startup(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
t.Run("OK", func(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
client := coderdtest.New(t, &coderdtest.Options{
|
|
|
|
IncludeProvisionerDaemon: true,
|
|
|
|
})
|
|
|
|
user := coderdtest.CreateFirstUser(t, client)
|
|
|
|
authToken := uuid.NewString()
|
|
|
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
|
|
|
Parse: echo.ParseComplete,
|
2023-08-25 06:10:15 +00:00
|
|
|
ProvisionPlan: echo.PlanComplete,
|
2023-05-18 03:49:25 +00:00
|
|
|
ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
|
|
|
|
})
|
|
|
|
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
|
|
|
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
|
|
|
|
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
|
|
|
|
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
|
|
|
|
|
|
|
|
agentClient := agentsdk.New(client.URL)
|
|
|
|
agentClient.SetSessionToken(authToken)
|
|
|
|
|
|
|
|
ctx := testutil.Context(t, testutil.WaitMedium)
|
|
|
|
|
2023-08-09 05:10:28 +00:00
|
|
|
var (
|
|
|
|
expectedVersion = "v1.2.3"
|
|
|
|
expectedDir = "/home/coder"
|
|
|
|
expectedSubsystems = []codersdk.AgentSubsystem{
|
|
|
|
codersdk.AgentSubsystemEnvbox,
|
|
|
|
codersdk.AgentSubsystemExectrace,
|
|
|
|
}
|
2023-05-18 03:49:25 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
err := agentClient.PostStartup(ctx, agentsdk.PostStartupRequest{
|
|
|
|
Version: expectedVersion,
|
|
|
|
ExpandedDirectory: expectedDir,
|
2023-08-09 05:10:28 +00:00
|
|
|
Subsystems: []codersdk.AgentSubsystem{
|
|
|
|
// Not sorted.
|
|
|
|
expectedSubsystems[1],
|
|
|
|
expectedSubsystems[0],
|
|
|
|
},
|
2023-05-18 03:49:25 +00:00
|
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
workspace, err = client.Workspace(ctx, workspace.ID)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
wsagent, err := client.WorkspaceAgent(ctx, workspace.LatestBuild.Resources[0].Agents[0].ID)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, expectedVersion, wsagent.Version)
|
|
|
|
require.Equal(t, expectedDir, wsagent.ExpandedDirectory)
|
2023-08-09 05:10:28 +00:00
|
|
|
// Sorted
|
|
|
|
require.Equal(t, expectedSubsystems, wsagent.Subsystems)
|
2023-05-18 03:49:25 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("InvalidSemver", func(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
client := coderdtest.New(t, &coderdtest.Options{
|
|
|
|
IncludeProvisionerDaemon: true,
|
|
|
|
})
|
|
|
|
user := coderdtest.CreateFirstUser(t, client)
|
|
|
|
authToken := uuid.NewString()
|
|
|
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
|
|
|
Parse: echo.ParseComplete,
|
2023-08-25 06:10:15 +00:00
|
|
|
ProvisionPlan: echo.PlanComplete,
|
2023-05-18 03:49:25 +00:00
|
|
|
ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
|
|
|
|
})
|
|
|
|
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
|
|
|
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
|
|
|
|
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
|
|
|
|
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
|
|
|
|
|
|
|
|
agentClient := agentsdk.New(client.URL)
|
|
|
|
agentClient.SetSessionToken(authToken)
|
|
|
|
|
|
|
|
ctx := testutil.Context(t, testutil.WaitMedium)
|
|
|
|
|
|
|
|
err := agentClient.PostStartup(ctx, agentsdk.PostStartupRequest{
|
|
|
|
Version: "1.2.3",
|
|
|
|
})
|
|
|
|
require.Error(t, err)
|
|
|
|
cerr, ok := codersdk.AsError(err)
|
|
|
|
require.True(t, ok)
|
|
|
|
require.Equal(t, http.StatusBadRequest, cerr.StatusCode())
|
|
|
|
})
|
|
|
|
}
|
2023-07-26 16:21:04 +00:00
|
|
|
|
|
|
|
// TestWorkspaceAgent_UpdatedDERP runs a real coderd server, with a real agent
|
|
|
|
// and a real client, and updates the DERP map live to ensure connections still
|
|
|
|
// work.
|
|
|
|
func TestWorkspaceAgent_UpdatedDERP(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug)
|
|
|
|
|
|
|
|
dv := coderdtest.DeploymentValues(t)
|
|
|
|
err := dv.DERP.Config.BlockDirect.Set("true")
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
client, closer, api := coderdtest.NewWithAPI(t, &coderdtest.Options{
|
|
|
|
IncludeProvisionerDaemon: true,
|
|
|
|
DeploymentValues: dv,
|
|
|
|
})
|
|
|
|
defer closer.Close()
|
|
|
|
user := coderdtest.CreateFirstUser(t, client)
|
|
|
|
|
|
|
|
// Change the DERP mapper to our custom one.
|
|
|
|
var currentDerpMap atomic.Pointer[tailcfg.DERPMap]
|
2023-08-01 15:50:43 +00:00
|
|
|
originalDerpMap, _ := tailnettest.RunDERPAndSTUN(t)
|
2023-07-26 16:21:04 +00:00
|
|
|
currentDerpMap.Store(originalDerpMap)
|
|
|
|
derpMapFn := func(_ *tailcfg.DERPMap) *tailcfg.DERPMap {
|
|
|
|
return currentDerpMap.Load().Clone()
|
|
|
|
}
|
|
|
|
api.DERPMapper.Store(&derpMapFn)
|
|
|
|
|
|
|
|
// Start workspace a workspace agent.
|
|
|
|
agentToken := uuid.NewString()
|
|
|
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
|
|
|
Parse: echo.ParseComplete,
|
2023-08-25 06:10:15 +00:00
|
|
|
ProvisionPlan: echo.PlanComplete,
|
2023-07-26 16:21:04 +00:00
|
|
|
ProvisionApply: echo.ProvisionApplyWithAgent(agentToken),
|
|
|
|
})
|
|
|
|
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
|
|
|
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
|
|
|
|
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
|
|
|
|
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
|
|
|
|
agentClient := agentsdk.New(client.URL)
|
|
|
|
agentClient.SetSessionToken(agentToken)
|
|
|
|
agentCloser := agent.New(agent.Options{
|
|
|
|
Client: agentClient,
|
|
|
|
Logger: logger.Named("agent"),
|
|
|
|
})
|
|
|
|
defer func() {
|
|
|
|
_ = agentCloser.Close()
|
|
|
|
}()
|
|
|
|
resources := coderdtest.AwaitWorkspaceAgents(t, client, workspace.ID)
|
|
|
|
agentID := resources[0].Agents[0].ID
|
|
|
|
|
|
|
|
// Connect from a client.
|
2023-08-01 15:50:43 +00:00
|
|
|
ctx := testutil.Context(t, testutil.WaitLong)
|
2023-07-26 16:21:04 +00:00
|
|
|
conn1, err := client.DialWorkspaceAgent(ctx, agentID, &codersdk.DialWorkspaceAgentOptions{
|
|
|
|
Logger: logger.Named("client1"),
|
|
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
|
|
defer conn1.Close()
|
|
|
|
ok := conn1.AwaitReachable(ctx)
|
|
|
|
require.True(t, ok)
|
|
|
|
|
|
|
|
// Change the DERP map and change the region ID.
|
|
|
|
newDerpMap, _ := tailnettest.RunDERPAndSTUN(t)
|
|
|
|
require.NotNil(t, newDerpMap)
|
|
|
|
newDerpMap.Regions[2] = newDerpMap.Regions[1]
|
|
|
|
delete(newDerpMap.Regions, 1)
|
|
|
|
newDerpMap.Regions[2].RegionID = 2
|
|
|
|
for _, node := range newDerpMap.Regions[2].Nodes {
|
|
|
|
node.RegionID = 2
|
|
|
|
}
|
|
|
|
currentDerpMap.Store(newDerpMap)
|
|
|
|
|
|
|
|
// Wait for the agent's DERP map to be updated.
|
2023-08-01 15:50:43 +00:00
|
|
|
require.Eventually(t, func() bool {
|
|
|
|
conn := agentCloser.TailnetConn()
|
|
|
|
if conn == nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
regionIDs := conn.DERPMap().RegionIDs()
|
|
|
|
return len(regionIDs) == 1 && regionIDs[0] == 2 && conn.Node().PreferredDERP == 2
|
|
|
|
}, testutil.WaitLong, testutil.IntervalFast)
|
2023-07-26 16:21:04 +00:00
|
|
|
|
|
|
|
// Wait for the DERP map to be updated on the existing client.
|
|
|
|
require.Eventually(t, func() bool {
|
|
|
|
regionIDs := conn1.Conn.DERPMap().RegionIDs()
|
|
|
|
return len(regionIDs) == 1 && regionIDs[0] == 2
|
|
|
|
}, testutil.WaitLong, testutil.IntervalFast)
|
|
|
|
|
|
|
|
// The first client should still be able to reach the agent.
|
|
|
|
ok = conn1.AwaitReachable(ctx)
|
|
|
|
require.True(t, ok)
|
|
|
|
|
|
|
|
// Connect from a second client.
|
|
|
|
conn2, err := client.DialWorkspaceAgent(ctx, agentID, &codersdk.DialWorkspaceAgentOptions{
|
|
|
|
Logger: logger.Named("client2"),
|
|
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
|
|
defer conn2.Close()
|
|
|
|
ok = conn2.AwaitReachable(ctx)
|
|
|
|
require.True(t, ok)
|
|
|
|
require.Equal(t, []int{2}, conn2.DERPMap().RegionIDs())
|
|
|
|
}
|