From aa17772748d80af3a7cd3eb14d48c19c90d5bd02 Mon Sep 17 00:00:00 2001 From: Jeff Ortel Date: Wed, 28 Jun 2023 09:52:00 -0700 Subject: [PATCH 1/5] checkpoint --- api/task.go | 14 ++++- migration/v6/model/pkg.go | 3 -- migration/v6/model/task.go | 83 ++++++++++++++++++++++++++++++ migration/v6/model/taskgroup.go | 91 +++++++++++++++++++++++++++++++++ task/manager.go | 10 ++-- 5 files changed, 191 insertions(+), 10 deletions(-) create mode 100644 migration/v6/model/task.go create mode 100644 migration/v6/model/taskgroup.go diff --git a/api/task.go b/api/task.go index b198d8da..7cbc5e51 100644 --- a/api/task.go +++ b/api/task.go @@ -496,6 +496,14 @@ type TTL struct { Failed int `json:"failed,omitempty"` } +// +// TaskEvent used in Task.Errors. +type TaskEvent struct { + Kind string `json:"kind"` + Origin string `json:"origin,omitempty"` + Description string `json:"description"` +} + // // Task REST resource. type Task struct { @@ -515,7 +523,7 @@ type Task struct { Purged bool `json:"purged,omitempty" yaml:",omitempty"` Started *time.Time `json:"started,omitempty" yaml:",omitempty"` Terminated *time.Time `json:"terminated,omitempty" yaml:",omitempty"` - Error string `json:"error,omitempty" yaml:",omitempty"` + Events []TaskEvent `json:"events,omitempty" yaml:",omitempty"` Pod string `json:"pod,omitempty" yaml:",omitempty"` Retries int `json:"retries,omitempty" yaml:",omitempty"` Canceled bool `json:"canceled,omitempty" yaml:",omitempty"` @@ -538,7 +546,6 @@ func (r *Task) With(m *model.Task) { r.State = m.State r.Started = m.Started r.Terminated = m.Terminated - r.Error = m.Error r.Pod = m.Pod r.Retries = m.Retries r.Canceled = m.Canceled @@ -551,6 +558,9 @@ func (r *Task) With(m *model.Task) { if m.TTL != nil { _ = json.Unmarshal(m.TTL, &r.TTL) } + if m.Events != nil { + _ = json.Unmarshal(m.Events, &r.Events) + } } // diff --git a/migration/v6/model/pkg.go b/migration/v6/model/pkg.go index 2b8dce2c..1d5a566b 100644 --- a/migration/v6/model/pkg.go +++ b/migration/v6/model/pkg.go @@ -29,12 +29,9 @@ type Stakeholder = model.Stakeholder type StakeholderGroup = model.StakeholderGroup type Tag = model.Tag type TagCategory = model.TagCategory -type Task = model.Task -type TaskGroup = model.TaskGroup type TaskReport = model.TaskReport type Ticket = model.Ticket type Tracker = model.Tracker -type TTL = model.TTL type ApplicationTag = model.ApplicationTag type DependencyCyclicError = model.DependencyCyclicError diff --git a/migration/v6/model/task.go b/migration/v6/model/task.go new file mode 100644 index 00000000..bd505627 --- /dev/null +++ b/migration/v6/model/task.go @@ -0,0 +1,83 @@ +package model + +import ( + "encoding/json" + "gorm.io/gorm" + "time" +) + +type Task struct { + Model + BucketOwner + Name string `gorm:"index"` + Addon string `gorm:"index"` + Locator string `gorm:"index"` + Priority int + Image string + Variant string + Policy string + TTL JSON + Data JSON + Started *time.Time + Terminated *time.Time + State string `gorm:"index"` + Events JSON + Pod string `gorm:"index"` + Retries int + Canceled bool + Report *TaskReport `gorm:"constraint:OnDelete:CASCADE"` + ApplicationID *uint + Application *Application + TaskGroupID *uint `gorm:"<-:create"` + TaskGroup *TaskGroup +} + +func (m *Task) Reset() { + m.Started = nil + m.Terminated = nil + m.Report = nil +} + +func (m *Task) BeforeCreate(db *gorm.DB) (err error) { + err = m.BucketOwner.BeforeCreate(db) + m.Reset() + return +} + +// +// Event appends an event. +func (m *Task) Event(kind, origin, description string) { + var events []TaskEvent + _ = json.Unmarshal(m.Events, &events) + events = append( + events, + TaskEvent{ + Kind: kind, + Origin: origin, + Description: description, + }) + m.Events, _ = json.Marshal(events) +} + +// +// Map alias. +type Map = map[string]interface{} + +// +// TTL time-to-live. +type TTL struct { + Created int `json:"created,omitempty"` + Pending int `json:"pending,omitempty"` + Postponed int `json:"postponed,omitempty"` + Running int `json:"running,omitempty"` + Succeeded int `json:"succeeded,omitempty"` + Failed int `json:"failed,omitempty"` +} + +// +// TaskEvent used in Task.Errors. +type TaskEvent struct { + Kind string `json:"kind"` + Origin string `json:"origin,omitempty"` + Description string `json:"description"` +} diff --git a/migration/v6/model/taskgroup.go b/migration/v6/model/taskgroup.go new file mode 100644 index 00000000..cc61ce22 --- /dev/null +++ b/migration/v6/model/taskgroup.go @@ -0,0 +1,91 @@ +package model + +import ( + "encoding/json" + liberr "github.com/jortel/go-utils/error" +) + +type TaskGroup struct { + Model + BucketOwner + Name string + Addon string + Data JSON + Tasks []Task `gorm:"constraint:OnDelete:CASCADE"` + List JSON + State string +} + +// +// Propagate group data into the task. +func (m *TaskGroup) Propagate() (err error) { + for i := range m.Tasks { + task := &m.Tasks[i] + task.State = m.State + task.SetBucket(m.BucketID) + if task.Addon == "" { + task.Addon = m.Addon + } + if m.Data == nil { + continue + } + a := Map{} + err = json.Unmarshal(m.Data, &a) + if err != nil { + err = liberr.Wrap( + err, + "id", + m.ID) + return + } + b := Map{} + err = json.Unmarshal(task.Data, &b) + if err != nil { + err = liberr.Wrap( + err, + "id", + m.ID) + return + } + task.Data, _ = json.Marshal(m.merge(a, b)) + } + + return +} + +// +// merge maps B into A. +// The B map is the authority. +func (m *TaskGroup) merge(a, b Map) (out Map) { + if a == nil { + a = Map{} + } + if b == nil { + b = Map{} + } + out = Map{} + // + // Merge-in elements found in B and in A. + for k, v := range a { + out[k] = v + if bv, found := b[k]; found { + out[k] = bv + if av, cast := v.(Map); cast { + if bv, cast := bv.(Map); cast { + out[k] = m.merge(av, bv) + } else { + out[k] = bv + } + } + } + } + // + // Add elements found only in B. + for k, v := range b { + if _, found := a[k]; !found { + out[k] = v + } + } + + return +} diff --git a/task/manager.go b/task/manager.go index e12aa3bc..7fcf1573 100644 --- a/task/manager.go +++ b/task/manager.go @@ -132,7 +132,7 @@ func (m *Manager) startReady() { mark := time.Now() task.State = Failed task.Terminated = &mark - task.Error = "Hub is disconnected." + task.Event("Error", "Hub", "Hub is disconnected.") sErr := m.DB.Save(task).Error Log.Error(sErr, "") continue @@ -147,6 +147,7 @@ func (m *Manager) startReady() { ready := task if m.postpone(ready, list) { ready.State = Postponed + ready.Event("Warning", "Hub", Postponed) Log.Info("Task postponed.", "id", ready.ID) sErr := m.DB.Save(ready).Error Log.Error(sErr, "") @@ -157,7 +158,7 @@ func (m *Manager) startReady() { err := rt.Run(m.Client) if err != nil { if errors.Is(err, &AddonNotFound{}) { - ready.Error = err.Error() + ready.Event("Error", "Hub", err.Error()) ready.State = Failed sErr := m.DB.Save(ready).Error Log.Error(sErr, "") @@ -269,7 +270,7 @@ func (r *Task) Run(client k8s.Client) (err error) { mark := time.Now() defer func() { if err != nil { - r.Error = err.Error() + r.Event("Error", "Hub", err.Error()) r.Terminated = &mark r.State = Failed } @@ -354,16 +355,15 @@ func (r *Task) Reflect(client k8s.Client) (err error) { r.State = Succeeded r.Terminated = &mark case core.PodFailed: + r.Event("Error", "k8s", pod.Status.Message) if r.Retries < Settings.Hub.Task.Retries { _ = client.Delete(context.TODO(), pod) r.Pod = "" - r.Error = "" r.State = Ready r.Retries++ } else { r.State = Failed r.Terminated = &mark - r.Error = "pod failed." } } From dc7c6dd323fb57a81610d0e46a6d3644123bd833 Mon Sep 17 00:00:00 2001 From: Jeff Ortel Date: Thu, 29 Jun 2023 07:48:20 -0700 Subject: [PATCH 2/5] checkpoint Signed-off-by: Jeff Ortel --- addon/task.go | 27 +++++++++++++-- api/task.go | 23 +++++++------ hack/cmd/addon/main.go | 2 ++ migration/v6/migrate.go | 68 ++++++++++++++++++++++++++++++++++++++ migration/v6/model/pkg.go | 1 - migration/v6/model/task.go | 42 +++++++++++++---------- task/manager.go | 9 +++-- 7 files changed, 136 insertions(+), 36 deletions(-) diff --git a/addon/task.go b/addon/task.go index 011fbd2d..e0c9915b 100644 --- a/addon/task.go +++ b/addon/task.go @@ -93,13 +93,34 @@ func (h *Task) Succeeded() { // Failed report addon failed. // The reason can be a printf style format. func (h *Task) Failed(reason string, x ...interface{}) { + reason = fmt.Sprintf(reason, x...) h.report.Status = task.Failed - h.report.Error = fmt.Sprintf(reason, x...) + h.report.Errors = append( + h.report.Errors, + api.TaskError{ + Severity: "Error", + Description: reason, + }) h.pushReport() Log.Info( "Addon reported: failed.", - "error", - h.report.Error) + "reason", + reason) + return +} + +// +// Error report addon error. +// The description can be a printf style format. +func (h *Task) Error(severity, description string, x ...interface{}) { + description = fmt.Sprintf(description, x...) + h.report.Errors = append( + h.report.Errors, + api.TaskError{ + Severity: severity, + Description: description, + }) + h.pushReport() return } diff --git a/api/task.go b/api/task.go index 7cbc5e51..5552fe61 100644 --- a/api/task.go +++ b/api/task.go @@ -497,10 +497,9 @@ type TTL struct { } // -// TaskEvent used in Task.Errors. -type TaskEvent struct { - Kind string `json:"kind"` - Origin string `json:"origin,omitempty"` +// TaskError used in Task.Errors. +type TaskError struct { + Severity string `json:"severity"` Description string `json:"description"` } @@ -523,7 +522,7 @@ type Task struct { Purged bool `json:"purged,omitempty" yaml:",omitempty"` Started *time.Time `json:"started,omitempty" yaml:",omitempty"` Terminated *time.Time `json:"terminated,omitempty" yaml:",omitempty"` - Events []TaskEvent `json:"events,omitempty" yaml:",omitempty"` + Errors []TaskError `json:"errors,omitempty" yaml:",omitempty"` Pod string `json:"pod,omitempty" yaml:",omitempty"` Retries int `json:"retries,omitempty" yaml:",omitempty"` Canceled bool `json:"canceled,omitempty" yaml:",omitempty"` @@ -558,8 +557,8 @@ func (r *Task) With(m *model.Task) { if m.TTL != nil { _ = json.Unmarshal(m.TTL, &r.TTL) } - if m.Events != nil { - _ = json.Unmarshal(m.Events, &r.Events) + if m.Errors != nil { + _ = json.Unmarshal(m.Errors, &r.Errors) } } @@ -589,7 +588,7 @@ func (r *Task) Model() (m *model.Task) { type TaskReport struct { Resource `yaml:",inline"` Status string `json:"status"` - Error string `json:"error,omitempty" yaml:",omitempty"` + Errors []TaskError `json:"errors,omitempty" yaml:",omitempty"` Total int `json:"total,omitempty" yaml:",omitempty"` Completed int `json:"completed,omitempty" yaml:",omitempty"` Activity []string `json:"activity,omitempty" yaml:",omitempty"` @@ -602,13 +601,15 @@ type TaskReport struct { func (r *TaskReport) With(m *model.TaskReport) { r.Resource.With(&m.Model) r.Status = m.Status - r.Error = m.Error r.Total = m.Total r.Completed = m.Completed r.TaskID = m.TaskID if m.Activity != nil { _ = json.Unmarshal(m.Activity, &r.Activity) } + if m.Errors != nil { + _ = json.Unmarshal(m.Errors, &r.Errors) + } if m.Result != nil { _ = json.Unmarshal(m.Result, &r.Result) } @@ -622,7 +623,6 @@ func (r *TaskReport) Model() (m *model.TaskReport) { } m = &model.TaskReport{ Status: r.Status, - Error: r.Error, Total: r.Total, Completed: r.Completed, TaskID: r.TaskID, @@ -633,6 +633,9 @@ func (r *TaskReport) Model() (m *model.TaskReport) { if r.Result != nil { m.Result, _ = json.Marshal(r.Result) } + if r.Errors != nil { + m.Errors, _ = json.Marshal(r.Errors) + } m.ID = r.ID return diff --git a/hack/cmd/addon/main.go b/hack/cmd/addon/main.go index b991eb04..f05f12ce 100644 --- a/hack/cmd/addon/main.go +++ b/hack/cmd/addon/main.go @@ -97,6 +97,8 @@ func main() { } return }) + + addon.Error("Warning", "Test warning.") } // diff --git a/migration/v6/migrate.go b/migration/v6/migrate.go index d29ac92b..ee7cf665 100644 --- a/migration/v6/migrate.go +++ b/migration/v6/migrate.go @@ -1,6 +1,7 @@ package v6 import ( + "encoding/json" "github.com/jortel/go-utils/logr" "github.com/konveyor/tackle2-hub/migration/v6/model" "gorm.io/gorm" @@ -21,9 +22,76 @@ func (r Migration) Apply(db *gorm.DB) (err error) { return } err = db.AutoMigrate(r.Models()...) + if err != nil { + return + } + err = r.taskReportError(db) + if err != nil { + return + } + err = r.taskError(db) + if err != nil { + return + } return } func (r Migration) Models() []interface{} { return model.All() } + +func (r Migration) taskError(db *gorm.DB) (err error) { + type M struct { + model.Task + Error string + } + var list []M + err = db.Find(&M{}, &list).Error + if err != nil { + return + } + for i := range list { + m := &list[i] + if m.Error == "" { + continue + } + m.Errors, _ = json.Marshal( + []model.TaskError{ + { + Severity: "Error", + Description: m.Error, + }, + }) + } + m := db.Migrator() + err = m.DropColumn(&model.Task{}, "Error") + return +} + +func (r Migration) taskReportError(db *gorm.DB) (err error) { + type M struct { + model.TaskReport + Error string + } + var list []M + err = db.Find(&M{}, &list).Error + if err != nil { + return + } + for i := range list { + m := &list[i] + if m.Error == "" { + continue + } + m.Errors, _ = json.Marshal( + []model.TaskError{ + { + Severity: "Error", + Description: m.Error, + }, + }) + } + m := db.Migrator() + err = m.DropColumn(&model.TaskReport{}, "Error") + return +} diff --git a/migration/v6/model/pkg.go b/migration/v6/model/pkg.go index 1d5a566b..1958d277 100644 --- a/migration/v6/model/pkg.go +++ b/migration/v6/model/pkg.go @@ -29,7 +29,6 @@ type Stakeholder = model.Stakeholder type StakeholderGroup = model.StakeholderGroup type Tag = model.Tag type TagCategory = model.TagCategory -type TaskReport = model.TaskReport type Ticket = model.Ticket type Tracker = model.Tracker type ApplicationTag = model.ApplicationTag diff --git a/migration/v6/model/task.go b/migration/v6/model/task.go index bd505627..44539cd3 100644 --- a/migration/v6/model/task.go +++ b/migration/v6/model/task.go @@ -2,6 +2,7 @@ package model import ( "encoding/json" + "fmt" "gorm.io/gorm" "time" ) @@ -21,7 +22,7 @@ type Task struct { Started *time.Time Terminated *time.Time State string `gorm:"index"` - Events JSON + Errors JSON Pod string `gorm:"index"` Retries int Canceled bool @@ -45,18 +46,14 @@ func (m *Task) BeforeCreate(db *gorm.DB) (err error) { } // -// Event appends an event. -func (m *Task) Event(kind, origin, description string) { - var events []TaskEvent - _ = json.Unmarshal(m.Events, &events) - events = append( - events, - TaskEvent{ - Kind: kind, - Origin: origin, - Description: description, - }) - m.Events, _ = json.Marshal(events) +// Error appends an error. +func (m *Task) Error(severity, description string, x ...interface{}) { + var list []TaskError + description = fmt.Sprintf(description, x...) + te := TaskError{Severity: severity, Description: description} + _ = json.Unmarshal(m.Errors, &list) + list = append(list, te) + m.Errors, _ = json.Marshal(list) } // @@ -75,9 +72,20 @@ type TTL struct { } // -// TaskEvent used in Task.Errors. -type TaskEvent struct { - Kind string `json:"kind"` - Origin string `json:"origin,omitempty"` +// TaskError used in Task.Errors. +type TaskError struct { + Severity string `json:"severity"` Description string `json:"description"` } + +type TaskReport struct { + Model + Status string + Errors JSON + Total int + Completed int + Activity JSON `gorm:"type:json"` + Result JSON `gorm:"type:json"` + TaskID uint `gorm:"<-:create;uniqueIndex"` + Task *Task +} diff --git a/task/manager.go b/task/manager.go index 7fcf1573..fd753be9 100644 --- a/task/manager.go +++ b/task/manager.go @@ -132,7 +132,7 @@ func (m *Manager) startReady() { mark := time.Now() task.State = Failed task.Terminated = &mark - task.Event("Error", "Hub", "Hub is disconnected.") + task.Error("Error", "Hub is disconnected.") sErr := m.DB.Save(task).Error Log.Error(sErr, "") continue @@ -147,7 +147,6 @@ func (m *Manager) startReady() { ready := task if m.postpone(ready, list) { ready.State = Postponed - ready.Event("Warning", "Hub", Postponed) Log.Info("Task postponed.", "id", ready.ID) sErr := m.DB.Save(ready).Error Log.Error(sErr, "") @@ -158,7 +157,7 @@ func (m *Manager) startReady() { err := rt.Run(m.Client) if err != nil { if errors.Is(err, &AddonNotFound{}) { - ready.Event("Error", "Hub", err.Error()) + ready.Error("Error", err.Error()) ready.State = Failed sErr := m.DB.Save(ready).Error Log.Error(sErr, "") @@ -270,7 +269,7 @@ func (r *Task) Run(client k8s.Client) (err error) { mark := time.Now() defer func() { if err != nil { - r.Event("Error", "Hub", err.Error()) + r.Error("Error", err.Error()) r.Terminated = &mark r.State = Failed } @@ -355,7 +354,7 @@ func (r *Task) Reflect(client k8s.Client) (err error) { r.State = Succeeded r.Terminated = &mark case core.PodFailed: - r.Event("Error", "k8s", pod.Status.Message) + r.Error("Error", "Pod failed: %s", pod.Status.Message) if r.Retries < Settings.Hub.Task.Retries { _ = client.Delete(context.TODO(), pod) r.Pod = "" From 66e8e162f4cb9cfeb4b1ea84f25c6faadb6392f4 Mon Sep 17 00:00:00 2001 From: Jeff Ortel Date: Thu, 29 Jun 2023 07:52:59 -0700 Subject: [PATCH 3/5] checkpoint --- migration/v6/migrate.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/migration/v6/migrate.go b/migration/v6/migrate.go index ee7cf665..91e782b8 100644 --- a/migration/v6/migrate.go +++ b/migration/v6/migrate.go @@ -46,6 +46,7 @@ func (r Migration) taskError(db *gorm.DB) (err error) { Error string } var list []M + db = db.Table("Task") err = db.Find(&M{}, &list).Error if err != nil { return @@ -74,6 +75,7 @@ func (r Migration) taskReportError(db *gorm.DB) (err error) { Error string } var list []M + db = db.Table("TaskReport") err = db.Find(&M{}, &list).Error if err != nil { return From ec54a40d463bc53fe3f981ed34e3d66e74531801 Mon Sep 17 00:00:00 2001 From: Jeff Ortel Date: Thu, 29 Jun 2023 08:17:31 -0700 Subject: [PATCH 4/5] Reset Errors. Signed-off-by: Jeff Ortel --- migration/v6/model/task.go | 1 + 1 file changed, 1 insertion(+) diff --git a/migration/v6/model/task.go b/migration/v6/model/task.go index 44539cd3..c74f45db 100644 --- a/migration/v6/model/task.go +++ b/migration/v6/model/task.go @@ -37,6 +37,7 @@ func (m *Task) Reset() { m.Started = nil m.Terminated = nil m.Report = nil + m.Errors = nil } func (m *Task) BeforeCreate(db *gorm.DB) (err error) { From 1983e42e9de9020d3ce3b9f9607233555720cb94 Mon Sep 17 00:00:00 2001 From: Jeff Ortel Date: Fri, 30 Jun 2023 12:49:47 -0700 Subject: [PATCH 5/5] checkpoint Signed-off-by: Jeff Ortel --- hack/add/analysis.sh | 5 +++++ migration/v6/migrate.go | 14 ++++++-------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/hack/add/analysis.sh b/hack/add/analysis.sh index 3ab7a019..fe9996b6 100755 --- a/hack/add/analysis.sh +++ b/hack/add/analysis.sh @@ -149,6 +149,11 @@ indirect: "true" version: 4.6 " >> ${file} echo -n "--- +name: github.com/hybernate +indirect: "true" +version: 5.0 +" >> ${file} +echo -n "--- name: github.com/ejb indirect: "true" version: 4.3 diff --git a/migration/v6/migrate.go b/migration/v6/migrate.go index 91e782b8..de8f9e4b 100644 --- a/migration/v6/migrate.go +++ b/migration/v6/migrate.go @@ -41,13 +41,12 @@ func (r Migration) Models() []interface{} { } func (r Migration) taskError(db *gorm.DB) (err error) { - type M struct { + type Task struct { model.Task Error string } - var list []M - db = db.Table("Task") - err = db.Find(&M{}, &list).Error + var list []Task + err = db.Find(&Task{}, &list).Error if err != nil { return } @@ -70,13 +69,12 @@ func (r Migration) taskError(db *gorm.DB) (err error) { } func (r Migration) taskReportError(db *gorm.DB) (err error) { - type M struct { + type TaskReport struct { model.TaskReport Error string } - var list []M - db = db.Table("TaskReport") - err = db.Find(&M{}, &list).Error + var list []TaskReport + err = db.Find(&TaskReport{}, &list).Error if err != nil { return }