Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 24 additions & 18 deletions hatchery/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,24 +178,30 @@ type HatcheryConfig struct {
DefaultPayModel PayModel `json:"default-pay-model"`
HashedUsernames bool `json:"hashed-usernames"`
// DisableLocalWS bool `json:"disable-local-ws"`
SkipNodeSelector bool `json:"skip-node-selector"`
UseInteralServicesURL bool `json:"use-internal-services-url"`
PayModels []PayModel `json:"pay-models"`
PayModelsDynamodbTable string `json:"pay-models-dynamodb-table"`
PayModelsDynamodbArn string `json:"pay-models-dynamodb-arn"`
LicenseUserMapsTable string `json:"license-user-maps-dynamodb-table"`
LicenseUserMapsGSI string `json:"license-user-maps-global-secondary-index"`
License LicenseInfo `json:"license"`
SubDir string `json:"sub-dir"`
Containers []Container `json:"containers"`
UserVolumeSize string `json:"user-volume-size"`
Sidecar SidecarContainer `json:"sidecar"`
MoreConfigs []AppConfigInfo `json:"more-configs"`
PrismaConfig PrismaConfig `json:"prisma"`
S3Config S3Config `json:"s3-config"`
NextflowGlobalConfig NextflowGlobalConfig `json:"nextflow-global"`
Pricing Pricing `json:"pricing"`
SharedWorkspace SharedWorkspaceConfig `json:"shared-workspace"`
SkipNodeSelector bool `json:"skip-node-selector"`
UseInteralServicesURL bool `json:"use-internal-services-url"`
PayModels []PayModel `json:"pay-models"`
PayModelsDynamodbTable string `json:"pay-models-dynamodb-table"`
PayModelsDynamodbArn string `json:"pay-models-dynamodb-arn"`
// AutoSelectSinglePayModel treats a user's only active pay model as their
// current one, even when current_pay_model is false in DynamoDB. Terminating
// a workspace clears that flag for every row (see resetCurrentPaymodel), so on
// commons where users never pick a pay model explicitly this otherwise leaves
// them unable to launch until someone flips the flag by hand.
AutoSelectSinglePayModel bool `json:"auto-select-single-paymodel"`
LicenseUserMapsTable string `json:"license-user-maps-dynamodb-table"`
LicenseUserMapsGSI string `json:"license-user-maps-global-secondary-index"`
License LicenseInfo `json:"license"`
SubDir string `json:"sub-dir"`
Containers []Container `json:"containers"`
UserVolumeSize string `json:"user-volume-size"`
Sidecar SidecarContainer `json:"sidecar"`
MoreConfigs []AppConfigInfo `json:"more-configs"`
PrismaConfig PrismaConfig `json:"prisma"`
S3Config S3Config `json:"s3-config"`
NextflowGlobalConfig NextflowGlobalConfig `json:"nextflow-global"`
Pricing Pricing `json:"pricing"`
SharedWorkspace SharedWorkspaceConfig `json:"shared-workspace"`
// OIDCProviderARN is the full ARN of the EKS cluster's OIDC provider, used to
// build IRSA trust policies. It is a cluster-level property shared by the
// shared-workspace and squashfs software-library features.
Expand Down
7 changes: 7 additions & 0 deletions hatchery/paymodels.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,13 @@ var getCurrentPayModel = func(userName string) (result *PayModel, err error) {
activePayModels, _ := payModelsFromDatabase(userName, false)

if activePayModels != nil && len(*activePayModels) > 0 {
// Terminating a workspace resets current_pay_model to false on every
// row, so a user with exactly one pay model is left with nothing
// current and cannot launch until they re-select it. Where that
// choice is not surfaced to users, treat the only option as current.
if Config.Config.AutoSelectSinglePayModel && len(*activePayModels) == 1 {
return &(*activePayModels)[0], nil
}
// return nil since there is no current paymodel set by the user
return nil, nil
}
Expand Down
68 changes: 68 additions & 0 deletions hatchery/paymodels_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,13 @@ func Test_GetCurrentPayModel(t *testing.T) {
},
}

configWithAutoSelectSinglePayModel := &FullHatcheryConfig{
Config: HatcheryConfig{
PayModelsDynamodbTable: "random_non_empty_string",
AutoSelectSinglePayModel: true,
},
}

defaultPayModelForTest := &PayModel{
Name: "Trial Workspace",
Local: true,
Expand Down Expand Up @@ -146,6 +153,67 @@ func Test_GetCurrentPayModel(t *testing.T) {
mockPayModelsFromDB: []PayModel{},
mockDefaultPaymodel: defaultPayModelForTest,
},
{
// A terminate resets current_pay_model on the user's only row. With
// the flag on, that row is still returned so the user can launch.
name: "AutoSelectSingle_SingleActiveNotCurrent",
want: &PayModel{
Id: "#1",
Name: "Trial Workspace",
CurrentPayModel: false,
Status: "active",
},
mockConfig: configWithAutoSelectSinglePayModel,
mockCurrentPayModelFromDB: []PayModel{},
mockPayModelsFromDB: []PayModel{
{
Id: "#1",
Name: "Trial Workspace",
CurrentPayModel: false,
Status: "active",
},
},
mockDefaultPaymodel: defaultPayModelForTest,
},
{
// With more than one option the user genuinely has a choice to make,
// so the flag must not pick one for them.
name: "AutoSelectSingle_MultipleActiveNotCurrent",
want: nil,
mockConfig: configWithAutoSelectSinglePayModel,
mockCurrentPayModelFromDB: []PayModel{},
mockPayModelsFromDB: []PayModel{
{
Id: "#1",
Name: "Trial Workspace",
CurrentPayModel: false,
Status: "active",
},
{
Id: "#2",
Name: "Direct Pay",
CurrentPayModel: false,
Status: "active",
},
},
mockDefaultPaymodel: defaultPayModelForTest,
},
{
// Same single-row shape as above but flag off: unchanged behaviour.
name: "AutoSelectSingleDisabled_SingleActiveNotCurrent",
want: nil,
mockConfig: configWithDbTable,
mockCurrentPayModelFromDB: []PayModel{},
mockPayModelsFromDB: []PayModel{
{
Id: "#1",
Name: "Trial Workspace",
CurrentPayModel: false,
Status: "active",
},
},
mockDefaultPaymodel: defaultPayModelForTest,
},
}

for _, testcase := range testCases {
Expand Down
Loading