From 6d9e29beb119831309b5ae32387bf630c70fd394 Mon Sep 17 00:00:00 2001 From: Ethan <39577870+ethanndickson@users.noreply.github.com> Date: Mon, 22 Sep 2025 17:25:47 +1000 Subject: [PATCH] refactor(scaletest): generate user and workspace names if omitted (#19885) Relates to https://github.com/coder/internal/issues/985. Some scaletest runners would autogenerate names if they weren't supplied on the config, while others required a name be supplied, and a name was autogenerated in the CLI command handler. This PR unifies the runners to make names and emails optional on each config, and generate them in the scaletest runner if omitted. The create user runner in the PR above in the stack will do this too. --- cli/exp_scaletest.go | 41 ++--------------- scaletest/createworkspaces/config.go | 14 +++--- scaletest/createworkspaces/config_test.go | 21 ++++++--- scaletest/loadtestutil/names.go | 55 +++++++++++++++++++++++ scaletest/workspacebuild/run.go | 7 ++- 5 files changed, 82 insertions(+), 56 deletions(-) create mode 100644 scaletest/loadtestutil/names.go diff --git a/cli/exp_scaletest.go b/cli/exp_scaletest.go index 4580ff3e1b..9f032b2070 100644 --- a/cli/exp_scaletest.go +++ b/cli/exp_scaletest.go @@ -32,11 +32,11 @@ import ( "github.com/coder/coder/v2/coderd/tracing" "github.com/coder/coder/v2/codersdk" "github.com/coder/coder/v2/codersdk/workspacesdk" - "github.com/coder/coder/v2/cryptorand" "github.com/coder/coder/v2/scaletest/agentconn" "github.com/coder/coder/v2/scaletest/createworkspaces" "github.com/coder/coder/v2/scaletest/dashboard" "github.com/coder/coder/v2/scaletest/harness" + "github.com/coder/coder/v2/scaletest/loadtestutil" "github.com/coder/coder/v2/scaletest/reconnectingpty" "github.com/coder/coder/v2/scaletest/workspacebuild" "github.com/coder/coder/v2/scaletest/workspacetraffic" @@ -647,16 +647,6 @@ func (r *RootCmd) scaletestCreateWorkspaces() *serpent.Command { if useHostUser { config.User.SessionToken = client.SessionToken() - } else { - config.User.Username, config.User.Email, err = newScaleTestUser(id) - if err != nil { - return xerrors.Errorf("create scaletest username and email: %w", err) - } - } - - config.Workspace.Request.Name, err = newScaleTestWorkspace(id) - if err != nil { - return xerrors.Errorf("create scaletest workspace name: %w", err) } if runCommand != "" { @@ -1408,31 +1398,6 @@ func (r *runnableTraceWrapper) Cleanup(ctx context.Context, id string, logs io.W return c.Cleanup(ctx, id, logs) } -// newScaleTestUser returns a random username and email address that can be used -// for scale testing. The returned username is prefixed with "scaletest-" and -// the returned email address is suffixed with "@scaletest.local". -func newScaleTestUser(id string) (username string, email string, err error) { - randStr, err := cryptorand.String(8) - return fmt.Sprintf("scaletest-%s-%s", randStr, id), fmt.Sprintf("%s-%s@scaletest.local", randStr, id), err -} - -// newScaleTestWorkspace returns a random workspace name that can be used for -// scale testing. The returned workspace name is prefixed with "scaletest-" and -// suffixed with the given id. -func newScaleTestWorkspace(id string) (name string, err error) { - randStr, err := cryptorand.String(8) - return fmt.Sprintf("scaletest-%s-%s", randStr, id), err -} - -func isScaleTestUser(user codersdk.User) bool { - return strings.HasSuffix(user.Email, "@scaletest.local") -} - -func isScaleTestWorkspace(workspace codersdk.Workspace) bool { - return strings.HasPrefix(workspace.OwnerName, "scaletest-") || - strings.HasPrefix(workspace.Name, "scaletest-") -} - func getScaletestWorkspaces(ctx context.Context, client *codersdk.Client, owner, template string) ([]codersdk.Workspace, int, error) { var ( pageNumber = 0 @@ -1471,7 +1436,7 @@ func getScaletestWorkspaces(ctx context.Context, client *codersdk.Client, owner, pageWorkspaces := make([]codersdk.Workspace, 0, len(page.Workspaces)) for _, w := range page.Workspaces { - if !isScaleTestWorkspace(w) { + if !loadtestutil.IsScaleTestWorkspace(w.Name, w.OwnerName) { continue } if noOwnerAccess && w.OwnerID != me.ID { @@ -1511,7 +1476,7 @@ func getScaletestUsers(ctx context.Context, client *codersdk.Client) ([]codersdk pageUsers := make([]codersdk.User, 0, len(page.Users)) for _, u := range page.Users { - if isScaleTestUser(u) { + if loadtestutil.IsScaleTestUser(u.Username, u.Email) { pageUsers = append(pageUsers, u) } } diff --git a/scaletest/createworkspaces/config.go b/scaletest/createworkspaces/config.go index 579d9b5288..bd6a81b2ba 100644 --- a/scaletest/createworkspaces/config.go +++ b/scaletest/createworkspaces/config.go @@ -13,9 +13,9 @@ import ( type UserConfig struct { // OrganizationID is the ID of the organization to add the user to. OrganizationID uuid.UUID `json:"organization_id"` - // Username is the username of the new user. + // Username is the username of the new user. Generated if empty. Username string `json:"username"` - // Email is the email of the new user. + // Email is the email of the new user. Generated if empty. Email string `json:"email"` // SessionToken is the session token of an already existing user. If set, no // user will be created. @@ -26,12 +26,12 @@ func (c UserConfig) Validate() error { if c.OrganizationID == uuid.Nil { return xerrors.New("organization_id must not be a nil UUID") } - if c.SessionToken == "" { - if c.Username == "" { - return xerrors.New("username must be set") + if c.SessionToken != "" { + if c.Username != "" { + return xerrors.New("username must be empty when session_token is set") } - if c.Email == "" { - return xerrors.New("email must be set") + if c.Email != "" { + return xerrors.New("email must be empty when session_token is set") } } diff --git a/scaletest/createworkspaces/config_test.go b/scaletest/createworkspaces/config_test.go index 3965e9f9df..4dffd36c8b 100644 --- a/scaletest/createworkspaces/config_test.go +++ b/scaletest/createworkspaces/config_test.go @@ -43,22 +43,29 @@ func Test_UserConfig(t *testing.T) { errContains: "organization_id must not be a nil UUID", }, { - name: "NoUsername", + name: "OKSessionToken", config: createworkspaces.UserConfig{ OrganizationID: id, - Username: "", - Email: "test@test.coder.com", + SessionToken: "sometoken", }, - errContains: "username must be set", }, { - name: "NoEmail", + name: "WithSessionTokenAndUsername", config: createworkspaces.UserConfig{ OrganizationID: id, Username: "test", - Email: "", + SessionToken: "sometoken", }, - errContains: "email must be set", + errContains: "username must be empty when session_token is set", + }, + { + name: "WithSessionTokenAndEmail", + config: createworkspaces.UserConfig{ + OrganizationID: id, + Email: "test@test.coder.com", + SessionToken: "sometoken", + }, + errContains: "email must be empty when session_token is set", }, } diff --git a/scaletest/loadtestutil/names.go b/scaletest/loadtestutil/names.go new file mode 100644 index 0000000000..f29ded1578 --- /dev/null +++ b/scaletest/loadtestutil/names.go @@ -0,0 +1,55 @@ +package loadtestutil + +import ( + "fmt" + "strings" + + "github.com/coder/coder/v2/cryptorand" +) + +const ( + // Prefix for all scaletest resources (users and workspaces) + ScaleTestPrefix = "scaletest" + + // Email domain for scaletest users + EmailDomain = "@scaletest.local" + + DefaultRandLength = 8 +) + +// GenerateUserIdentifier generates a username and email for scale testing. +// The username follows the pattern: scaletest-- +// The email follows the pattern: -@scaletest.local +func GenerateUserIdentifier(id string) (username, email string, err error) { + randStr, err := cryptorand.String(DefaultRandLength) + if err != nil { + return "", "", err + } + + username = fmt.Sprintf("%s-%s-%s", ScaleTestPrefix, randStr, id) + email = fmt.Sprintf("%s-%s%s", randStr, id, EmailDomain) + return username, email, nil +} + +// GenerateWorkspaceName generates a workspace name for scale testing. +// The workspace name follows the pattern: scaletest-- +func GenerateWorkspaceName(id string) (name string, err error) { + randStr, err := cryptorand.String(DefaultRandLength) + if err != nil { + return "", err + } + + return fmt.Sprintf("%s-%s-%s", ScaleTestPrefix, randStr, id), nil +} + +// IsScaleTestUser checks if a username indicates it was created for scale testing. +func IsScaleTestUser(username, email string) bool { + return strings.HasPrefix(username, ScaleTestPrefix+"-") || + strings.HasSuffix(email, EmailDomain) +} + +// IsScaleTestWorkspace checks if a workspace name indicates it was created for scale testing. +func IsScaleTestWorkspace(workspaceName, ownerName string) bool { + return strings.HasPrefix(workspaceName, ScaleTestPrefix+"-") || + strings.HasPrefix(ownerName, ScaleTestPrefix+"-") +} diff --git a/scaletest/workspacebuild/run.go b/scaletest/workspacebuild/run.go index 3b369a4e48..5b8c2c3237 100644 --- a/scaletest/workspacebuild/run.go +++ b/scaletest/workspacebuild/run.go @@ -15,7 +15,6 @@ import ( "github.com/coder/coder/v2/coderd/tracing" "github.com/coder/coder/v2/codersdk" - "github.com/coder/coder/v2/cryptorand" "github.com/coder/coder/v2/scaletest/harness" "github.com/coder/coder/v2/scaletest/loadtestutil" ) @@ -40,7 +39,7 @@ func NewRunner(client *codersdk.Client, cfg Config) *Runner { } // Run implements Runnable. -func (r *Runner) Run(ctx context.Context, _ string, logs io.Writer) error { +func (r *Runner) Run(ctx context.Context, id string, logs io.Writer) error { ctx, span := tracing.StartSpan(ctx) defer span.End() @@ -51,11 +50,11 @@ func (r *Runner) Run(ctx context.Context, _ string, logs io.Writer) error { req := r.cfg.Request if req.Name == "" { - randName, err := cryptorand.HexString(8) + randName, err := loadtestutil.GenerateWorkspaceName(id) if err != nil { return xerrors.Errorf("generate random name for workspace: %w", err) } - req.Name = "test-" + randName + req.Name = randName } workspace, err := r.client.CreateWorkspace(ctx, r.cfg.OrganizationID, r.cfg.UserID, req)