From 2863e687189a8c178443b5ea219d77a21815d235 Mon Sep 17 00:00:00 2001 From: chlins Date: Sun, 12 Jul 2020 13:43:16 +0800 Subject: [PATCH 1/2] feat: add task controller Signed-off-by: chlins --- src/controller/task/controller.go | 80 +++++++++++++ src/controller/task/controller_test.go | 82 ++++++++++++++ src/controller/task/execution_controller.go | 105 ++++++++++++++++++ .../task/execution_controller_test.go | 98 ++++++++++++++++ 4 files changed, 365 insertions(+) create mode 100644 src/controller/task/controller.go create mode 100644 src/controller/task/controller_test.go create mode 100644 src/controller/task/execution_controller.go create mode 100644 src/controller/task/execution_controller_test.go diff --git a/src/controller/task/controller.go b/src/controller/task/controller.go new file mode 100644 index 000000000..7d570cd0f --- /dev/null +++ b/src/controller/task/controller.go @@ -0,0 +1,80 @@ +// 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 task + +import ( + "context" + + "github.com/goharbor/harbor/src/lib/q" + "github.com/goharbor/harbor/src/pkg/task" +) + +var ( + // Ctl is a global task controller instance + Ctl = NewController() +) + +// Controller manages the task +type Controller interface { + // Create submits the job to jobservice and creates a corresponding task record. + // An execution must be created first and the task will be linked to it. + // The "extraAttrs" can be used to set the customized attributes. + Create(ctx context.Context, executionID int64, job *task.Job, extraAttrs ...map[string]interface{}) (id int64, err error) + // Stop the specified task. + Stop(ctx context.Context, id int64) (err error) + // Get the specified task. + Get(ctx context.Context, id int64) (task *task.Task, err error) + // List the tasks according to the query. + List(ctx context.Context, query *q.Query) (tasks []*task.Task, err error) + // Get the log of the specified task. + GetLog(ctx context.Context, id int64) (log []byte, err error) +} + +// NewController creates an instance of the default task controller. +func NewController() Controller { + return &controller{ + mgr: task.Mgr, + } +} + +// controller defines the default task controller. +type controller struct { + mgr task.Manager +} + +// Create submits the job to jobservice and creates a corresponding task record. +func (c *controller) Create(ctx context.Context, executionID int64, job *task.Job, extraAttrs ...map[string]interface{}) (id int64, err error) { + return c.mgr.Create(ctx, executionID, job, extraAttrs...) +} + +// Stop the specified task. +func (c *controller) Stop(ctx context.Context, id int64) (err error) { + return c.mgr.Stop(ctx, id) +} + +// Get the specified task. +func (c *controller) Get(ctx context.Context, id int64) (task *task.Task, err error) { + return c.mgr.Get(ctx, id) +} + +// List the tasks according to the query. +func (c *controller) List(ctx context.Context, query *q.Query) (tasks []*task.Task, err error) { + return c.mgr.List(ctx, query) +} + +// Get the log of the specified task. +func (c *controller) GetLog(ctx context.Context, id int64) (log []byte, err error) { + return c.mgr.GetLog(ctx, id) +} diff --git a/src/controller/task/controller_test.go b/src/controller/task/controller_test.go new file mode 100644 index 000000000..f223554a5 --- /dev/null +++ b/src/controller/task/controller_test.go @@ -0,0 +1,82 @@ +// 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 task + +import ( + "testing" + + model "github.com/goharbor/harbor/src/pkg/task" + "github.com/goharbor/harbor/src/testing/mock" + "github.com/goharbor/harbor/src/testing/pkg/task" + "github.com/stretchr/testify/suite" +) + +type controllerTestSuite struct { + suite.Suite + ctl *controller + mgr *task.FakeManager +} + +// TestControllerTestSuite tests controller. +func TestControllerTestSuite(t *testing.T) { + suite.Run(t, &controllerTestSuite{}) +} + +// SetupTest setups the testing env. +func (c *controllerTestSuite) SetupTest() { + c.mgr = &task.FakeManager{} + c.ctl = &controller{mgr: c.mgr} +} + +// TestCreate tests create. +func (c *controllerTestSuite) TestCreate() { + c.mgr.On("Create", mock.Anything, mock.Anything, mock.Anything).Return(int64(1), nil) + id, err := c.ctl.Create(nil, 1, nil) + c.NoError(err) + c.Equal(int64(1), id) +} + +// TestStop tests stop. +func (c *controllerTestSuite) TestStop() { + c.mgr.On("Stop", mock.Anything, mock.Anything).Return(nil) + err := c.ctl.Stop(nil, 1) + c.NoError(err) +} + +// TestGet tests get. +func (c *controllerTestSuite) TestGet() { + c.mgr.On("Get", mock.Anything, int64(1)).Return(&model.Task{ID: 1}, nil) + t, err := c.ctl.Get(nil, 1) + c.NoError(err) + c.Equal(int64(1), t.ID) +} + +// TestList tests list. +func (c *controllerTestSuite) TestList() { + c.mgr.On("List", mock.Anything, mock.Anything).Return([]*model.Task{ + {ID: 1}, {ID: 2}, + }, nil) + ts, err := c.ctl.List(nil, nil) + c.NoError(err) + c.Len(ts, 2) +} + +// TestGetLog tests get log. +func (c *controllerTestSuite) TestGetLog() { + c.mgr.On("GetLog", mock.Anything, mock.Anything).Return([]byte("logs"), nil) + l, err := c.ctl.GetLog(nil, 1) + c.NoError(err) + c.Equal([]byte("logs"), l) +} diff --git a/src/controller/task/execution_controller.go b/src/controller/task/execution_controller.go new file mode 100644 index 000000000..8fdb6e1fe --- /dev/null +++ b/src/controller/task/execution_controller.go @@ -0,0 +1,105 @@ +// 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 task + +import ( + "context" + + "github.com/goharbor/harbor/src/lib/q" + "github.com/goharbor/harbor/src/pkg/task" +) + +// ExecutionController manages the execution. +type ExecutionController interface { + // Create an execution. The "vendorType" specifies the type of vendor (e.g. replication, scan, gc, retention, etc.), + // and the "vendorID" specifies the ID of vendor if needed(e.g. policy ID for replication and retention). + // The "extraAttrs" can be used to set the customized attributes. + Create(ctx context.Context, vendorType string, vendorID int64, trigger string, + extraAttrs ...map[string]interface{}) (id int64, err error) + // MarkDone marks the status of the specified execution as success. + // It must be called to update the execution status if the created execution contains no tasks. + // In other cases, the execution status can be calculated from the referenced tasks automatically + // and no need to update it explicitly. + MarkDone(ctx context.Context, id int64, message string) (err error) + // MarkError marks the status of the specified execution as error. + // It must be called to update the execution status when failed to create tasks. + // In other cases, the execution status can be calculated from the referenced tasks automatically + // and no need to update it explicitly. + MarkError(ctx context.Context, id int64, message string) (err error) + // Stop all linked tasks of the specified execution. + Stop(ctx context.Context, id int64) (err error) + // Delete the specified execution and its tasks. + Delete(ctx context.Context, id int64) (err error) + // Get the specified execution. + Get(ctx context.Context, id int64) (execution *task.Execution, err error) + // List executions according to the query. + List(ctx context.Context, query *q.Query) (executions []*task.Execution, err error) +} + +// executionController defines the execution controller. +type executionController struct { + mgr task.ExecutionManager +} + +// NewController creates an instance of the default execution controller. +func NewExecutionController() ExecutionController { + return &executionController{ + mgr: task.ExecMgr, + } +} + +// Create an execution. The "vendorType" specifies the type of vendor (e.g. replication, scan, gc, retention, etc.), +// and the "vendorID" specifies the ID of vendor if needed(e.g. policy ID for replication and retention). +// The "extraAttrs" can be used to set the customized attributes. +func (ec *executionController) Create(ctx context.Context, vendorType string, vendorID int64, trigger string, + extraAttrs ...map[string]interface{}) (id int64, err error) { + return ec.mgr.Create(ctx, vendorType, vendorID, trigger, extraAttrs...) +} + +// MarkDone marks the status of the specified execution as success. +// It must be called to update the execution status if the created execution contains no tasks. +// In other cases, the execution status can be calculated from the referenced tasks automatically +// and no need to update it explicitly. +func (ec *executionController) MarkDone(ctx context.Context, id int64, message string) (err error) { + return ec.mgr.MarkDone(ctx, id, message) +} + +// MarkError marks the status of the specified execution as error. +// It must be called to update the execution status when failed to create tasks. +// In other cases, the execution status can be calculated from the referenced tasks automatically +// and no need to update it explicitly. +func (ec *executionController) MarkError(ctx context.Context, id int64, message string) (err error) { + return ec.mgr.MarkError(ctx, id, message) +} + +// Stop all linked tasks of the specified execution. +func (ec *executionController) Stop(ctx context.Context, id int64) (err error) { + return ec.mgr.Stop(ctx, id) +} + +// Delete the specified execution and its tasks. +func (ec *executionController) Delete(ctx context.Context, id int64) (err error) { + return ec.mgr.Delete(ctx, id) +} + +// Get the specified execution. +func (ec *executionController) Get(ctx context.Context, id int64) (execution *task.Execution, err error) { + return ec.mgr.Get(ctx, id) +} + +// List executions according to the query. +func (ec *executionController) List(ctx context.Context, query *q.Query) (executions []*task.Execution, err error) { + return ec.mgr.List(ctx, query) +} diff --git a/src/controller/task/execution_controller_test.go b/src/controller/task/execution_controller_test.go new file mode 100644 index 000000000..0c6202179 --- /dev/null +++ b/src/controller/task/execution_controller_test.go @@ -0,0 +1,98 @@ +// 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 task + +import ( + "testing" + + model "github.com/goharbor/harbor/src/pkg/task" + "github.com/goharbor/harbor/src/testing/mock" + "github.com/goharbor/harbor/src/testing/pkg/task" + "github.com/stretchr/testify/suite" +) + +type executionControllerTestSuite struct { + suite.Suite + ctl *executionController + mgr *task.FakeExecutionManager +} + +// TestExecutionControllerTestSuite tests controller. +func TestExecutionControllerTestSuite(t *testing.T) { + suite.Run(t, &executionControllerTestSuite{}) +} + +// SetupTest setups the testing env. +func (ec *executionControllerTestSuite) SetupTest() { + ec.mgr = &task.FakeExecutionManager{} + ec.ctl = &executionController{ + mgr: ec.mgr, + } +} + +// TestCreate tests create. +func (ec *executionControllerTestSuite) TestCreate() { + ec.mgr.On("Create", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(int64(1), nil) + id, err := ec.ctl.Create(nil, "", 1, "") + ec.NoError(err) + ec.Equal(int64(1), id) +} + +// TestMarkDown tests mark down. +func (ec *executionControllerTestSuite) TestMarkDone() { + ec.mgr.On("MarkDone", mock.Anything, mock.Anything, mock.Anything).Return(nil) + err := ec.ctl.MarkDone(nil, 1, "") + ec.NoError(err) +} + +// TestMarkError tests mark error. +func (ec *executionControllerTestSuite) TestMarkError() { + ec.mgr.On("MarkError", mock.Anything, mock.Anything, mock.Anything).Return(nil) + err := ec.ctl.MarkError(nil, 1, "") + ec.NoError(err) +} + +// TestStop tests stop. +func (ec *executionControllerTestSuite) TestStop() { + ec.mgr.On("Stop", mock.Anything, mock.Anything).Return(nil) + err := ec.ctl.Stop(nil, 1) + ec.NoError(err) +} + +// TestDelete tests delete. +func (ec *executionControllerTestSuite) TestDelete() { + ec.mgr.On("Delete", mock.Anything, mock.Anything).Return(nil) + err := ec.ctl.Delete(nil, 1) + ec.NoError(err) +} + +// TestGet tests get. +func (ec *executionControllerTestSuite) TestGet() { + ec.mgr.On("Get", mock.Anything, mock.Anything).Return(&model.Execution{ID: 1}, nil) + e, err := ec.ctl.Get(nil, 1) + ec.NoError(err) + ec.Equal(int64(1), e.ID) +} + +// TestList tests list. +func (ec *executionControllerTestSuite) TestList() { + ec.mgr.On("List", mock.Anything, mock.Anything).Return([]*model.Execution{ + {ID: 1}, + {ID: 2}, + }, nil) + es, err := ec.ctl.List(nil, nil) + ec.NoError(err) + ec.Len(es, 2) +} From 08bd46e125b98fd75ed8f52d5939d164d20ba350 Mon Sep 17 00:00:00 2001 From: chlins Date: Sun, 12 Jul 2020 15:53:08 +0800 Subject: [PATCH 2/2] feat: add preheat execution api handler Signed-off-by: chlins --- api/v2.0/swagger.yaml | 245 +++++++++++++++++- src/controller/task/controller.go | 9 - src/controller/task/controller_test.go | 8 - src/controller/task/execution_controller.go | 46 +--- .../task/execution_controller_test.go | 22 -- src/server/v2.0/handler/preheat.go | 198 +++++++++++++- src/server/v2.0/handler/preheat_test.go | 112 ++++++++ 7 files changed, 554 insertions(+), 86 deletions(-) diff --git a/api/v2.0/swagger.yaml b/api/v2.0/swagger.yaml index aedfaec7d..3677c87af 100644 --- a/api/v2.0/swagger.yaml +++ b/api/v2.0/swagger.yaml @@ -1013,6 +1013,151 @@ paths: $ref: '#/responses/403' '500': $ref: '#/responses/500' + /projects/{project_name}/preheat/policies/{preheat_policy_name}/executions: + get: + summary: List executions for the given policy + description: List executions for the given policy + tags: + - preheat + operationId: ListExecutions + parameters: + - $ref: '#/parameters/requestId' + - $ref: '#/parameters/projectName' + - $ref: '#/parameters/preheatPolicyName' + - $ref: '#/parameters/page' + - $ref: '#/parameters/pageSize' + - $ref: '#/parameters/query' + responses: + '200': + description: List executions success + schema: + type: array + items: + $ref: '#/definitions/Execution' + '400': + $ref: '#/responses/400' + '401': + $ref: '#/responses/401' + '404': + $ref: '#/responses/404' + '403': + $ref: '#/responses/403' + '500': + $ref: '#/responses/500' + /projects/{project_name}/preheat/policies/{preheat_policy_name}/executions/{execution_id}: + get: + summary: Get a execution detail by id + description: Get a execution detail by id + tags: + - preheat + operationId: GetExecution + parameters: + - $ref: '#/parameters/requestId' + - $ref: '#/parameters/projectName' + - $ref: '#/parameters/preheatPolicyName' + - $ref: '#/parameters/executionId' + responses: + '200': + description: Get execution success + schema: + $ref: '#/definitions/Execution' + '400': + $ref: '#/responses/400' + '401': + $ref: '#/responses/401' + '404': + $ref: '#/responses/404' + '403': + $ref: '#/responses/403' + '500': + $ref: '#/responses/500' + patch: + summary: Stop a execution + description: Stop a execution + tags: + - preheat + operationId: StopExecution + parameters: + - $ref: '#/parameters/requestId' + - $ref: '#/parameters/projectName' + - $ref: '#/parameters/preheatPolicyName' + - $ref: '#/parameters/executionId' + - name: execution + description: The data of execution + in: body + required: true + schema: + $ref: '#/definitions/Execution' + responses: + '200': + $ref: '#/responses/200' + '400': + $ref: '#/responses/400' + '401': + $ref: '#/responses/401' + '404': + $ref: '#/responses/404' + '403': + $ref: '#/responses/403' + '500': + $ref: '#/responses/500' + /projects/{project_name}/preheat/policies/{preheat_policy_name}/executions/{execution_id}/tasks: + get: + summary: List all the related tasks for the given execution + description: List all the related tasks for the given execution + tags: + - preheat + operationId: ListTasks + parameters: + - $ref: '#/parameters/requestId' + - $ref: '#/parameters/projectName' + - $ref: '#/parameters/preheatPolicyName' + - $ref: '#/parameters/executionId' + responses: + '200': + description: List tasks success + schema: + type: array + items: + $ref: '#/definitions/Task' + '400': + $ref: '#/responses/400' + '401': + $ref: '#/responses/401' + '404': + $ref: '#/responses/404' + '403': + $ref: '#/responses/403' + '500': + $ref: '#/responses/500' + /projects/{project_name}/preheat/policies/{preheat_policy_name}/executions/{execution_id}/tasks/{task_id}/logs: + get: + summary: Get the log text stream of the specified task for the given execution + description: Get the log text stream of the specified task for the given execution + tags: + - preheat + operationId: GetLog + parameters: + - $ref: '#/parameters/requestId' + - $ref: '#/parameters/projectName' + - $ref: '#/parameters/preheatPolicyName' + - $ref: '#/parameters/executionId' + - $ref: '#/parameters/taskId' + responses: + '200': + description: Get log success + schema: + type: string + '400': + $ref: '#/responses/400' + '401': + $ref: '#/responses/401' + '404': + $ref: '#/responses/404' + '403': + $ref: '#/responses/403' + '500': + $ref: '#/responses/500' parameters: query: name: q @@ -1085,7 +1230,18 @@ parameters: description: Preheat Policy Name required: true type: string - + executionId: + name: execution_id + in: path + description: Execution ID + required: true + type: integer + taskId: + name: task_id + in: path + description: Task ID + required: true + type: integer responses: '200': description: Success @@ -1601,3 +1757,90 @@ definitions: type: string format: date-time description: The Update Time of preheat policy + Metrics: + type: object + properties: + task_count: + type: integer + description: The count of task + success_task_count: + type: integer + description: The count of success task + error_task_count: + type: integer + description: The count of error task + pending_task_count: + type: integer + description: The count of pending task + running_task_count: + type: integer + description: The count of running task + scheduled_task_count: + type: integer + description: The count of scheduled task + stopped_task_count: + type: integer + description: The count of stopped task + Execution: + type: object + properties: + id: + type: integer + description: The ID of execution + vendor_type: + type: string + description: The vendor type of execution + vendor_id: + type: integer + description: The vendor id of execution + status: + type: string + description: The status of execution + status_message: + type: string + description: The status message of execution + metrics: + $ref: '#/definitions/Metrics' + trigger: + type: string + description: The trigger of execution + extra_attrs: + $ref: '#/definitions/ExtraAttrs' + start_time: + type: string + description: The start time of execution + end_time: + type: string + description: The end time of execution + Task: + type: object + properties: + id: + type: integer + description: The ID of task + execution_id: + type: integer + description: The ID of task execution + status: + type: string + description: The status of task + status_message: + type: string + description: The status message of task + run_count: + type: integer + description: The count of task run + extra_attrs: + $ref: '#/definitions/ExtraAttrs' + creation_time: + type: string + description: The creation time of task + update_time: + type: string + description: The update time of task + start_time: + type: string + description: The start time of task + end_time: + type: string + description: The end time of task diff --git a/src/controller/task/controller.go b/src/controller/task/controller.go index 7d570cd0f..bd8f80871 100644 --- a/src/controller/task/controller.go +++ b/src/controller/task/controller.go @@ -28,10 +28,6 @@ var ( // Controller manages the task type Controller interface { - // Create submits the job to jobservice and creates a corresponding task record. - // An execution must be created first and the task will be linked to it. - // The "extraAttrs" can be used to set the customized attributes. - Create(ctx context.Context, executionID int64, job *task.Job, extraAttrs ...map[string]interface{}) (id int64, err error) // Stop the specified task. Stop(ctx context.Context, id int64) (err error) // Get the specified task. @@ -54,11 +50,6 @@ type controller struct { mgr task.Manager } -// Create submits the job to jobservice and creates a corresponding task record. -func (c *controller) Create(ctx context.Context, executionID int64, job *task.Job, extraAttrs ...map[string]interface{}) (id int64, err error) { - return c.mgr.Create(ctx, executionID, job, extraAttrs...) -} - // Stop the specified task. func (c *controller) Stop(ctx context.Context, id int64) (err error) { return c.mgr.Stop(ctx, id) diff --git a/src/controller/task/controller_test.go b/src/controller/task/controller_test.go index f223554a5..b0ac6ca0a 100644 --- a/src/controller/task/controller_test.go +++ b/src/controller/task/controller_test.go @@ -40,14 +40,6 @@ func (c *controllerTestSuite) SetupTest() { c.ctl = &controller{mgr: c.mgr} } -// TestCreate tests create. -func (c *controllerTestSuite) TestCreate() { - c.mgr.On("Create", mock.Anything, mock.Anything, mock.Anything).Return(int64(1), nil) - id, err := c.ctl.Create(nil, 1, nil) - c.NoError(err) - c.Equal(int64(1), id) -} - // TestStop tests stop. func (c *controllerTestSuite) TestStop() { c.mgr.On("Stop", mock.Anything, mock.Anything).Return(nil) diff --git a/src/controller/task/execution_controller.go b/src/controller/task/execution_controller.go index 8fdb6e1fe..49b7821f1 100644 --- a/src/controller/task/execution_controller.go +++ b/src/controller/task/execution_controller.go @@ -21,23 +21,13 @@ import ( "github.com/goharbor/harbor/src/pkg/task" ) +var ( + // ExecutionCtl is a global execution controller. + ExecutionCtl = NewExecutionController() +) + // ExecutionController manages the execution. type ExecutionController interface { - // Create an execution. The "vendorType" specifies the type of vendor (e.g. replication, scan, gc, retention, etc.), - // and the "vendorID" specifies the ID of vendor if needed(e.g. policy ID for replication and retention). - // The "extraAttrs" can be used to set the customized attributes. - Create(ctx context.Context, vendorType string, vendorID int64, trigger string, - extraAttrs ...map[string]interface{}) (id int64, err error) - // MarkDone marks the status of the specified execution as success. - // It must be called to update the execution status if the created execution contains no tasks. - // In other cases, the execution status can be calculated from the referenced tasks automatically - // and no need to update it explicitly. - MarkDone(ctx context.Context, id int64, message string) (err error) - // MarkError marks the status of the specified execution as error. - // It must be called to update the execution status when failed to create tasks. - // In other cases, the execution status can be calculated from the referenced tasks automatically - // and no need to update it explicitly. - MarkError(ctx context.Context, id int64, message string) (err error) // Stop all linked tasks of the specified execution. Stop(ctx context.Context, id int64) (err error) // Delete the specified execution and its tasks. @@ -53,37 +43,13 @@ type executionController struct { mgr task.ExecutionManager } -// NewController creates an instance of the default execution controller. +// NewExecutionController creates an instance of the default execution controller. func NewExecutionController() ExecutionController { return &executionController{ mgr: task.ExecMgr, } } -// Create an execution. The "vendorType" specifies the type of vendor (e.g. replication, scan, gc, retention, etc.), -// and the "vendorID" specifies the ID of vendor if needed(e.g. policy ID for replication and retention). -// The "extraAttrs" can be used to set the customized attributes. -func (ec *executionController) Create(ctx context.Context, vendorType string, vendorID int64, trigger string, - extraAttrs ...map[string]interface{}) (id int64, err error) { - return ec.mgr.Create(ctx, vendorType, vendorID, trigger, extraAttrs...) -} - -// MarkDone marks the status of the specified execution as success. -// It must be called to update the execution status if the created execution contains no tasks. -// In other cases, the execution status can be calculated from the referenced tasks automatically -// and no need to update it explicitly. -func (ec *executionController) MarkDone(ctx context.Context, id int64, message string) (err error) { - return ec.mgr.MarkDone(ctx, id, message) -} - -// MarkError marks the status of the specified execution as error. -// It must be called to update the execution status when failed to create tasks. -// In other cases, the execution status can be calculated from the referenced tasks automatically -// and no need to update it explicitly. -func (ec *executionController) MarkError(ctx context.Context, id int64, message string) (err error) { - return ec.mgr.MarkError(ctx, id, message) -} - // Stop all linked tasks of the specified execution. func (ec *executionController) Stop(ctx context.Context, id int64) (err error) { return ec.mgr.Stop(ctx, id) diff --git a/src/controller/task/execution_controller_test.go b/src/controller/task/execution_controller_test.go index 0c6202179..2a956cf00 100644 --- a/src/controller/task/execution_controller_test.go +++ b/src/controller/task/execution_controller_test.go @@ -42,28 +42,6 @@ func (ec *executionControllerTestSuite) SetupTest() { } } -// TestCreate tests create. -func (ec *executionControllerTestSuite) TestCreate() { - ec.mgr.On("Create", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(int64(1), nil) - id, err := ec.ctl.Create(nil, "", 1, "") - ec.NoError(err) - ec.Equal(int64(1), id) -} - -// TestMarkDown tests mark down. -func (ec *executionControllerTestSuite) TestMarkDone() { - ec.mgr.On("MarkDone", mock.Anything, mock.Anything, mock.Anything).Return(nil) - err := ec.ctl.MarkDone(nil, 1, "") - ec.NoError(err) -} - -// TestMarkError tests mark error. -func (ec *executionControllerTestSuite) TestMarkError() { - ec.mgr.On("MarkError", mock.Anything, mock.Anything, mock.Anything).Return(nil) - err := ec.ctl.MarkError(nil, 1, "") - ec.NoError(err) -} - // TestStop tests stop. func (ec *executionControllerTestSuite) TestStop() { ec.mgr.On("Stop", mock.Anything, mock.Anything).Return(nil) diff --git a/src/server/v2.0/handler/preheat.go b/src/server/v2.0/handler/preheat.go index e78ae878b..87281f3c9 100644 --- a/src/server/v2.0/handler/preheat.go +++ b/src/server/v2.0/handler/preheat.go @@ -8,11 +8,18 @@ import ( "regexp" "time" + "github.com/goharbor/harbor/src/lib/q" + + "github.com/goharbor/harbor/src/jobservice/job" + + "github.com/goharbor/harbor/src/pkg/task" + "github.com/go-openapi/runtime/middleware" "github.com/go-openapi/strfmt" "github.com/goharbor/harbor/src/common/rbac" preheatCtl "github.com/goharbor/harbor/src/controller/p2p/preheat" projectCtl "github.com/goharbor/harbor/src/controller/project" + taskCtl "github.com/goharbor/harbor/src/controller/task" liberrors "github.com/goharbor/harbor/src/lib/errors" "github.com/goharbor/harbor/src/pkg/p2p/preheat/models/policy" instanceModel "github.com/goharbor/harbor/src/pkg/p2p/preheat/models/provider" @@ -24,9 +31,11 @@ import ( func newPreheatAPI() *preheatAPI { return &preheatAPI{ - preheatCtl: preheatCtl.Ctl, - projectCtl: projectCtl.Ctl, - enforcer: preheatCtl.Enf, + preheatCtl: preheatCtl.Ctl, + projectCtl: projectCtl.Ctl, + enforcer: preheatCtl.Enf, + executionCtl: taskCtl.ExecutionCtl, + taskCtl: taskCtl.Ctl, } } @@ -37,9 +46,11 @@ const nameRegex = "^[A-Za-z0-9]+(?:[._-][A-Za-z0-9]+)*$" type preheatAPI struct { BaseAPI - preheatCtl preheatCtl.Controller - projectCtl projectCtl.Controller - enforcer preheatCtl.Enforcer + preheatCtl preheatCtl.Controller + projectCtl projectCtl.Controller + enforcer preheatCtl.Enforcer + executionCtl taskCtl.ExecutionController + taskCtl taskCtl.Controller } func (api *preheatAPI) Prepare(ctx context.Context, operation string, params interface{}) middleware.Responder { @@ -473,3 +484,178 @@ func convertParamInstanceToModelInstance(model *models.Instance) (*instanceModel Vendor: model.Vendor, }, nil } + +// convertExecutionToPayload converts model execution to swagger model. +func convertExecutionToPayload(model *task.Execution) (*models.Execution, error) { + if model == nil { + return nil, errors.New("execution can not be nil") + } + + execution := &models.Execution{ + EndTime: model.EndTime.String(), + ExtraAttrs: model.ExtraAttrs, + ID: model.ID, + StartTime: model.StartTime.String(), + Status: model.Status, + StatusMessage: model.StatusMessage, + Trigger: model.Trigger, + VendorID: model.VendorID, + VendorType: model.VendorType, + } + if model.Metrics != nil { + execution.Metrics = &models.Metrics{ + ErrorTaskCount: model.Metrics.ErrorTaskCount, + PendingTaskCount: model.Metrics.PendingTaskCount, + RunningTaskCount: model.Metrics.RunningTaskCount, + ScheduledTaskCount: model.Metrics.ScheduledTaskCount, + StoppedTaskCount: model.Metrics.StoppedTaskCount, + SuccessTaskCount: model.Metrics.SuccessTaskCount, + TaskCount: model.Metrics.TaskCount, + } + } + + return execution, nil +} + +// GetExecution gets an execution. +func (api *preheatAPI) GetExecution(ctx context.Context, params operation.GetExecutionParams) middleware.Responder { + if err := api.RequireProjectAccess(ctx, params.ProjectName, rbac.ActionRead, rbac.ResourcePreatPolicy); err != nil { + return api.SendError(ctx, err) + } + + execution, err := api.executionCtl.Get(ctx, params.ExecutionID) + if err != nil { + return api.SendError(ctx, err) + } + + payload, err := convertExecutionToPayload(execution) + if err != nil { + return api.SendError(ctx, err) + } + + return operation.NewGetExecutionOK().WithPayload(payload) +} + +// ListExecutions lists executions. +func (api *preheatAPI) ListExecutions(ctx context.Context, params operation.ListExecutionsParams) middleware.Responder { + if err := api.RequireProjectAccess(ctx, params.ProjectName, rbac.ActionList, rbac.ResourcePreatPolicy); err != nil { + return api.SendError(ctx, err) + } + + project, err := api.projectCtl.GetByName(ctx, params.ProjectName) + if err != nil { + return api.SendError(ctx, err) + } + + policy, err := api.preheatCtl.GetPolicyByName(ctx, project.ProjectID, params.PreheatPolicyName) + if err != nil { + return api.SendError(ctx, err) + } + + query, err := api.BuildQuery(ctx, params.Q, params.Page, params.PageSize) + if err != nil { + return api.SendError(ctx, err) + } + + if query != nil { + query.Keywords["vendor_type"] = job.P2PPreheat + query.Keywords["vendor_id"] = policy.ID + } + + executions, err := api.executionCtl.List(ctx, query) + if err != nil { + return api.SendError(ctx, err) + } + + var payloads []*models.Execution + for _, exec := range executions { + p, err := convertExecutionToPayload(exec) + if err != nil { + return api.SendError(ctx, err) + } + payloads = append(payloads, p) + } + + return operation.NewListExecutionsOK().WithPayload(payloads) +} + +// StopExecution stops execution. +func (api *preheatAPI) StopExecution(ctx context.Context, params operation.StopExecutionParams) middleware.Responder { + if err := api.RequireProjectAccess(ctx, params.ProjectName, rbac.ActionUpdate, rbac.ResourcePreatPolicy); err != nil { + return api.SendError(ctx, err) + } + + if params.Execution.Status == "Stopped" { + err := api.executionCtl.Stop(ctx, params.ExecutionID) + if err != nil { + return api.SendError(ctx, err) + } + + return operation.NewStopExecutionOK() + } + + return api.SendError(ctx, fmt.Errorf("param status invalid: %#v", params.Execution)) +} + +// convertTaskToPayload converts task to swagger model. +func convertTaskToPayload(model *task.Task) (*models.Task, error) { + if model == nil { + return nil, errors.New("task model can not be nil") + } + + return &models.Task{ + CreationTime: model.CreationTime.String(), + EndTime: model.EndTime.String(), + ExecutionID: model.ExecutionID, + ExtraAttrs: model.ExtraAttrs, + ID: model.ID, + RunCount: int64(model.RunCount), + StartTime: model.StartTime.String(), + Status: model.Status, + StatusMessage: model.StatusMessage, + UpdateTime: model.UpdateTime.String(), + }, nil +} + +// ListTasks lists tasks. +func (api *preheatAPI) ListTasks(ctx context.Context, params operation.ListTasksParams) middleware.Responder { + if err := api.RequireProjectAccess(ctx, params.ProjectName, rbac.ActionList, rbac.ResourcePreatPolicy); err != nil { + return api.SendError(ctx, err) + } + + query := &q.Query{ + Keywords: map[string]interface{}{ + "execution_id": params.ExecutionID, + }, + } + + tasks, err := api.taskCtl.List(ctx, query) + if err != nil { + return api.SendError(ctx, err) + } + + var payloads []*models.Task + for _, task := range tasks { + p, err := convertTaskToPayload(task) + if err != nil { + return api.SendError(ctx, err) + } + payloads = append(payloads, p) + } + + return operation.NewListTasksOK().WithPayload(payloads) +} + +// GetLog gets log. +func (api *preheatAPI) GetLog(ctx context.Context, params operation.GetLogParams) middleware.Responder { + if err := api.RequireProjectAccess(ctx, params.ProjectName, rbac.ActionRead, rbac.ResourcePreatPolicy); err != nil { + return api.SendError(ctx, err) + } + + l, err := api.taskCtl.GetLog(ctx, params.TaskID) + if err != nil { + return api.SendError(ctx, err) + } + + return operation.NewGetLogOK().WithPayload(string(l)) +} diff --git a/src/server/v2.0/handler/preheat_test.go b/src/server/v2.0/handler/preheat_test.go index 4936ecb19..d4f0c6f4d 100644 --- a/src/server/v2.0/handler/preheat_test.go +++ b/src/server/v2.0/handler/preheat_test.go @@ -5,6 +5,8 @@ import ( "testing" "time" + "github.com/goharbor/harbor/src/pkg/task" + "github.com/go-openapi/strfmt" "github.com/goharbor/harbor/src/pkg/p2p/preheat/models/policy" instanceModel "github.com/goharbor/harbor/src/pkg/p2p/preheat/models/provider" @@ -289,3 +291,113 @@ func Test_convertParamInstanceToModelInstance(t *testing.T) { }) } } + +func Test_convertExecutionToPayload(t *testing.T) { + tests := []struct { + name string + input *task.Execution + expect *models.Execution + shouldErr bool + }{ + { + name: "nil model", + input: nil, + expect: nil, + shouldErr: true, + }, + { + name: "should ok", + input: &task.Execution{ + ID: 1, + VendorType: "p2p", + VendorID: 1, + Status: "", + StatusMessage: "", + Metrics: nil, + Trigger: "", + ExtraAttrs: nil, + StartTime: time.Time{}, + EndTime: time.Time{}, + }, + expect: &models.Execution{ + EndTime: "0001-01-01 00:00:00 +0000 UTC", + ExtraAttrs: nil, + ID: 1, + Metrics: nil, + StartTime: "0001-01-01 00:00:00 +0000 UTC", + Status: "", + StatusMessage: "", + Trigger: "", + VendorID: 1, + VendorType: "p2p", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := convertExecutionToPayload(tt.input) + if (err != nil) != tt.shouldErr { + t.Errorf("convertExecutionToPayload() error = %v, wantErr %v", err, tt.shouldErr) + return + } + if !reflect.DeepEqual(got, tt.expect) { + t.Errorf("convertExecutionToPayload() = %v, want %v", got, tt.expect) + } + }) + } +} + +func Test_convertTaskToPayload(t *testing.T) { + tests := []struct { + name string + input *task.Task + expect *models.Task + shouldErr bool + }{ + { + name: "nil model", + input: nil, + expect: nil, + shouldErr: true, + }, + { + name: "should ok", + input: &task.Task{ + ID: 0, + ExecutionID: 0, + Status: "", + StatusMessage: "", + RunCount: 0, + ExtraAttrs: nil, + CreationTime: time.Time{}, + StartTime: time.Time{}, + UpdateTime: time.Time{}, + EndTime: time.Time{}, + }, + expect: &models.Task{ + CreationTime: "0001-01-01 00:00:00 +0000 UTC", + EndTime: "0001-01-01 00:00:00 +0000 UTC", + ExecutionID: 0, + ExtraAttrs: nil, + ID: 0, + RunCount: 0, + StartTime: "0001-01-01 00:00:00 +0000 UTC", + Status: "", + StatusMessage: "", + UpdateTime: "0001-01-01 00:00:00 +0000 UTC", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := convertTaskToPayload(tt.input) + if (err != nil) != tt.shouldErr { + t.Errorf("convertTaskToPayload() error = %v, wantErr %v", err, tt.shouldErr) + return + } + if !reflect.DeepEqual(got, tt.expect) { + t.Errorf("convertTaskToPayload() = %v, want %v", got, tt.expect) + } + }) + } +}