Merge pull request #6093 from cd1989/replication-record-id

Add op uuid to image replication
This commit is contained in:
Steven Zou 2018-11-30 14:54:43 +08:00 committed by GitHub
commit ec2ad4d0b8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 114 additions and 14 deletions

View File

@ -1506,6 +1506,11 @@ paths:
format: int format: int
required: true required: true
description: The ID of the policy that triggered this job. description: The ID of the policy that triggered this job.
- name: op_uuid
in: query
type: string
required: false
description: The UUID of one trigger of replication policy.
- name: num - name: num
in: query in: query
type: integer type: integer
@ -2018,6 +2023,8 @@ paths:
responses: responses:
'200': '200':
description: Trigger the replication successfully. description: Trigger the replication successfully.
schema:
$ref: '#/definitions/ReplicationResponse'
'401': '401':
description: User need to log in first. description: User need to log in first.
'404': '404':
@ -4083,6 +4090,12 @@ definitions:
policy_id: policy_id:
type: integer type: integer
description: The ID of replication policy description: The ID of replication policy
ReplicationResponse:
type: object
properties:
uuid:
type: string
description: UUID of the replication
RepositoryDescription: RepositoryDescription:
type: object type: object
properties: properties:

View File

@ -0,0 +1 @@
ALTER TABLE replication_job ADD COLUMN op_uuid varchar(64);

View File

@ -356,6 +356,9 @@ func repJobQueryConditions(query ...*models.RepJobQuery) orm.QuerySeter {
if q.PolicyID != 0 { if q.PolicyID != 0 {
qs = qs.Filter("PolicyID", q.PolicyID) qs = qs.Filter("PolicyID", q.PolicyID)
} }
if len(q.OpUUID) > 0 {
qs = qs.Filter("OpUUID__exact", q.OpUUID)
}
if len(q.Repository) > 0 { if len(q.Repository) > 0 {
qs = qs.Filter("Repository__icontains", q.Repository) qs = qs.Filter("Repository__icontains", q.Repository)
} }

View File

@ -54,15 +54,15 @@ type RepPolicy struct {
// RepJob is the model for a replication job, which is the execution unit on job service, currently it is used to transfer/remove // RepJob is the model for a replication job, which is the execution unit on job service, currently it is used to transfer/remove
// a repository to/from a remote registry instance. // a repository to/from a remote registry instance.
type RepJob struct { type RepJob struct {
ID int64 `orm:"pk;auto;column(id)" json:"id"` ID int64 `orm:"pk;auto;column(id)" json:"id"`
Status string `orm:"column(status)" json:"status"` Status string `orm:"column(status)" json:"status"`
Repository string `orm:"column(repository)" json:"repository"` Repository string `orm:"column(repository)" json:"repository"`
PolicyID int64 `orm:"column(policy_id)" json:"policy_id"` PolicyID int64 `orm:"column(policy_id)" json:"policy_id"`
Operation string `orm:"column(operation)" json:"operation"` OpUUID string `orm:"column(op_uuid)" json:"op_uuid"`
Tags string `orm:"column(tags)" json:"-"` Operation string `orm:"column(operation)" json:"operation"`
TagList []string `orm:"-" json:"tags"` Tags string `orm:"column(tags)" json:"-"`
UUID string `orm:"column(job_uuid)" json:"-"` TagList []string `orm:"-" json:"tags"`
// Policy RepPolicy `orm:"-" json:"policy"` UUID string `orm:"column(job_uuid)" json:"-"`
CreationTime time.Time `orm:"column(creation_time);auto_now_add" json:"creation_time"` CreationTime time.Time `orm:"column(creation_time);auto_now_add" json:"creation_time"`
UpdateTime time.Time `orm:"column(update_time);auto_now" json:"update_time"` UpdateTime time.Time `orm:"column(update_time);auto_now" json:"update_time"`
} }
@ -126,6 +126,7 @@ func (r *RepPolicy) TableName() string {
// RepJobQuery holds query conditions for replication job // RepJobQuery holds query conditions for replication job
type RepJobQuery struct { type RepJobQuery struct {
PolicyID int64 PolicyID int64
OpUUID string
Repository string Repository string
Statuses []string Statuses []string
Operations []string Operations []string

View File

@ -23,6 +23,11 @@ type Replication struct {
PolicyID int64 `json:"policy_id"` PolicyID int64 `json:"policy_id"`
} }
// ReplicationResponse describes response of a replication request, it gives
type ReplicationResponse struct {
UUID string `json:"uuid"`
}
// Valid ... // Valid ...
func (r *Replication) Valid(v *validation.Validation) { func (r *Replication) Valid(v *validation.Validation) {
if r.PolicyID <= 0 { if r.PolicyID <= 0 {

View File

@ -17,6 +17,7 @@ package api
import ( import (
"fmt" "fmt"
"net/http" "net/http"
"strings"
"github.com/goharbor/harbor/src/common/dao" "github.com/goharbor/harbor/src/common/dao"
"github.com/goharbor/harbor/src/common/models" "github.com/goharbor/harbor/src/common/models"
@ -26,6 +27,8 @@ import (
"github.com/goharbor/harbor/src/replication/core" "github.com/goharbor/harbor/src/replication/core"
"github.com/goharbor/harbor/src/replication/event/notification" "github.com/goharbor/harbor/src/replication/event/notification"
"github.com/goharbor/harbor/src/replication/event/topic" "github.com/goharbor/harbor/src/replication/event/topic"
"github.com/docker/distribution/uuid"
) )
// ReplicationAPI handles API calls for replication // ReplicationAPI handles API calls for replication
@ -78,16 +81,27 @@ func (r *ReplicationAPI) Post() {
return return
} }
if err = startReplication(replication.PolicyID); err != nil { opUUID, err := startReplication(replication.PolicyID)
if err != nil {
r.HandleInternalServerError(fmt.Sprintf("failed to publish replication topic for policy %d: %v", replication.PolicyID, err)) r.HandleInternalServerError(fmt.Sprintf("failed to publish replication topic for policy %d: %v", replication.PolicyID, err))
return return
} }
log.Infof("replication signal for policy %d sent", replication.PolicyID) log.Infof("replication signal for policy %d sent", replication.PolicyID)
r.Data["json"] = api_models.ReplicationResponse{
UUID: opUUID,
}
r.ServeJSON()
} }
func startReplication(policyID int64) error { // startReplication triggers a replication and return the uuid of this replication.
return notifier.Publish(topic.StartReplicationTopic, func startReplication(policyID int64) (string, error) {
opUUID := strings.Replace(uuid.Generate().String(), "-", "", -1)
return opUUID, notifier.Publish(topic.StartReplicationTopic,
notification.StartReplicationNotification{ notification.StartReplicationNotification{
PolicyID: policyID, PolicyID: policyID,
Metadata: map[string]interface{}{
"op_uuid": opUUID,
},
}) })
} }

View File

@ -94,6 +94,7 @@ func (ra *RepJobAPI) List() {
query.Repository = ra.GetString("repository") query.Repository = ra.GetString("repository")
query.Statuses = ra.GetStrings("status") query.Statuses = ra.GetStrings("status")
query.OpUUID = ra.GetString("op_uuid")
startTimeStr := ra.GetString("start_time") startTimeStr := ra.GetString("start_time")
if len(startTimeStr) != 0 { if len(startTimeStr) != 0 {

View File

@ -192,7 +192,7 @@ func (pa *RepPolicyAPI) Post() {
if policy.ReplicateExistingImageNow { if policy.ReplicateExistingImageNow {
go func() { go func() {
if err = startReplication(id); err != nil { if _, err = startReplication(id); err != nil {
log.Errorf("failed to send replication signal for policy %d: %v", id, err) log.Errorf("failed to send replication signal for policy %d: %v", id, err)
return return
} }
@ -304,7 +304,7 @@ func (pa *RepPolicyAPI) Put() {
if policy.ReplicateExistingImageNow { if policy.ReplicateExistingImageNow {
go func() { go func() {
if err = startReplication(id); err != nil { if _, err = startReplication(id); err != nil {
log.Errorf("failed to send replication signal for policy %d: %v", id, err) log.Errorf("failed to send replication signal for policy %d: %v", id, err)
return return
} }

View File

@ -16,6 +16,8 @@ package core
import ( import (
"fmt" "fmt"
"reflect"
"strings"
common_models "github.com/goharbor/harbor/src/common/models" common_models "github.com/goharbor/harbor/src/common/models"
"github.com/goharbor/harbor/src/common/utils/log" "github.com/goharbor/harbor/src/common/utils/log"
@ -27,6 +29,8 @@ import (
"github.com/goharbor/harbor/src/replication/source" "github.com/goharbor/harbor/src/replication/source"
"github.com/goharbor/harbor/src/replication/target" "github.com/goharbor/harbor/src/replication/target"
"github.com/goharbor/harbor/src/replication/trigger" "github.com/goharbor/harbor/src/replication/trigger"
"github.com/docker/distribution/uuid"
) )
// Controller defines the methods that a replicatoin controllter should implement // Controller defines the methods that a replicatoin controllter should implement
@ -220,9 +224,16 @@ func (ctl *DefaultController) Replicate(policyID int64, metadata ...map[string]i
targets = append(targets, target) targets = append(targets, target)
} }
// Get operation uuid from metadata, if none provided, generate one.
opUUID, err := getOpUUID(metadata...)
if err != nil {
return err
}
// submit the replication // submit the replication
return ctl.replicator.Replicate(&replicator.Replication{ return ctl.replicator.Replicate(&replicator.Replication{
PolicyID: policyID, PolicyID: policyID,
OpUUID: opUUID,
Candidates: candidates, Candidates: candidates,
Targets: targets, Targets: targets,
}) })
@ -290,3 +301,26 @@ func buildFilterChain(policy *models.ReplicationPolicy, sourcer *source.Sourcer)
return source.NewDefaultFilterChain(filters) return source.NewDefaultFilterChain(filters)
} }
// getOpUUID get operation uuid from metadata or generate one if none found.
func getOpUUID(metadata ...map[string]interface{}) (string, error) {
if len(metadata) <= 0 {
return strings.Replace(uuid.Generate().String(), "-", "", -1), nil
}
opUUID, ok := metadata[0]["op_uuid"]
if !ok {
return strings.Replace(uuid.Generate().String(), "-", "", -1), nil
}
id, ok := opUUID.(string)
if !ok {
return "", fmt.Errorf("operation uuid should have type 'string', but got '%s'", reflect.TypeOf(opUUID).Name())
}
if id == "" {
return "", fmt.Errorf("provided operation uuid is empty")
}
return id, nil
}

View File

@ -156,3 +156,26 @@ func TestBuildFilterChain(t *testing.T) {
chain := buildFilterChain(policy, sourcer) chain := buildFilterChain(policy, sourcer)
assert.Equal(t, 3, len(chain.Filters())) assert.Equal(t, 3, len(chain.Filters()))
} }
func TestGetOpUUID(t *testing.T) {
uuid, err := getOpUUID()
assert.Nil(t, err)
assert.NotEmpty(t, uuid)
uuid, err = getOpUUID(map[string]interface{}{
"name": "test",
})
assert.Nil(t, err)
assert.NotEmpty(t, uuid)
uuid, err = getOpUUID(map[string]interface{}{
"op_uuid": 0,
})
assert.NotNil(t, err)
uuid, err = getOpUUID(map[string]interface{}{
"op_uuid": "0",
})
assert.Nil(t, err)
assert.Equal(t, uuid, "0")
}

View File

@ -30,6 +30,7 @@ import (
// Replication holds information for a replication // Replication holds information for a replication
type Replication struct { type Replication struct {
PolicyID int64 PolicyID int64
OpUUID string
Candidates []models.FilterItem Candidates []models.FilterItem
Targets []*common_models.RepTarget Targets []*common_models.RepTarget
Operation string Operation string
@ -60,6 +61,9 @@ func (d *DefaultReplicator) Replicate(replication *Replication) error {
operation := "" operation := ""
for _, candidate := range replication.Candidates { for _, candidate := range replication.Candidates {
strs := strings.SplitN(candidate.Value, ":", 2) strs := strings.SplitN(candidate.Value, ":", 2)
if len(strs) != 2 {
return fmt.Errorf("malforld image '%s'", candidate.Value)
}
repositories[strs[0]] = append(repositories[strs[0]], strs[1]) repositories[strs[0]] = append(repositories[strs[0]], strs[1])
operation = candidate.Operation operation = candidate.Operation
} }
@ -69,6 +73,7 @@ func (d *DefaultReplicator) Replicate(replication *Replication) error {
// create job in database // create job in database
id, err := dao.AddRepJob(common_models.RepJob{ id, err := dao.AddRepJob(common_models.RepJob{
PolicyID: replication.PolicyID, PolicyID: replication.PolicyID,
OpUUID: replication.OpUUID,
Repository: repository, Repository: repository,
TagList: tags, TagList: tags,
Operation: operation, Operation: operation,