Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

feat: update workspace deadline when workspace ttl updated#2165

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes fromall commits
Commits
Show all changes
20 commits
Select commitHold shift + click to select a range
5c443de
RED: update unit test with new desired behaviour
johnstcnJun 7, 2022
c2127c1
cli: warn user upon ttl-triggered workspace shutdown
johnstcnJun 7, 2022
07320a4
GREEN: do the thing
johnstcnJun 8, 2022
7846dd8
REFACTOR: coderd: putWorkspaceTTL: wrap in tx
johnstcnJun 8, 2022
6d7187d
update unit tests
johnstcnJun 8, 2022
daa9a5f
feat: WorkspaceScheduleForm: show shutdown time when editing workspac…
johnstcnJun 8, 2022
fe79c0c
make it prettier
johnstcnJun 8, 2022
85da57b
hopefully fix unit tests
johnstcnJun 9, 2022
8c38a74
Merge remote-tracking branch 'origin/main' into cj/1783/updating-work…
johnstcnJun 9, 2022
a4c07df
Address PR comments
johnstcnJun 9, 2022
582873a
Merge origin/main
greyscaledJun 9, 2022
9a7c623
chore: WorkspaceScheduleForm: add unit tests
johnstcnJun 9, 2022
0278a93
WorkspaceScheduleForm: refactor ttlShutdownAt
johnstcnJun 9, 2022
a372dce
apply suggestions from code review
johnstcnJun 9, 2022
ac1d29a
fixup! apply suggestions from code review
johnstcnJun 9, 2022
23b474c
yarn lint; more pr comments
johnstcnJun 9, 2022
de28477
bump ci
johnstcnJun 9, 2022
a52d815
Merge remote-tracking branch 'origin/main' into cj/1783/updating-work…
johnstcnJun 9, 2022
fd86474
mind your language
johnstcnJun 9, 2022
2b9f93c
fumpt
johnstcnJun 9, 2022
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
41 changes: 41 additions & 0 deletionscli/ttl.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
package cli

import (
"errors"
"fmt"
"time"

"github.com/spf13/cobra"
"golang.org/x/xerrors"

"github.com/coder/coder/cli/cliui"
"github.com/coder/coder/codersdk"
)

Expand DownExpand Up@@ -89,6 +91,30 @@ func ttlset() *cobra.Command {
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "warning: ttl rounded down to %s\n", truncated)
}

if changed, newDeadline := changedNewDeadline(workspace, truncated); changed {
// For the purposes of the user, "less than a minute" is essentially the same as "immediately".
timeRemaining := time.Until(newDeadline).Truncate(time.Minute)
humanRemaining := "in " + timeRemaining.String()
if timeRemaining <= 0 {
humanRemaining = "immediately"
}
_, err = cliui.Prompt(cmd, cliui.PromptOptions{
Text: fmt.Sprintf(
"Workspace %q will be stopped %s. Are you sure?",
workspace.Name,
humanRemaining,
),
Comment on lines +97 to +106
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

praise: wonderful strings!

Default: "yes",
IsConfirm: true,
Comment on lines +101 to +108
Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

review: we can skip the prompt iftimeRemaining >= SOME_CUTOFF.

})
if err != nil {
if errors.Is(err, cliui.Canceled) {
return nil
}
return err
}
}

millis := truncated.Milliseconds()
if err = client.UpdateWorkspaceTTL(cmd.Context(), workspace.ID, codersdk.UpdateWorkspaceTTLRequest{
TTLMillis: &millis,
Expand DownExpand Up@@ -131,3 +157,18 @@ func ttlunset() *cobra.Command {
},
}
}

func changedNewDeadline(ws codersdk.Workspace, newTTL time.Duration) (changed bool, newDeadline time.Time) {
if ws.LatestBuild.Transition != codersdk.WorkspaceTransitionStart {
// not running
return false, newDeadline
}

if ws.LatestBuild.Job.CompletedAt == nil {
// still building
return false, newDeadline
}

newDeadline = ws.LatestBuild.Job.CompletedAt.Add(newTTL)
return true, newDeadline
}
77 changes: 53 additions & 24 deletionscli/ttl_test.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,16 +3,19 @@ package cli_test
import (
"bytes"
"context"
"fmt"
"strings"
"testing"
"time"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/coder/coder/cli/clitest"
"github.com/coder/coder/coderd/coderdtest"
"github.com/coder/coder/coderd/util/ptr"
"github.com/coder/coder/codersdk"
"github.com/coder/coder/pty/ptytest"
)

func TestTTL(t *testing.T) {
Expand All@@ -22,33 +25,29 @@ func TestTTL(t *testing.T) {
t.Parallel()

var (
ctx = context.Background()
client = coderdtest.New(t, &coderdtest.Options{IncludeProvisionerD: true})
user = coderdtest.CreateFirstUser(t, client)
version = coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
_ = coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
project = coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
workspace = coderdtest.CreateWorkspace(t, client, user.OrganizationID, project.ID)
template = coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
ttl = 7*time.Hour + 30*time.Minute + 30*time.Second
workspace = coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID, func(cwr *codersdk.CreateWorkspaceRequest) {
cwr.TTLMillis = ptr.Ref(ttl.Milliseconds())
})
cmdArgs = []string{"ttl", "show", workspace.Name}
ttl = 8*time.Hour + 30*time.Minute + 30*time.Second
stdoutBuf = &bytes.Buffer{}
)

err := client.UpdateWorkspaceTTL(ctx, workspace.ID, codersdk.UpdateWorkspaceTTLRequest{
TTLMillis: ptr.Ref(ttl.Milliseconds()),
})
require.NoError(t, err)

cmd, root := clitest.New(t, cmdArgs...)
clitest.SetupConfig(t, client, root)
cmd.SetOut(stdoutBuf)

err = cmd.Execute()
err:= cmd.Execute()
require.NoError(t, err, "unexpected error")
require.Equal(t, ttl.Truncate(time.Minute).String(), strings.TrimSpace(stdoutBuf.String()))
})

t.Run("SetUnsetOK", func(t *testing.T) {
t.Run("UnsetOK", func(t *testing.T) {
t.Parallel()

var (
Expand All@@ -58,9 +57,11 @@ func TestTTL(t *testing.T) {
version = coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
_ = coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
project = coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
workspace = coderdtest.CreateWorkspace(t, client, user.OrganizationID, project.ID)
ttl = 8*time.Hour + 30*time.Minute + 30*time.Second
cmdArgs = []string{"ttl", "set", workspace.Name, ttl.String()}
workspace = coderdtest.CreateWorkspace(t, client, user.OrganizationID, project.ID, func(cwr *codersdk.CreateWorkspaceRequest) {
cwr.TTLMillis = ptr.Ref(ttl.Milliseconds())
})
cmdArgs = []string{"ttl", "unset", workspace.Name}
stdoutBuf = &bytes.Buffer{}
)

Expand All@@ -71,24 +72,52 @@ func TestTTL(t *testing.T) {
err := cmd.Execute()
require.NoError(t, err, "unexpected error")

// Ensure ttlupdated
// Ensure ttlunset
updated, err := client.Workspace(ctx, workspace.ID)
require.NoError(t, err, "fetch updated workspace")
require.Equal(t,ttl.Truncate(time.Minute), time.Duration(*updated.TTLMillis)*time.Millisecond)
require.Contains(t, stdoutBuf.String(), "warning: ttl rounded down")
require.Nil(t, updated.TTLMillis, "expected ttl to not be set")
})

// unset schedule
cmd, root = clitest.New(t, "ttl", "unset", workspace.Name)
clitest.SetupConfig(t, client, root)
cmd.SetOut(stdoutBuf)
t.Run("SetOK", func(t *testing.T) {
t.Parallel()

err = cmd.Execute()
require.NoError(t, err, "unexpected error")
var (
ctx = context.Background()
client = coderdtest.New(t, &coderdtest.Options{IncludeProvisionerD: true})
user = coderdtest.CreateFirstUser(t, client)
version = coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
_ = coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
project = coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
ttl = 8*time.Hour + 30*time.Minute + 30*time.Second
workspace = coderdtest.CreateWorkspace(t, client, user.OrganizationID, project.ID, func(cwr *codersdk.CreateWorkspaceRequest) {
cwr.TTLMillis = ptr.Ref(ttl.Milliseconds())
})
_ = coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
cmdArgs = []string{"ttl", "set", workspace.Name, ttl.String()}
done = make(chan struct{})
)

cmd, root := clitest.New(t, cmdArgs...)
clitest.SetupConfig(t, client, root)
pty := ptytest.New(t)
cmd.SetIn(pty.Input())
cmd.SetOut(pty.Output())

go func() {
defer close(done)
err := cmd.Execute()
assert.NoError(t, err, "unexpected error")
}()

pty.ExpectMatch(fmt.Sprintf("warning: ttl rounded down to %s", ttl.Truncate(time.Minute)))
pty.ExpectMatch(fmt.Sprintf("Workspace %q will be stopped in 8h29m0s. Are you sure?", workspace.Name))
pty.WriteLine("yes")
// Ensure ttl updated
updated, err = client.Workspace(ctx, workspace.ID)
updated, err:= client.Workspace(ctx, workspace.ID)
require.NoError(t, err, "fetch updated workspace")
require.Nil(t, updated.TTLMillis, "expected ttl to not be set")
require.Equal(t, ttl.Truncate(time.Minute), time.Duration(*updated.TTLMillis)*time.Millisecond)

<-done
})

t.Run("ZeroInvalid", func(t *testing.T) {
Expand Down
33 changes: 28 additions & 5 deletionscoderd/autobuild/executor/lifecycle_executor_test.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -440,18 +440,41 @@ func TestExecutorWorkspaceAutostopNoWaitChangedMyMind(t *testing.T) {
err := client.UpdateWorkspaceTTL(ctx, workspace.ID, codersdk.UpdateWorkspaceTTLRequest{TTLMillis: nil})
require.NoError(t, err)

// When: the autobuild executor ticks after the deadline
// Then: the deadline should be the zero value
updated := coderdtest.MustWorkspace(t, client, workspace.ID)
assert.Zero(t, updated.LatestBuild.Deadline)

// When: the autobuild executor ticks after the original deadline
go func() {
tickCh <- workspace.LatestBuild.Deadline.Add(time.Minute)
close(tickCh)
}()

// Then: the workspace shouldstill stop - sorry!
// Then: the workspace shouldnot stop
stats := <-statsCh
assert.NoError(t, stats.Error)
assert.Len(t, stats.Transitions, 0)

// Given: the user changes their mind again and wants to enable auto-stop
newTTL := 8 * time.Hour
expectedDeadline := workspace.LatestBuild.UpdatedAt.Add(newTTL)
err = client.UpdateWorkspaceTTL(ctx, workspace.ID, codersdk.UpdateWorkspaceTTLRequest{TTLMillis: ptr.Ref(newTTL.Milliseconds())})
require.NoError(t, err)

// Then: the deadline should be updated based on the TTL
updated = coderdtest.MustWorkspace(t, client, workspace.ID)
assert.WithinDuration(t, expectedDeadline, updated.LatestBuild.Deadline, time.Minute)

// When: the relentless onward march of time continues
go func() {
tickCh <- workspace.LatestBuild.Deadline.Add(newTTL + time.Minute)
close(tickCh)
}()

// Then: the workspace should stop
stats = <-statsCh
assert.NoError(t, stats.Error)
assert.Len(t, stats.Transitions, 1)
assert.Contains(t, stats.Transitions, workspace.ID)
assert.Equal(t, database.WorkspaceTransitionStop, stats.Transitions[workspace.ID])
assert.Equal(t, stats.Transitions[workspace.ID], database.WorkspaceTransitionStop)
}

func TestExecutorAutostartMultipleOK(t *testing.T) {
Expand Down
48 changes: 44 additions & 4 deletionscoderd/workspaces.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -566,17 +566,57 @@ func (api *API) putWorkspaceTTL(rw http.ResponseWriter, r *http.Request) {
return
}

err = api.Database.UpdateWorkspaceTTL(r.Context(), database.UpdateWorkspaceTTLParams{
ID: workspace.ID,
Ttl: dbTTL,
err = api.Database.InTx(func(s database.Store) error {
if err := s.UpdateWorkspaceTTL(r.Context(), database.UpdateWorkspaceTTLParams{
ID: workspace.ID,
Ttl: dbTTL,
}); err != nil {
return xerrors.Errorf("update workspace TTL: %w", err)
}

// Also extend the workspace deadline if the workspace is running
latestBuild, err := s.GetLatestWorkspaceBuildByWorkspaceID(r.Context(), workspace.ID)
if err != nil {
return xerrors.Errorf("get latest workspace build: %w", err)
}

if latestBuild.Transition != database.WorkspaceTransitionStart {
return nil // nothing to do
}

if latestBuild.UpdatedAt.IsZero() {
// Build in progress; provisionerd should update with the new TTL.
return nil
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Question: What happens if a user starts a workspace with one TTL, but tries to update the TTL before the build is complete? As a user, I'd expect the new TTL to be in-effect once the build has completed or receive an error that TTL cannot be updated (yet).

greyscaled reacted with thumbs up emoji
Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

See

workspaceDeadline=now.Add(time.Duration(workspace.Ttl.Int64))
-- we fetch the workspace TTL in a transaction and update the deadline upon finishing the provisioner job.

  • If the user updates the TTL first, then the updated TTL will be used by provisionerd.
  • If provisionerd updates the TTL first, then the deadline should be updated.

mafredri and greyscaled reacted with thumbs up emoji
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Ooh yeah that makes sense, thanks for clarifying!

greyscaled reacted with thumbs up emoji
}

var newDeadline time.Time
if dbTTL.Valid {
newDeadline = latestBuild.UpdatedAt.Add(time.Duration(dbTTL.Int64))
}

if err := s.UpdateWorkspaceBuildByID(
r.Context(),
database.UpdateWorkspaceBuildByIDParams{
ID: latestBuild.ID,
UpdatedAt: latestBuild.UpdatedAt,
ProvisionerState: latestBuild.ProvisionerState,
Deadline: newDeadline,
},
); err != nil {
return xerrors.Errorf("update workspace deadline: %w", err)
}
return nil
})

if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
Message: "Internal errorupdating workspaceTTL.",
Message: "Errorupdating workspacetime until shutdown!",
Detail: err.Error(),
})
return
}

httpapi.Write(rw, http.StatusOK, nil)
}

func (api *API) putExtendWorkspace(rw http.ResponseWriter, r *http.Request) {
Expand Down
Loading

[8]ページ先頭

©2009-2025 Movatter.jp