harbor/api/replication_policy.go

390 lines
12 KiB
Go

/*
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
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 api
import (
"fmt"
"net/http"
"strconv"
"github.com/vmware/harbor/dao"
"github.com/vmware/harbor/models"
"github.com/vmware/harbor/utils/log"
)
// RepPolicyAPI handles /api/replicationPolicies /api/replicationPolicies/:id/enablement
type RepPolicyAPI struct {
BaseAPI
}
// Prepare validates whether the user has system admin role
func (pa *RepPolicyAPI) Prepare() {
uid := pa.ValidateUser()
var err error
isAdmin, err := dao.IsAdminRole(uid)
if err != nil {
log.Errorf("Failed to Check if the user is admin, error: %v, uid: %d", err, uid)
}
if !isAdmin {
pa.CustomAbort(http.StatusForbidden, "")
}
}
// Get ...
func (pa *RepPolicyAPI) Get() {
id := pa.GetIDFromURL()
policy, err := dao.GetRepPolicy(id)
if err != nil {
log.Errorf("failed to get policy %d: %v", id, err)
pa.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
}
if policy == nil {
pa.CustomAbort(http.StatusNotFound, http.StatusText(http.StatusNotFound))
}
pa.Data["json"] = policy
pa.ServeJSON()
}
// List filters policies by name and project_id, if name and project_id
// are nil, List returns all policies
func (pa *RepPolicyAPI) List() {
name := pa.GetString("name")
projectIDStr := pa.GetString("project_id")
var projectID int64
var err error
if len(projectIDStr) != 0 {
projectID, err = strconv.ParseInt(projectIDStr, 10, 64)
if err != nil || projectID <= 0 {
pa.CustomAbort(http.StatusBadRequest, "invalid project ID")
}
}
policies, err := dao.FilterRepPolicies(name, projectID)
if err != nil {
log.Errorf("failed to filter policies %s project ID %d: %v", name, projectID, err)
pa.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
}
pa.Data["json"] = policies
pa.ServeJSON()
}
// Post creates a policy, and if it is enbled, the replication will be triggered right now.
func (pa *RepPolicyAPI) Post() {
policy := &models.RepPolicy{}
pa.DecodeJSONReqAndValidate(policy)
po, err := dao.GetRepPolicyByName(policy.Name)
if err != nil {
log.Errorf("failed to get policy %s: %v", policy.Name, err)
pa.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
}
if po != nil {
pa.CustomAbort(http.StatusConflict, "name is already used")
}
project, err := dao.GetProjectByID(policy.ProjectID)
if err != nil {
log.Errorf("failed to get project %d: %v", policy.ProjectID, err)
pa.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
}
if project == nil {
pa.CustomAbort(http.StatusBadRequest, fmt.Sprintf("project %d does not exist", policy.ProjectID))
}
target, err := dao.GetRepTarget(policy.TargetID)
if err != nil {
log.Errorf("failed to get target %d: %v", policy.TargetID, err)
pa.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
}
if target == nil {
pa.CustomAbort(http.StatusBadRequest, fmt.Sprintf("target %d does not exist", policy.TargetID))
}
policies, err := dao.GetRepPolicyByProjectAndTarget(policy.ProjectID, policy.TargetID)
if err != nil {
log.Errorf("failed to get policy [project ID: %d,targetID: %d]: %v", policy.ProjectID, policy.TargetID, err)
pa.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
}
if len(policies) > 0 {
pa.CustomAbort(http.StatusConflict, "policy already exists with the same project and target")
}
pid, err := dao.AddRepPolicy(*policy)
if err != nil {
log.Errorf("Failed to add policy to DB, error: %v", err)
pa.RenderError(http.StatusInternalServerError, "Internal Error")
return
}
if policy.Enabled == 1 {
go func() {
if err := TriggerReplication(pid, "", nil, models.RepOpTransfer); err != nil {
log.Errorf("failed to trigger replication of %d: %v", pid, err)
} else {
log.Infof("replication of %d triggered", pid)
}
}()
}
pa.Redirect(http.StatusCreated, strconv.FormatInt(pid, 10))
}
// Put modifies name, description, target and enablement of policy
func (pa *RepPolicyAPI) Put() {
id := pa.GetIDFromURL()
originalPolicy, err := dao.GetRepPolicy(id)
if err != nil {
log.Errorf("failed to get policy %d: %v", id, err)
pa.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
}
if originalPolicy == nil {
pa.CustomAbort(http.StatusNotFound, http.StatusText(http.StatusNotFound))
}
policy := &models.RepPolicy{}
pa.DecodeJSONReq(policy)
policy.ProjectID = originalPolicy.ProjectID
pa.Validate(policy)
// check duplicate name
if policy.Name != originalPolicy.Name {
po, err := dao.GetRepPolicyByName(policy.Name)
if err != nil {
log.Errorf("failed to get policy %s: %v", policy.Name, err)
pa.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
}
if po != nil {
pa.CustomAbort(http.StatusConflict, "name is already used")
}
}
if policy.TargetID != originalPolicy.TargetID {
//target of policy can not be modified when the policy is enabled
if originalPolicy.Enabled == 1 {
pa.CustomAbort(http.StatusBadRequest, "target of policy can not be modified when the policy is enabled")
}
// check the existance of target
target, err := dao.GetRepTarget(policy.TargetID)
if err != nil {
log.Errorf("failed to get target %d: %v", policy.TargetID, err)
pa.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
}
if target == nil {
pa.CustomAbort(http.StatusBadRequest, fmt.Sprintf("target %d does not exist", policy.TargetID))
}
// check duplicate policy with the same project and target
policies, err := dao.GetRepPolicyByProjectAndTarget(policy.ProjectID, policy.TargetID)
if err != nil {
log.Errorf("failed to get policy [project ID: %d,targetID: %d]: %v", policy.ProjectID, policy.TargetID, err)
pa.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
}
if len(policies) > 0 {
pa.CustomAbort(http.StatusConflict, "policy already exists with the same project and target")
}
}
policy.ID = id
/*
isTargetChanged := !(policy.TargetID == originalPolicy.TargetID)
isEnablementChanged := !(policy.Enabled == policy.Enabled)
var shouldStop, shouldTrigger bool
// if target and enablement are not changed, do nothing
if !isTargetChanged && !isEnablementChanged {
shouldStop = false
shouldTrigger = false
} else if !isTargetChanged && isEnablementChanged {
// target is not changed, but enablement is changed
if policy.Enabled == 0 {
shouldStop = true
shouldTrigger = false
} else {
shouldStop = false
shouldTrigger = true
}
} else if isTargetChanged && !isEnablementChanged {
// target is changed, but enablement is not changed
if policy.Enabled == 0 {
// enablement is 0, do nothing
shouldStop = false
shouldTrigger = false
} else {
// enablement is 1, so stop original target's jobs
// and trigger new target's jobs
shouldStop = true
shouldTrigger = true
}
} else {
// both target and enablement are changed
// enablement: 1 -> 0
if policy.Enabled == 0 {
shouldStop = true
shouldTrigger = false
} else {
shouldStop = false
shouldTrigger = true
}
}
if shouldStop {
if err := postReplicationAction(id, "stop"); err != nil {
log.Errorf("failed to stop replication of %d: %v", id, err)
pa.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
}
log.Infof("replication of %d has been stopped", id)
}
if err = dao.UpdateRepPolicy(policy); err != nil {
log.Errorf("failed to update policy %d: %v", id, err)
pa.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
}
if shouldTrigger {
go func() {
if err := TriggerReplication(id, "", nil, models.RepOpTransfer); err != nil {
log.Errorf("failed to trigger replication of %d: %v", id, err)
} else {
log.Infof("replication of %d triggered", id)
}
}()
}
*/
if err = dao.UpdateRepPolicy(policy); err != nil {
log.Errorf("failed to update policy %d: %v", id, err)
pa.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
}
if policy.Enabled != originalPolicy.Enabled && policy.Enabled == 1 {
go func() {
if err := TriggerReplication(id, "", nil, models.RepOpTransfer); err != nil {
log.Errorf("failed to trigger replication of %d: %v", id, err)
} else {
log.Infof("replication of %d triggered", id)
}
}()
}
}
type enablementReq struct {
Enabled int `json:"enabled"`
}
// UpdateEnablement changes the enablement of the policy
func (pa *RepPolicyAPI) UpdateEnablement() {
id := pa.GetIDFromURL()
policy, err := dao.GetRepPolicy(id)
if err != nil {
log.Errorf("failed to get policy %d: %v", id, err)
pa.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
}
if policy == nil {
pa.CustomAbort(http.StatusNotFound, http.StatusText(http.StatusNotFound))
}
e := enablementReq{}
pa.DecodeJSONReq(&e)
if e.Enabled != 0 && e.Enabled != 1 {
pa.RenderError(http.StatusBadRequest, "invalid enabled value")
return
}
if policy.Enabled == e.Enabled {
return
}
if err := dao.UpdateRepPolicyEnablement(id, e.Enabled); err != nil {
log.Errorf("Failed to update policy enablement in DB, error: %v", err)
pa.RenderError(http.StatusInternalServerError, "Internal Error")
return
}
if e.Enabled == 1 {
go func() {
if err := TriggerReplication(id, "", nil, models.RepOpTransfer); err != nil {
log.Errorf("failed to trigger replication of %d: %v", id, err)
} else {
log.Infof("replication of %d triggered", id)
}
}()
} else {
go func() {
if err := postReplicationAction(id, "stop"); err != nil {
log.Errorf("failed to stop replication of %d: %v", id, err)
} else {
log.Infof("try to stop replication of %d", id)
}
}()
}
}
// Delete : policies which are disabled and have no running jobs
// can be deleted
func (r *RepPolicyAPI) Delete() {
id := r.GetIDFromURL()
policy, err := dao.GetRepPolicy(id)
if err != nil {
log.Errorf("failed to get policy %d: %v", id, err)
r.CustomAbort(http.StatusInternalServerError, "")
}
if policy == nil || policy.Deleted == 1 {
r.CustomAbort(http.StatusNotFound, "")
}
if policy.Enabled == 1 {
r.CustomAbort(http.StatusPreconditionFailed, "plicy is enabled, can not be deleted")
}
jobs, err := dao.GetRepJobByPolicy(id)
if err != nil {
log.Errorf("failed to get jobs of policy %d: %v", id, err)
r.CustomAbort(http.StatusInternalServerError, "")
}
for _, job := range jobs {
if job.Status == models.JobRunning ||
job.Status == models.JobRetrying ||
job.Status == models.JobPending {
r.CustomAbort(http.StatusPreconditionFailed, "policy has running/retrying/pending jobs, can not be deleted")
}
}
if err = dao.DeleteRepPolicy(id); err != nil {
log.Errorf("failed to delete policy %d: %v", id, err)
r.CustomAbort(http.StatusInternalServerError, "")
}
}