From ab7c566fc52322ef4b9e4c43c70fb2e8ea1f4dd6 Mon Sep 17 00:00:00 2001 From: Volker Schukai <volker.schukai@schukai.com> Date: Fri, 3 May 2024 17:48:36 +0200 Subject: [PATCH] feat: map time and duration in json context #60 --- .idea/workspace.xml | 65 ++++++++++++++------------- go.mod | 8 ++-- go.sum | 17 ++++--- job-generic.go | 4 +- job.go | 16 +++---- job_test.go | 8 ++-- manager_test.go | 10 +++-- persistence.go | 105 ++++++++++++++++++++++++++++++++++---------- persistence_test.go | 88 ++++++++++++++++++++++++++++++++++++- worker.go | 8 ++-- worker_test.go | 8 ++-- 11 files changed, 243 insertions(+), 94 deletions(-) diff --git a/.idea/workspace.xml b/.idea/workspace.xml index ffc9107..2a263b4 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -4,7 +4,9 @@ <option name="autoReloadType" value="SELECTIVE" /> </component> <component name="ChangeListManager"> - <list default="true" id="9979eb22-471e-4f2f-b624-fd3edb5e8c6e" name="Changes" comment="" /> + <list default="true" id="9979eb22-471e-4f2f-b624-fd3edb5e8c6e" name="Changes" comment=""> + <change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" /> + </list> <option name="SHOW_DIALOG" value="false" /> <option name="HIGHLIGHT_CONFLICTS" value="true" /> <option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" /> @@ -38,37 +40,38 @@ <option name="sortByType" value="true" /> <option name="sortKey" value="BY_TYPE" /> </component> - <component name="PropertiesComponent">{ - "keyToString": { - "DefaultGoTemplateProperty": "Go File", - "Go Test.TestDummyRunnable in gitlab.schukai.com/oss/libraries/go/services/job-queues.executor": "Debug", - "Go Test.TestManagerEventHandling in gitlab.schukai.com/oss/libraries/go/services/job-queues.executor": "Debug", - "Go Test.TestResetStats in gitlab.schukai.com/oss/libraries/go/services/job-queues.executor": "Debug", - "Go Test.TestRoundTrip in gitlab.schukai.com/oss/libraries/go/services/job-queues.executor": "Debug", - "Go Test.TestScheduleJob in gitlab.schukai.com/oss/libraries/go/services/job-queues.executor": "Debug", - "Go Test.TestStructure in gitlab.schukai.com/oss/libraries/go/services/job-queues.executor": "Debug", - "Go Test.TestTimeFunctionSame in gitlab.schukai.com/oss/libraries/go/services/job-queues.executor": "Debug", - "Go Test.TestWriteToDB1 in gitlab.schukai.com/oss/libraries/go/services/job-queues.executor": "Debug", - "Go Test.TestWriteToDB2 in gitlab.schukai.com/oss/libraries/go/services/job-queues.executor": "Debug", - "Go Test.TestWriteToDB4 in gitlab.schukai.com/oss/libraries/go/services/job-queues.executor": "Debug", - "Go Test.go test gitlab.schukai.com/oss/libraries/go/services/job-queues.executor": "Debug", - "RunOnceActivity.ShowReadmeOnStart": "true", - "RunOnceActivity.go.formatter.settings.were.checked": "true", - "RunOnceActivity.go.migrated.go.modules.settings": "true", - "SHARE_PROJECT_CONFIGURATION_FILES": "true", - "git-widget-placeholder": "master", - "go.import.settings.migrated": "true", - "go.sdk.automatically.set": "true", - "last_opened_file_path": "/home/vs/workspaces/oss/go-libs/job-queues/nix/scripts", - "node.js.detected.package.eslint": "true", - "node.js.detected.package.tslint": "true", - "node.js.selected.package.eslint": "(autodetect)", - "node.js.selected.package.tslint": "(autodetect)", - "nodejs_package_manager_path": "npm", - "settings.editor.selected.configurable": "http.proxy", - "vue.rearranger.settings.migration": "true" + <component name="PropertiesComponent"><![CDATA[{ + "keyToString": { + "DefaultGoTemplateProperty": "Go File", + "Go Test.TestDummyRunnable in gitlab.schukai.com/oss/libraries/go/services/job-queues.executor": "Debug", + "Go Test.TestManagerEventHandling in gitlab.schukai.com/oss/libraries/go/services/job-queues.executor": "Debug", + "Go Test.TestResetStats in gitlab.schukai.com/oss/libraries/go/services/job-queues.executor": "Debug", + "Go Test.TestRoundTrip in gitlab.schukai.com/oss/libraries/go/services/job-queues.executor": "Debug", + "Go Test.TestScheduleJob in gitlab.schukai.com/oss/libraries/go/services/job-queues.executor": "Debug", + "Go Test.TestStructure in gitlab.schukai.com/oss/libraries/go/services/job-queues.executor": "Debug", + "Go Test.TestTimeFunctionSame in gitlab.schukai.com/oss/libraries/go/services/job-queues.executor": "Debug", + "Go Test.TestWriteToDB1 in gitlab.schukai.com/oss/libraries/go/services/job-queues.executor": "Debug", + "Go Test.TestWriteToDB2 in gitlab.schukai.com/oss/libraries/go/services/job-queues.executor": "Debug", + "Go Test.TestWriteToDB4 in gitlab.schukai.com/oss/libraries/go/services/job-queues.executor": "Debug", + "Go Test.go test gitlab.schukai.com/oss/libraries/go/services/job-queues.executor": "Debug", + "RunOnceActivity.ShowReadmeOnStart": "true", + "RunOnceActivity.go.formatter.settings.were.checked": "true", + "RunOnceActivity.go.migrated.go.modules.settings": "true", + "RunOnceActivity.go.modules.automatic.dependencies.download": "true", + "SHARE_PROJECT_CONFIGURATION_FILES": "true", + "git-widget-placeholder": "master", + "go.import.settings.migrated": "true", + "go.sdk.automatically.set": "true", + "last_opened_file_path": "/home/vs/workspaces/oss/go-libs/job-queues/nix/scripts", + "node.js.detected.package.eslint": "true", + "node.js.detected.package.tslint": "true", + "node.js.selected.package.eslint": "(autodetect)", + "node.js.selected.package.tslint": "(autodetect)", + "nodejs_package_manager_path": "npm", + "settings.editor.selected.configurable": "http.proxy", + "vue.rearranger.settings.migration": "true" } -}</component> +}]]></component> <component name="RecentsManager"> <key name="CopyFile.RECENT_KEYS"> <recent name="$PROJECT_DIR$/nix/scripts" /> diff --git a/go.mod b/go.mod index d586c3e..196794e 100644 --- a/go.mod +++ b/go.mod @@ -10,14 +10,14 @@ require ( github.com/google/uuid v1.6.0 github.com/pkg/sftp v1.13.6 github.com/robfig/cron/v3 v3.0.1 - github.com/shirou/gopsutil/v3 v3.24.3 + github.com/shirou/gopsutil/v3 v3.24.4 github.com/stretchr/testify v1.9.0 go.uber.org/zap v1.27.0 golang.org/x/crypto v0.22.0 gopkg.in/yaml.v3 v3.0.1 gorm.io/driver/mysql v1.5.6 gorm.io/driver/sqlite v1.5.5 - gorm.io/gorm v1.25.9 + gorm.io/gorm v1.25.10 gotest.tools/v3 v3.5.1 ) @@ -45,8 +45,8 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect - github.com/tklauser/go-sysconf v0.3.13 // indirect - github.com/tklauser/numcpus v0.7.0 // indirect + github.com/tklauser/go-sysconf v0.3.14 // indirect + github.com/tklauser/numcpus v0.8.0 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/mod v0.8.0 // indirect diff --git a/go.sum b/go.sum index c8d7124..e8af371 100644 --- a/go.sum +++ b/go.sum @@ -67,8 +67,8 @@ github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= -github.com/shirou/gopsutil/v3 v3.24.3 h1:eoUGJSmdfLzJ3mxIhmOAhgKEKgQkeOwKpz1NbhVnuPE= -github.com/shirou/gopsutil/v3 v3.24.3/go.mod h1:JpND7O217xa72ewWz9zN2eIIkPWsDN/3pl0H8Qt0uwg= +github.com/shirou/gopsutil/v3 v3.24.4 h1:dEHgzZXt4LMNm+oYELpzl9YCqV65Yr/6SfrvgRBtXeU= +github.com/shirou/gopsutil/v3 v3.24.4/go.mod h1:lTd2mdiOspcqLgAnr9/nGi71NkeMpWKdmhuxm9GusH8= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= @@ -83,11 +83,11 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= -github.com/tklauser/go-sysconf v0.3.13 h1:GBUpcahXSpR2xN01jhkNAbTLRk2Yzgggk8IM08lq3r4= -github.com/tklauser/go-sysconf v0.3.13/go.mod h1:zwleP4Q4OehZHGn4CYZDipCgg9usW5IJePewFCGVEa0= +github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU= +github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY= github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= -github.com/tklauser/numcpus v0.7.0 h1:yjuerZP127QG9m5Zh/mSO4wqurYil27tHrqwRoRjpr4= -github.com/tklauser/numcpus v0.7.0/go.mod h1:bb6dMVcj8A42tSE7i32fsIUCbQNllK5iDguyOZRUzAY= +github.com/tklauser/numcpus v0.8.0 h1:Mx4Wwe/FjZLeQsK/6kt2EOepwwSl7SmJrK5bV/dXYgY= +github.com/tklauser/numcpus v0.8.0/go.mod h1:ZJZlAY+dmR4eut8epnzf0u/VwodKmryxR8txiloSqBE= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= @@ -138,7 +138,6 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -173,7 +172,7 @@ gorm.io/driver/mysql v1.5.6/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkD gorm.io/driver/sqlite v1.5.5 h1:7MDMtUZhV065SilG62E0MquljeArQZNfJnjd9i9gx3E= gorm.io/driver/sqlite v1.5.5/go.mod h1:6NgQ7sQWAIFsPrJJl1lSNSu2TABh0ZZ/zm5fosATavE= gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= -gorm.io/gorm v1.25.9 h1:wct0gxZIELDk8+ZqF/MVnHLkA1rvYlBWUMv2EdsK1g8= -gorm.io/gorm v1.25.9/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= +gorm.io/gorm v1.25.10 h1:dQpO+33KalOA+aFYGlK+EfxcI5MbO7EP2yYygwh9h+s= +gorm.io/gorm v1.25.10/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= diff --git a/job-generic.go b/job-generic.go index 3a2efc3..be6e644 100644 --- a/job-generic.go +++ b/job-generic.go @@ -21,9 +21,9 @@ type GenericJob interface { GetMaxRetries() uint - GetRetryDelay() time.Duration + GetRetryDelay() *time.Duration - GetTimeout() time.Duration + GetTimeout() *time.Duration GetPersistence() JobPersistence diff --git a/job.go b/job.go index b24f4c4..bf3f1b6 100644 --- a/job.go +++ b/job.go @@ -38,9 +38,9 @@ type Job[T any] struct { description string priority Priority - timeout time.Duration + timeout *time.Duration maxRetries uint - RetryDelay time.Duration + retryDelay *time.Duration scheduler Scheduler @@ -101,7 +101,7 @@ func (j *Job[T]) GetPersistence() JobPersistence { Priority: j.priority, Timeout: j.timeout, MaxRetries: j.maxRetries, - RetryDelay: j.RetryDelay, + RetryDelay: j.retryDelay, Dependencies: j.dependencies, Runnable: j.runner.GetPersistence(), @@ -314,12 +314,12 @@ func (j *Job[T]) SetTimeout(timeout time.Duration) *Job[T] { j.mu.Lock() defer j.mu.Unlock() - j.timeout = timeout + j.timeout = &timeout return j } // GetTimeout returns the timeout of the job -func (j *Job[T]) GetTimeout() time.Duration { +func (j *Job[T]) GetTimeout() *time.Duration { j.mu.Lock() defer j.mu.Unlock() @@ -348,16 +348,16 @@ func (j *Job[T]) SetRetryDelay(retryDelay time.Duration) *Job[T] { j.mu.Lock() defer j.mu.Unlock() - j.RetryDelay = retryDelay + j.retryDelay = &retryDelay return j } // GetRetryDelay returns the retry delay of the job -func (j *Job[T]) GetRetryDelay() time.Duration { +func (j *Job[T]) GetRetryDelay() *time.Duration { j.mu.Lock() defer j.mu.Unlock() - return j.RetryDelay + return j.retryDelay } // SetDependencies sets the dependencies of the job diff --git a/job_test.go b/job_test.go index 55138d9..2ef4c1e 100644 --- a/job_test.go +++ b/job_test.go @@ -39,7 +39,7 @@ func TestSetPriority(t *testing.T) { func TestSetAndGetTimeout(t *testing.T) { job := NewJob[ShellResult]("id1", &ShellRunnable{}) job.SetTimeout(5 * time.Minute) - assert.Equal(t, 5*time.Minute, job.GetTimeout()) + assert.Equal(t, 5*time.Minute, *job.GetTimeout()) } func TestSetAndGetMaxRetries(t *testing.T) { @@ -57,7 +57,7 @@ func TestSetAndGetRunnable(t *testing.T) { func TestSetAndGetRetryDelay(t *testing.T) { job := NewJob[ShellResult]("id1", &ShellRunnable{}) job.SetRetryDelay(2 * time.Second) - assert.Equal(t, 2*time.Second, job.GetRetryDelay()) + assert.Equal(t, 2*time.Second, *job.GetRetryDelay()) } func TestSetAndGetDependencies(t *testing.T) { @@ -90,7 +90,7 @@ func TestGetPriority(t *testing.T) { func TestGetTimeout(t *testing.T) { job := NewJob[ShellResult]("id2", &ShellRunnable{}) - assert.Equal(t, time.Duration(0), job.GetTimeout()) + assert.Nil(t, job.GetTimeout()) } func TestGetMaxRetries(t *testing.T) { @@ -100,7 +100,7 @@ func TestGetMaxRetries(t *testing.T) { func TestGetRetryDelay(t *testing.T) { job := NewJob[ShellResult]("id2", &ShellRunnable{}) - assert.Equal(t, time.Duration(0), job.GetRetryDelay()) + assert.Nil(t, job.GetRetryDelay()) } func TestGetDependencies(t *testing.T) { diff --git a/manager_test.go b/manager_test.go index 5bb93dd..ee2321d 100644 --- a/manager_test.go +++ b/manager_test.go @@ -72,12 +72,14 @@ func (m *MockGenericJob) GetMaxRetries() uint { return 0 } -func (m *MockGenericJob) GetRetryDelay() time.Duration { - return 0 +func (m *MockGenericJob) GetRetryDelay() *time.Duration { + dur := time.Duration(0) + return &dur } -func (m *MockGenericJob) GetTimeout() time.Duration { - return 0 +func (m *MockGenericJob) GetTimeout() *time.Duration { + dur := time.Duration(0) + return &dur } func (m *MockGenericJob) GetID() JobID { diff --git a/persistence.go b/persistence.go index ea0c8a6..0f8896d 100644 --- a/persistence.go +++ b/persistence.go @@ -19,16 +19,20 @@ type JobPersistence struct { ID JobID `yaml:"id" json:"id" gorm:"type:varchar(255);primaryKey"` Description string `yaml:"description" json:"description" gorm:"column:description"` Priority Priority `yaml:"priority" json:"priority" gorm:"column:priority"` - Timeout time.Duration `yaml:"timeout" json:"timeout" gorm:"column:timeout"` + Timeout *time.Duration `yaml:"-" json:"-" gorm:"column:timeout"` MaxRetries uint `yaml:"maxRetries" json:"maxRetries" gorm:"column:max_retries"` - RetryDelay time.Duration `yaml:"retryDelay" json:"retryDelay" gorm:"column:retry_delay"` + RetryDelay *time.Duration `yaml:"-" json:"-" gorm:"column:retry_delay"` Dependencies []JobID `yaml:"dependencies" json:"dependencies,omitempty" gorm:"column:dependencies;type:json"` Runnable RunnableImport `yaml:"runnable" json:"runnable" gorm:"embedded;embeddedPrefix:runnable_"` Scheduler SchedulerPersistence `yaml:"scheduler" json:"scheduler,omitempty" gorm:"embedded;embeddedPrefix:scheduler_"` Pause bool `yaml:"pause" json:"pause" gorm:"column:pause"` PauseReason string `yaml:"pauseReason" json:"pauseReason" gorm:"column:pause_reason"` - PauseUntil *time.Time `yaml:"pauseUntil" json:"pauseUntil" gorm:"column:pause_until"` + PauseUntil *time.Time `yaml:"-" json:"-" gorm:"column:pause_until"` + + TimeoutString *string `yaml:"timeout,omitempty" json:"timeout,omitempty" gorm:"-"` + RetryDelayString *string `yaml:"retryDelay,omitempty" json:"retryDelay,omitempty" gorm:"-"` + PauseUntilString *string `yaml:"pauseUntil" json:"pauseUntil,omitempty" gorm:"-"` Logs []JobLog `gorm:"foreignKey:JobID;references:ID" json:"-" yaml:"-"` Stats JobStats `gorm:"foreignKey:JobID" json:"stats" yaml:"stats"` @@ -38,39 +42,95 @@ type JobPersistence struct { DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;index" json:"-" yaml:"-"` } -// UnmarshalJSON implements the json.Unmarshaler interface. -func (jp *JobPersistence) UnmarshalJSON(data []byte) error { - // Anonymous struct for unmarshalling with custom time format +func (j *JobPersistence) MarshalJSON() ([]byte, error) { + type Alias JobPersistence + return json.Marshal(&struct { + *Alias + TimeoutString string `json:"timeout,omitempty"` + RetryDelayString string `json:"retryDelay,omitempty"` + PauseUntilString string `json:"pauseUntil,omitempty"` + }{ + Alias: (*Alias)(j), + TimeoutString: formatDuration(j.Timeout), + RetryDelayString: formatDuration(j.RetryDelay), + PauseUntilString: formatTime(j.PauseUntil), + }) +} + +func (j *JobPersistence) UnmarshalJSON(data []byte) error { type Alias JobPersistence aux := &struct { - PauseUntil *string `json:"pauseUntil,omitempty"` *Alias + TimeoutString *string `json:"timeout,omitempty"` + RetryDelayString *string `json:"retryDelay,omitempty"` + PauseUntilString *string `json:"pauseUntil,omitempty"` }{ - Alias: (*Alias)(jp), + Alias: (*Alias)(j), } - - if err := json.Unmarshal(data, &aux); err != nil { + if err := json.Unmarshal(data, aux); err != nil { return err } - - if aux.PauseUntil != nil { - var t time.Time - var err error - for _, format := range SupportedTimeFormats { - t, err = time.Parse(format, *aux.PauseUntil) - if err == nil { - break - } + if aux.TimeoutString != nil && *aux.TimeoutString != "" { + d, err := time.ParseDuration(*aux.TimeoutString) + if err != nil { + return err } + j.Timeout = &d + } + if aux.RetryDelayString != nil && *aux.RetryDelayString != "" { + d, err := time.ParseDuration(*aux.RetryDelayString) if err != nil { return err } - jp.PauseUntil = &t + j.RetryDelay = &d } + if aux.PauseUntilString != nil && *aux.PauseUntilString != "" { + t, err := parseMultipleDateFormats(*aux.PauseUntilString, []string{ + time.RFC3339, time.RFC3339Nano, + "2006-01-02T15:04:05", + "2006-01-02T15:04:05.999999999", + "2006-01-02T15:04:05Z07:00", + "2006-01-02T15:04:05", + "2006-01-02T15:04", + "2006-01-02T15", + "2006-01-02T", + "2006-01-02", + }) + if err != nil { + return err + } + j.PauseUntil = &t + } return nil } +func parseMultipleDateFormats(value string, formats []string) (time.Time, error) { + var t time.Time + var err error + for _, format := range formats { + t, err = time.Parse(format, value) + if err == nil { + return t, nil + } + } + return t, fmt.Errorf("date string '%s' does not match any known format", value) +} + +func formatDuration(d *time.Duration) string { + if d != nil { + return d.String() + } + return "" +} + +func formatTime(t *time.Time) string { + if t != nil { + return t.Format(time.RFC3339) + } + return "" +} + func (jp JobPersistence) GetLogs() []JobLog { return jp.Logs } @@ -96,7 +156,7 @@ func (jp JobPersistence) GetPriority() Priority { } func (jp JobPersistence) GetTimeout() time.Duration { - return jp.Timeout + return *jp.Timeout } func (JobPersistence) TableName() string { @@ -182,13 +242,14 @@ func ReadFromGORM(db *gorm.DB) ([]JobPersistence, error) { } func CreateGenericJobFromPersistence[T any](jobImport JobPersistence, runner Runnable[T]) GenericJob { + return &Job[T]{ id: jobImport.ID, description: jobImport.Description, priority: jobImport.Priority, timeout: jobImport.Timeout, maxRetries: jobImport.MaxRetries, - RetryDelay: jobImport.RetryDelay, + retryDelay: jobImport.RetryDelay, dependencies: jobImport.Dependencies, pause: jobImport.Pause, pauseReason: jobImport.PauseReason, diff --git a/persistence_test.go b/persistence_test.go index 9cc2d89..60ac796 100644 --- a/persistence_test.go +++ b/persistence_test.go @@ -14,6 +14,10 @@ import ( ) func TestCreateJobAndSchedulerFromInput(t *testing.T) { + + time10s := 10 * time.Second + time2s := 5 * time.Minute + tests := []struct { name string input JobPersistence @@ -26,9 +30,9 @@ func TestCreateJobAndSchedulerFromInput(t *testing.T) { input: JobPersistence{ ID: "1", Priority: 1, - Timeout: 10 * time.Second, + Timeout: &time10s, MaxRetries: 3, - RetryDelay: 2 * time.Second, + RetryDelay: &time2s, Runnable: RunnableImport{ Type: "Shell", Data: map[string]any{"ScriptPath": "script.sh"}, @@ -89,6 +93,86 @@ func TestCreateJobAndSchedulerFromInput(t *testing.T) { } } +func TestJobPersistence_MarshalUnmarshalJSON(t *testing.T) { + timeRef := time.Now().Round(time.Second) // Runden, um Genauigkeitsverlust zu vermeiden + timeRefString := timeRef.Format(time.RFC3339) + + time5m := 5 * time.Minute + time10s := 10 * time.Second + time30s := 30 * time.Second + time1Hour := 1 * time.Hour + + time10sString := "10s" + time30sString := "30s" + time5m0sString := "5m0s" + time1h0m0sString := "1h0m0s" + emptyString := "" + + tests := []struct { + name string + job JobPersistence + expected string + }{ + { + name: "All fields present", + job: JobPersistence{ + Timeout: &time5m, + RetryDelay: &time10s, + PauseUntil: &timeRef, + TimeoutString: &time5m0sString, + RetryDelayString: &time10sString, + PauseUntilString: &timeRefString, + }, + expected: ` {"id":"","description":"","priority":0,"maxRetries":0,"runnable":{"type":""},"scheduler":{"type":""},"pause":false,"pauseReason":"","timeout":"5m0s","retryDelay":"10s","pauseUntil":"` + timeRefString + `","stats":{"jobId":"","runCount":0,"successCount":0,"errorCount":0,"timeMetrics":{"avg":0,"max":0,"min":0,"total":0},"createdAt":"0001-01-01T00:00:00Z","updatedAt":"0001-01-01T00:00:00Z"},"createdAt":"0001-01-01T00:00:00Z","updatedAt":"0001-01-01T00:00:00Z"}`, + }, + { + name: "Nil pauseUntil", + job: JobPersistence{ + Timeout: &time1Hour, + RetryDelay: &time30s, + PauseUntil: nil, + TimeoutString: &time1h0m0sString, + RetryDelayString: &time30sString, + PauseUntilString: &emptyString, + }, + expected: `{"id":"","description":"","priority":0,"maxRetries":0,"runnable":{"type":""},"scheduler":{"type":""},"pause":false,"pauseReason":"","timeout":"1h0m0s","retryDelay":"30s","pauseUntil":"","stats":{"jobId":"","runCount":0,"successCount":0,"errorCount":0,"timeMetrics":{"avg":0,"max":0,"min":0,"total":0},"createdAt":"0001-01-01T00:00:00Z","updatedAt":"0001-01-01T00:00:00Z"},"createdAt":"0001-01-01T00:00:00Z","updatedAt":"0001-01-01T00:00:00Z"}`, + }, + { + name: "Nil timeout and retryDelay", + job: JobPersistence{ + Timeout: nil, + RetryDelay: nil, + PauseUntil: nil, + TimeoutString: &emptyString, + RetryDelayString: &emptyString, + PauseUntilString: &emptyString, + }, + expected: `{"id":"","description":"","priority":0,"maxRetries":0,"runnable":{"type":""},"scheduler":{"type":""},"pause":false,"pauseReason":"","timeout":"","retryDelay":"","pauseUntil":"","stats":{"jobId":"","runCount":0,"successCount":0,"errorCount":0,"timeMetrics":{"avg":0,"max":0,"min":0,"total":0},"createdAt":"0001-01-01T00:00:00Z","updatedAt":"0001-01-01T00:00:00Z"},"createdAt":"0001-01-01T00:00:00Z","updatedAt":"0001-01-01T00:00:00Z"}`, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + // MarshalJSON testen + data, err := json.Marshal(tc.job) + + assert.NoError(t, err) + assert.JSONEq(t, tc.expected, string(data)) + + var job JobPersistence + err = json.Unmarshal(data, &job) + assert.NoError(t, err) + assert.Equal(t, tc.job.Timeout, job.Timeout) + assert.Equal(t, tc.job.RetryDelay, job.RetryDelay) + if tc.job.PauseUntil != nil { + assert.Equal(t, *tc.job.PauseUntil, *job.PauseUntil) + } else { + assert.Nil(t, job.PauseUntil) + } + }) + } +} + func TestReadJsonFile(t *testing.T) { testContent := `[{"id": "1", "priority": 1}]` tempFile, err := ioutil.TempFile("", "test.json") diff --git a/worker.go b/worker.go index 604df3e..fb1dce2 100644 --- a/worker.go +++ b/worker.go @@ -208,8 +208,8 @@ func (w *LocalWorker) run(jobChannel chan GenericJob, stopChan chan bool, cancel var cancel context.CancelFunc timeout := job.GetTimeout() - if timeout > 0 { - ctx, cancel = context.WithTimeout(ctx, timeout) + if timeout != nil && *timeout > 0 { + ctx, cancel = context.WithTimeout(ctx, *timeout) } Info("Executing job on worker thread", "worker", w.ID, "thread_id", workerThreadID, "job_id", job.GetID()) @@ -230,8 +230,8 @@ func (w *LocalWorker) run(jobChannel chan GenericJob, stopChan chan bool, cancel break } - if retryDelay > 0 { - time.Sleep(retryDelay) + if retryDelay != nil && *retryDelay > 0 { + time.Sleep(*retryDelay) } retries-- diff --git a/worker_test.go b/worker_test.go index 84bdb4c..9ca1a54 100644 --- a/worker_test.go +++ b/worker_test.go @@ -47,12 +47,12 @@ func (j DummyJob) GetMaxRetries() uint { return 0 } -func (j DummyJob) GetRetryDelay() time.Duration { - return 0 +func (j DummyJob) GetRetryDelay() *time.Duration { + return nil } -func (j DummyJob) GetTimeout() time.Duration { - return 0 +func (j DummyJob) GetTimeout() *time.Duration { + return nil } func (j DummyJob) Execute(_ context.Context) (RunGenericResult, error) { -- GitLab