mirror of
https://github.com/coder/coder.git
synced 2025-07-06 15:41:45 +00:00
This commit reverts some of the changes in #8029 and implements an alternative method of keeping track of when the startup script has ended and there will be no more logs. This is achieved by adding new agent fields for tracking when the agent enters the "starting" and "ready"/"start_error" lifecycle states. The timestamps simplify logic since we don't need understand if the current state is before or after the state we're interested in. They can also be used to show data like how long the startup script took to execute. This also allowed us to remove the EOF field from the logs as the implementation was problematic when we returned the EOF log entry in the response since requesting _after_ that ID would give no logs and the API would thus lose track of EOF.
315 lines
8.6 KiB
Go
315 lines
8.6 KiB
Go
//go:build linux
|
|
|
|
package database_test
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/coder/coder/coderd/database"
|
|
"github.com/coder/coder/coderd/database/dbgen"
|
|
"github.com/coder/coder/coderd/database/migrations"
|
|
"github.com/coder/coder/testutil"
|
|
)
|
|
|
|
func TestGetDeploymentWorkspaceAgentStats(t *testing.T) {
|
|
t.Parallel()
|
|
if testing.Short() {
|
|
t.SkipNow()
|
|
}
|
|
t.Run("Aggregates", func(t *testing.T) {
|
|
t.Parallel()
|
|
sqlDB := testSQLDB(t)
|
|
err := migrations.Up(sqlDB)
|
|
require.NoError(t, err)
|
|
db := database.New(sqlDB)
|
|
ctx := context.Background()
|
|
dbgen.WorkspaceAgentStat(t, db, database.WorkspaceAgentStat{
|
|
TxBytes: 1,
|
|
RxBytes: 1,
|
|
ConnectionMedianLatencyMS: 1,
|
|
SessionCountVSCode: 1,
|
|
})
|
|
dbgen.WorkspaceAgentStat(t, db, database.WorkspaceAgentStat{
|
|
TxBytes: 1,
|
|
RxBytes: 1,
|
|
ConnectionMedianLatencyMS: 2,
|
|
SessionCountVSCode: 1,
|
|
})
|
|
stats, err := db.GetDeploymentWorkspaceAgentStats(ctx, database.Now().Add(-time.Hour))
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, int64(2), stats.WorkspaceTxBytes)
|
|
require.Equal(t, int64(2), stats.WorkspaceRxBytes)
|
|
require.Equal(t, 1.5, stats.WorkspaceConnectionLatency50)
|
|
require.Equal(t, 1.95, stats.WorkspaceConnectionLatency95)
|
|
require.Equal(t, int64(2), stats.SessionCountVSCode)
|
|
})
|
|
|
|
t.Run("GroupsByAgentID", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
sqlDB := testSQLDB(t)
|
|
err := migrations.Up(sqlDB)
|
|
require.NoError(t, err)
|
|
db := database.New(sqlDB)
|
|
ctx := context.Background()
|
|
agentID := uuid.New()
|
|
insertTime := database.Now()
|
|
dbgen.WorkspaceAgentStat(t, db, database.WorkspaceAgentStat{
|
|
CreatedAt: insertTime.Add(-time.Second),
|
|
AgentID: agentID,
|
|
TxBytes: 1,
|
|
RxBytes: 1,
|
|
ConnectionMedianLatencyMS: 1,
|
|
SessionCountVSCode: 1,
|
|
})
|
|
dbgen.WorkspaceAgentStat(t, db, database.WorkspaceAgentStat{
|
|
// Ensure this stat is newer!
|
|
CreatedAt: insertTime,
|
|
AgentID: agentID,
|
|
TxBytes: 1,
|
|
RxBytes: 1,
|
|
ConnectionMedianLatencyMS: 2,
|
|
SessionCountVSCode: 1,
|
|
})
|
|
stats, err := db.GetDeploymentWorkspaceAgentStats(ctx, database.Now().Add(-time.Hour))
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, int64(2), stats.WorkspaceTxBytes)
|
|
require.Equal(t, int64(2), stats.WorkspaceRxBytes)
|
|
require.Equal(t, 1.5, stats.WorkspaceConnectionLatency50)
|
|
require.Equal(t, 1.95, stats.WorkspaceConnectionLatency95)
|
|
require.Equal(t, int64(1), stats.SessionCountVSCode)
|
|
})
|
|
}
|
|
|
|
func TestInsertWorkspaceAgentStartupLogs(t *testing.T) {
|
|
t.Parallel()
|
|
if testing.Short() {
|
|
t.SkipNow()
|
|
}
|
|
sqlDB := testSQLDB(t)
|
|
ctx := context.Background()
|
|
err := migrations.Up(sqlDB)
|
|
require.NoError(t, err)
|
|
db := database.New(sqlDB)
|
|
org := dbgen.Organization(t, db, database.Organization{})
|
|
job := dbgen.ProvisionerJob(t, db, database.ProvisionerJob{
|
|
OrganizationID: org.ID,
|
|
})
|
|
resource := dbgen.WorkspaceResource(t, db, database.WorkspaceResource{
|
|
JobID: job.ID,
|
|
})
|
|
agent := dbgen.WorkspaceAgent(t, db, database.WorkspaceAgent{
|
|
ResourceID: resource.ID,
|
|
})
|
|
logs, err := db.InsertWorkspaceAgentStartupLogs(ctx, database.InsertWorkspaceAgentStartupLogsParams{
|
|
AgentID: agent.ID,
|
|
CreatedAt: []time.Time{database.Now()},
|
|
Output: []string{"first"},
|
|
Level: []database.LogLevel{database.LogLevelInfo},
|
|
// 1 MB is the max
|
|
OutputLength: 1 << 20,
|
|
})
|
|
require.NoError(t, err)
|
|
require.Equal(t, int64(1), logs[0].ID)
|
|
|
|
_, err = db.InsertWorkspaceAgentStartupLogs(ctx, database.InsertWorkspaceAgentStartupLogsParams{
|
|
AgentID: agent.ID,
|
|
CreatedAt: []time.Time{database.Now()},
|
|
Output: []string{"second"},
|
|
Level: []database.LogLevel{database.LogLevelInfo},
|
|
OutputLength: 1,
|
|
})
|
|
require.True(t, database.IsStartupLogsLimitError(err))
|
|
}
|
|
|
|
func TestProxyByHostname(t *testing.T) {
|
|
t.Parallel()
|
|
if testing.Short() {
|
|
t.SkipNow()
|
|
}
|
|
sqlDB := testSQLDB(t)
|
|
err := migrations.Up(sqlDB)
|
|
require.NoError(t, err)
|
|
db := database.New(sqlDB)
|
|
|
|
// Insert a bunch of different proxies.
|
|
proxies := []struct {
|
|
name string
|
|
accessURL string
|
|
wildcardHostname string
|
|
}{
|
|
{
|
|
name: "one",
|
|
accessURL: "https://one.coder.com",
|
|
wildcardHostname: "*.wildcard.one.coder.com",
|
|
},
|
|
{
|
|
name: "two",
|
|
accessURL: "https://two.coder.com",
|
|
wildcardHostname: "*--suffix.two.coder.com",
|
|
},
|
|
}
|
|
for _, p := range proxies {
|
|
dbgen.WorkspaceProxy(t, db, database.WorkspaceProxy{
|
|
Name: p.name,
|
|
Url: p.accessURL,
|
|
WildcardHostname: p.wildcardHostname,
|
|
})
|
|
}
|
|
|
|
cases := []struct {
|
|
name string
|
|
testHostname string
|
|
allowAccessURL bool
|
|
allowWildcardHost bool
|
|
matchProxyName string
|
|
}{
|
|
{
|
|
name: "NoMatch",
|
|
testHostname: "test.com",
|
|
allowAccessURL: true,
|
|
allowWildcardHost: true,
|
|
matchProxyName: "",
|
|
},
|
|
{
|
|
name: "MatchAccessURL",
|
|
testHostname: "one.coder.com",
|
|
allowAccessURL: true,
|
|
allowWildcardHost: true,
|
|
matchProxyName: "one",
|
|
},
|
|
{
|
|
name: "MatchWildcard",
|
|
testHostname: "something.wildcard.one.coder.com",
|
|
allowAccessURL: true,
|
|
allowWildcardHost: true,
|
|
matchProxyName: "one",
|
|
},
|
|
{
|
|
name: "MatchSuffix",
|
|
testHostname: "something--suffix.two.coder.com",
|
|
allowAccessURL: true,
|
|
allowWildcardHost: true,
|
|
matchProxyName: "two",
|
|
},
|
|
{
|
|
name: "ValidateHostname/1",
|
|
testHostname: ".*ne.coder.com",
|
|
allowAccessURL: true,
|
|
allowWildcardHost: true,
|
|
matchProxyName: "",
|
|
},
|
|
{
|
|
name: "ValidateHostname/2",
|
|
testHostname: "https://one.coder.com",
|
|
allowAccessURL: true,
|
|
allowWildcardHost: true,
|
|
matchProxyName: "",
|
|
},
|
|
{
|
|
name: "ValidateHostname/3",
|
|
testHostname: "one.coder.com:8080/hello",
|
|
allowAccessURL: true,
|
|
allowWildcardHost: true,
|
|
matchProxyName: "",
|
|
},
|
|
{
|
|
name: "IgnoreAccessURLMatch",
|
|
testHostname: "one.coder.com",
|
|
allowAccessURL: false,
|
|
allowWildcardHost: true,
|
|
matchProxyName: "",
|
|
},
|
|
{
|
|
name: "IgnoreWildcardMatch",
|
|
testHostname: "hi.wildcard.one.coder.com",
|
|
allowAccessURL: true,
|
|
allowWildcardHost: false,
|
|
matchProxyName: "",
|
|
},
|
|
}
|
|
|
|
for _, c := range cases {
|
|
c := c
|
|
t.Run(c.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
proxy, err := db.GetWorkspaceProxyByHostname(context.Background(), database.GetWorkspaceProxyByHostnameParams{
|
|
Hostname: c.testHostname,
|
|
AllowAccessUrl: c.allowAccessURL,
|
|
AllowWildcardHostname: c.allowWildcardHost,
|
|
})
|
|
if c.matchProxyName == "" {
|
|
require.ErrorIs(t, err, sql.ErrNoRows)
|
|
require.Empty(t, proxy)
|
|
} else {
|
|
require.NoError(t, err)
|
|
require.NotEmpty(t, proxy)
|
|
require.Equal(t, c.matchProxyName, proxy.Name)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDefaultProxy(t *testing.T) {
|
|
t.Parallel()
|
|
if testing.Short() {
|
|
t.SkipNow()
|
|
}
|
|
sqlDB := testSQLDB(t)
|
|
err := migrations.Up(sqlDB)
|
|
require.NoError(t, err)
|
|
db := database.New(sqlDB)
|
|
|
|
ctx := testutil.Context(t, testutil.WaitLong)
|
|
depID := uuid.NewString()
|
|
err = db.InsertDeploymentID(ctx, depID)
|
|
require.NoError(t, err, "insert deployment id")
|
|
|
|
// Fetch empty proxy values
|
|
defProxy, err := db.GetDefaultProxyConfig(ctx)
|
|
require.NoError(t, err, "get def proxy")
|
|
|
|
require.Equal(t, defProxy.DisplayName, "Default")
|
|
require.Equal(t, defProxy.IconUrl, "/emojis/1f3e1.png")
|
|
|
|
// Set the proxy values
|
|
args := database.UpsertDefaultProxyParams{
|
|
DisplayName: "displayname",
|
|
IconUrl: "/icon.png",
|
|
}
|
|
err = db.UpsertDefaultProxy(ctx, args)
|
|
require.NoError(t, err, "insert def proxy")
|
|
|
|
defProxy, err = db.GetDefaultProxyConfig(ctx)
|
|
require.NoError(t, err, "get def proxy")
|
|
require.Equal(t, defProxy.DisplayName, args.DisplayName)
|
|
require.Equal(t, defProxy.IconUrl, args.IconUrl)
|
|
|
|
// Upsert values
|
|
args = database.UpsertDefaultProxyParams{
|
|
DisplayName: "newdisplayname",
|
|
IconUrl: "/newicon.png",
|
|
}
|
|
err = db.UpsertDefaultProxy(ctx, args)
|
|
require.NoError(t, err, "upsert def proxy")
|
|
|
|
defProxy, err = db.GetDefaultProxyConfig(ctx)
|
|
require.NoError(t, err, "get def proxy")
|
|
require.Equal(t, defProxy.DisplayName, args.DisplayName)
|
|
require.Equal(t, defProxy.IconUrl, args.IconUrl)
|
|
|
|
// Ensure other site configs are the same
|
|
found, err := db.GetDeploymentID(ctx)
|
|
require.NoError(t, err, "get deployment id")
|
|
require.Equal(t, depID, found)
|
|
}
|