@@ -17,12 +17,15 @@ import (
17
17
"github.com/coder/coder/v2/coderd/autobuild"
18
18
"github.com/coder/coder/v2/coderd/coderdtest"
19
19
"github.com/coder/coder/v2/coderd/database"
20
+ "github.com/coder/coder/v2/coderd/database/dbtestutil"
21
+ "github.com/coder/coder/v2/coderd/database/dbtime"
20
22
"github.com/coder/coder/v2/coderd/schedule"
21
23
"github.com/coder/coder/v2/coderd/schedule/cron"
22
24
"github.com/coder/coder/v2/coderd/util/ptr"
23
25
"github.com/coder/coder/v2/codersdk"
24
26
"github.com/coder/coder/v2/provisioner/echo"
25
27
"github.com/coder/coder/v2/provisionersdk/proto"
28
+ "github.com/coder/coder/v2/testutil"
26
29
)
27
30
28
31
func TestExecutorAutostartOK (t * testing.T ) {
@@ -781,6 +784,131 @@ func TestExecutorFailedWorkspace(t *testing.T) {
781
784
})
782
785
}
783
786
787
+ func TestExecutorBumpWorkspace (t * testing.T ) {
788
+ t .Parallel ()
789
+
790
+ midnight := time .Date (2023 , 10 , 4 , 0 , 0 , 0 , 0 , time .UTC )
791
+
792
+ cases := []struct {
793
+ name string
794
+ autostartSchedule string
795
+ now time.Time // defaults to 1h before the deadline
796
+ deadline time.Time
797
+ maxDeadline time.Time
798
+ ttl time.Duration
799
+ expected time.Time
800
+ }{
801
+ {
802
+ name : "not eligible" ,
803
+ autostartSchedule : "CRON_TZ=UTC 0 0 * * *" ,
804
+ deadline : midnight .Add (time .Hour * - 2 ),
805
+ ttl : time .Hour * 10 ,
806
+ // not eligible because the deadline is over an hour before the next
807
+ // autostart time
808
+ expected : time.Time {},
809
+ },
810
+ {
811
+ name : "autostart before deadline by 1h" ,
812
+ autostartSchedule : "CRON_TZ=UTC 0 0 * * *" ,
813
+ deadline : midnight .Add (time .Hour ),
814
+ ttl : time .Hour * 10 ,
815
+ expected : midnight .Add (time .Hour * 10 ),
816
+ },
817
+ {
818
+ name : "autostart before deadline by 9h" ,
819
+ autostartSchedule : "CRON_TZ=UTC 0 0 * * *" ,
820
+ deadline : midnight .Add (time .Hour * 9 ),
821
+ ttl : time .Hour * 10 ,
822
+ // should still be bumped
823
+ expected : midnight .Add (time .Hour * 10 ),
824
+ },
825
+ {
826
+ name : "eligible but exceeds next next autostart" ,
827
+ autostartSchedule : "CRON_TZ=UTC 0 0 * * *" ,
828
+ deadline : midnight .Add (time .Hour * 1 ),
829
+ // ttl causes next autostart + 25h to exceed the next next autostart
830
+ ttl : time .Hour * 25 ,
831
+ // should not be bumped to avoid infinite bumping every day
832
+ expected : time.Time {},
833
+ },
834
+ {
835
+ name : "deadline is 1h before autostart" ,
836
+ autostartSchedule : "CRON_TZ=UTC 0 0 * * *" ,
837
+ deadline : midnight .Add (time .Hour * - 1 ).Add (time .Minute ),
838
+ ttl : time .Hour * 10 ,
839
+ // should still be bumped
840
+ expected : midnight .Add (time .Hour * 10 ),
841
+ },
842
+ }
843
+
844
+ for _ , c := range cases {
845
+ c := c
846
+ t .Run (c .name , func (t * testing.T ) {
847
+ t .Parallel ()
848
+
849
+ var (
850
+ tickCh = make (chan time.Time )
851
+ statsCh = make (chan autobuild.Stats )
852
+ db , pubsub = dbtestutil .NewDB (t )
853
+
854
+ client = coderdtest .New (t , & coderdtest.Options {
855
+ AutobuildTicker : tickCh ,
856
+ AutobuildStats : statsCh ,
857
+ Database : db ,
858
+ Pubsub : pubsub ,
859
+ IncludeProvisionerDaemon : true ,
860
+ TemplateScheduleStore : schedule.MockTemplateScheduleStore {
861
+ GetFn : func (_ context.Context , _ database.Store , _ uuid.UUID ) (schedule.TemplateScheduleOptions , error ) {
862
+ return schedule.TemplateScheduleOptions {
863
+ UserAutostartEnabled : true ,
864
+ UserAutostopEnabled : true ,
865
+ DefaultTTL : 0 ,
866
+ AutostopRequirement : schedule.TemplateAutostopRequirement {},
867
+ }, nil
868
+ },
869
+ },
870
+ })
871
+ workspace = mustProvisionWorkspace (t , client , func (cwr * codersdk.CreateWorkspaceRequest ) {
872
+ cwr .AutostartSchedule = ptr .Ref (c .autostartSchedule )
873
+ cwr .TTLMillis = ptr .Ref (c .ttl .Milliseconds ())
874
+ })
875
+ )
876
+
877
+ // Set the deadline and max deadline to what the test expects.
878
+ ctx := testutil .Context (t , testutil .WaitLong )
879
+ err := db .UpdateWorkspaceBuildDeadlineByID (ctx , database.UpdateWorkspaceBuildDeadlineByIDParams {
880
+ ID : workspace .LatestBuild .ID ,
881
+ UpdatedAt : dbtime .Now (),
882
+ Deadline : c .deadline ,
883
+ MaxDeadline : c .maxDeadline ,
884
+ })
885
+ require .NoError (t , err )
886
+
887
+ if c .now .IsZero () {
888
+ c .now = c .deadline .Add (- time .Hour )
889
+ }
890
+
891
+ go func () {
892
+ tickCh <- c .now
893
+ close (tickCh )
894
+ }()
895
+ stats := <- statsCh
896
+ require .NoError (t , stats .Error )
897
+ require .Len (t , stats .Transitions , 0 )
898
+
899
+ // Get the latest workspace build.
900
+ build , err := client .WorkspaceBuild (ctx , workspace .LatestBuild .ID )
901
+ require .NoError (t , err )
902
+
903
+ // Should be bumped to the expected time.
904
+ if c .expected .IsZero () {
905
+ c .expected = c .deadline
906
+ }
907
+ require .WithinDuration (t , c .expected , build .Deadline .Time , time .Minute )
908
+ })
909
+ }
910
+ }
911
+
784
912
// TestExecutorInactiveWorkspace test AGPL functionality which mainly
785
913
// ensures that autostop actions as a result of an inactive workspace
786
914
// do not trigger.
0 commit comments