// Copyright Project Harbor Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package dao import ( "fmt" "time" "github.com/astaxie/beego/orm" "github.com/goharbor/harbor/src/common/dao" "github.com/goharbor/harbor/src/lib/log" "github.com/goharbor/harbor/src/replication/dao/models" ) // AddExecution ... func AddExecution(execution *models.Execution) (int64, error) { o := dao.GetOrmer() now := time.Now() execution.StartTime = now return o.Insert(execution) } // GetTotalOfExecutions returns the total count of replication execution func GetTotalOfExecutions(query ...*models.ExecutionQuery) (int64, error) { qs := executionQueryConditions(query...) return qs.Count() } // GetExecutions ... func GetExecutions(query ...*models.ExecutionQuery) ([]*models.Execution, error) { executions := []*models.Execution{} qs := executionQueryConditions(query...) if len(query) > 0 && query[0] != nil { qs = paginateForQuerySetter(qs, query[0].Page, query[0].Size) } qs = qs.OrderBy("-StartTime") _, err := qs.All(&executions) if err != nil || len(executions) == 0 { return executions, err } for _, e := range executions { fillExecution(e) } return executions, err } func executionQueryConditions(query ...*models.ExecutionQuery) orm.QuerySeter { qs := dao.GetOrmer().QueryTable(new(models.Execution)) if len(query) == 0 || query[0] == nil { return qs } q := query[0] if q.PolicyID != 0 { qs = qs.Filter("PolicyID", q.PolicyID) } if len(q.Trigger) > 0 { qs = qs.Filter("Trigger", q.Trigger) } if len(q.Statuses) > 0 { qs = qs.Filter("Status__in", q.Statuses) } return qs } // GetExecution ... func GetExecution(id int64) (*models.Execution, error) { o := dao.GetOrmer() t := models.Execution{ID: id} err := o.Read(&t) if err == orm.ErrNoRows { return nil, nil } if err != nil { return nil, err } fillExecution(&t) return &t, err } // fillExecution will fill the statistics data and status by tasks data func fillExecution(execution *models.Execution) error { if executionFinished(execution.Status) { return nil } o := dao.GetOrmer() sql := `select status, count(*) as c from replication_task where execution_id = ? group by status` queryParam := make([]interface{}, 1) queryParam = append(queryParam, execution.ID) dt := []*models.TaskStat{} count, err := o.Raw(sql, queryParam).QueryRows(&dt) if err != nil { log.Errorf("Query tasks error execution %d: %v", execution.ID, err) return err } if count == 0 { return nil } total := 0 for _, d := range dt { status, _ := getStatus(d.Status) updateStatusCount(execution, status, d.C) total += d.C } if execution.Total != total { log.Debugf("execution task count inconsistent and fixed, executionID=%d, execution.total=%d, tasks.count=%d", execution.ID, execution.Total, total) execution.Total = total } resetExecutionStatus(execution) return nil } func getStatus(status string) (string, error) { switch status { case models.TaskStatusInitialized, models.TaskStatusPending, models.TaskStatusInProgress: return models.ExecutionStatusInProgress, nil case models.TaskStatusSucceed: return models.ExecutionStatusSucceed, nil case models.TaskStatusStopped: return models.ExecutionStatusStopped, nil case models.TaskStatusFailed: return models.ExecutionStatusFailed, nil } return "", fmt.Errorf("Not support task status ") } func updateStatusCount(execution *models.Execution, status string, delta int) error { switch status { case models.ExecutionStatusInProgress: execution.InProgress += delta case models.ExecutionStatusSucceed: execution.Succeed += delta case models.ExecutionStatusStopped: execution.Stopped += delta case models.ExecutionStatusFailed: execution.Failed += delta } return nil } func resetExecutionStatus(execution *models.Execution) error { execution.Status = generateStatus(execution) if executionFinished(execution.Status) { o := dao.GetOrmer() sql := `select max(end_time) from replication_task where execution_id = ?` queryParam := make([]interface{}, 1) queryParam = append(queryParam, execution.ID) var et time.Time err := o.Raw(sql, queryParam).QueryRow(&et) if err != nil { log.Errorf("Query end_time from tasks error execution %d: %v", execution.ID, err) et = time.Now() } execution.EndTime = et } return nil } func generateStatus(execution *models.Execution) string { if execution.InProgress > 0 { return models.ExecutionStatusInProgress } else if execution.Failed > 0 { return models.ExecutionStatusFailed } else if execution.Stopped > 0 { return models.ExecutionStatusStopped } return models.ExecutionStatusSucceed } func executionFinished(status string) bool { if status == models.ExecutionStatusStopped || status == models.ExecutionStatusSucceed || status == models.ExecutionStatusFailed { return true } return false } // DeleteExecution ... func DeleteExecution(id int64) error { o := dao.GetOrmer() _, err := o.Delete(&models.Execution{ID: id}) return err } // DeleteAllExecutions ... func DeleteAllExecutions(policyID int64) error { o := dao.GetOrmer() _, err := o.Delete(&models.Execution{PolicyID: policyID}, "PolicyID") return err } // UpdateExecution ... func UpdateExecution(execution *models.Execution, props ...string) (int64, error) { if execution.ID == 0 { return 0, fmt.Errorf("execution ID is empty") } o := dao.GetOrmer() return o.Update(execution, props...) } // AddTask ... func AddTask(task *models.Task) (int64, error) { o := dao.GetOrmer() now := time.Now() task.StartTime = now return o.Insert(task) } // GetTask ... func GetTask(id int64) (*models.Task, error) { o := dao.GetOrmer() sql := `select * from replication_task where id = ?` var task models.Task if err := o.Raw(sql, id).QueryRow(&task); err != nil { if err == orm.ErrNoRows { return nil, nil } return nil, err } return &task, nil } // GetTotalOfTasks ... func GetTotalOfTasks(query ...*models.TaskQuery) (int64, error) { qs := taskQueryConditions(query...) return qs.Count() } // GetTasks ... func GetTasks(query ...*models.TaskQuery) ([]*models.Task, error) { tasks := []*models.Task{} qs := taskQueryConditions(query...) if len(query) > 0 && query[0] != nil { qs = paginateForQuerySetter(qs, query[0].Page, query[0].Size) } qs = qs.OrderBy("-StartTime") _, err := qs.All(&tasks) return tasks, err } func taskQueryConditions(query ...*models.TaskQuery) orm.QuerySeter { qs := dao.GetOrmer().QueryTable(new(models.Task)) if len(query) == 0 || query[0] == nil { return qs } q := query[0] if q.ExecutionID != 0 { qs = qs.Filter("ExecutionID", q.ExecutionID) } if len(q.JobID) > 0 { qs = qs.Filter("JobID", q.JobID) } if len(q.ResourceType) > 0 { qs = qs.Filter("ResourceType", q.ResourceType) } if len(q.Statuses) > 0 { qs = qs.Filter("Status__in", q.Statuses) } return qs } // DeleteTask ... func DeleteTask(id int64) error { o := dao.GetOrmer() _, err := o.Delete(&models.Task{ID: id}) return err } // DeleteAllTasks ... func DeleteAllTasks(executionID int64) error { o := dao.GetOrmer() _, err := o.Delete(&models.Task{ExecutionID: executionID}, "ExecutionID") return err } // UpdateTask ... func UpdateTask(task *models.Task, props ...string) (int64, error) { if task.ID == 0 { return 0, fmt.Errorf("task ID is empty") } o := dao.GetOrmer() return o.Update(task, props...) } // UpdateTaskStatus updates the status of task. // The implementation uses raw sql rather than QuerySetter.Filter... as QuerySetter // will generate sql like: // `UPDATE "replication_task" SET "end_time" = $1, "status" = $2 // WHERE "id" IN ( SELECT T0."id" FROM "replication_task" T0 WHERE T0."id" = $3 // AND T0."status" IN ($4, $5, $6))]` // which is not a "single" sql statement, this will cause issues when running in concurrency func UpdateTaskStatus(id int64, status string, statusRevision int64, statusCondition ...string) (int64, error) { params := []interface{}{} sql := `update replication_task set status = ?, status_revision = ?, end_time = ? ` params = append(params, status, statusRevision) var t time.Time // when the task is in final status, update the endtime // when the task re-runs again, the endtime should be cleared // so set the endtime to null if the task isn't in final status if taskFinished(status) { t = time.Now() } params = append(params, t) sql += fmt.Sprintf(`where id = ? and (status_revision < ? or status_revision = ? and status in (%s)) `, dao.ParamPlaceholderForIn(len(statusCondition))) params = append(params, id, statusRevision, statusRevision, statusCondition) result, err := dao.GetOrmer().Raw(sql, params...).Exec() if err != nil { return 0, err } n, _ := result.RowsAffected() if n > 0 { log.Debugf("update task status %d: -> %s", id, status) } return n, err } func taskFinished(status string) bool { return status == models.TaskStatusFailed || status == models.TaskStatusStopped || status == models.TaskStatusSucceed }