From 52b26996158e39b19874e3ea26d1e3c67545815a Mon Sep 17 00:00:00 2001 From: wanyaoqi Date: Fri, 17 May 2019 14:04:07 +0800 Subject: [PATCH] snapshot policy --- cmd/climc/shell/disks.go | 27 ++ cmd/climc/shell/snapshot_policy.go | 76 ++++ pkg/apis/compute/snapshot.go | 14 + pkg/apis/compute/snapshot_const.go | 7 + pkg/cloudcommon/db/opslog.go | 24 +- pkg/cloudprovider/resources.go | 18 + pkg/cloudprovider/snapshot_policy.go | 25 ++ pkg/compute/models/cloudproviders.go | 1 + pkg/compute/models/cloudsync.go | 20 + pkg/compute/models/disks.go | 74 +++- pkg/compute/models/purge.go | 26 ++ pkg/compute/models/regiondrivers.go | 8 +- pkg/compute/models/snapshotpolicy.go | 406 ++++++++++++++++++ pkg/compute/regiondrivers/aliyun.go | 46 ++ pkg/compute/regiondrivers/base.go | 21 + pkg/compute/regiondrivers/managedvirtual.go | 74 ++++ pkg/compute/service/handlers.go | 1 + .../tasks/snapshot_policy_delete_task.go | 54 +++ .../tasks/snapshotpolicy_create_task.go | 202 +++++++++ pkg/mcclient/modules/mod_snapshotpolicy.go | 27 ++ pkg/mcclient/modules/mod_snapshots.go | 2 +- pkg/multicloud/disk_base.go | 7 + pkg/multicloud/region_base.go | 33 ++ pkg/util/aliyun/disk.go | 6 + pkg/util/aliyun/region.go | 3 + pkg/util/aliyun/snapshot_policy.go | 263 ++++++++++++ pkg/util/aws/disk.go | 2 + pkg/util/aws/region.go | 3 + pkg/util/azure/classic_disk.go | 2 + pkg/util/azure/disk.go | 2 + pkg/util/azure/region.go | 2 + pkg/util/esxi/manager.go | 2 + pkg/util/esxi/vdisk.go | 4 + pkg/util/huawei/disk.go | 2 + pkg/util/huawei/region.go | 3 + pkg/util/logclient/logclient.go | 2 + pkg/util/openstack/disk.go | 2 + pkg/util/openstack/region.go | 3 + pkg/util/qcloud/disk.go | 2 + pkg/util/qcloud/localdisk.go | 3 + pkg/util/qcloud/region.go | 3 + pkg/util/ucloud/disk.go | 2 + pkg/util/ucloud/region.go | 3 + pkg/util/zstack/disk.go | 5 +- pkg/util/zstack/region.go | 4 +- 45 files changed, 1501 insertions(+), 15 deletions(-) create mode 100644 cmd/climc/shell/snapshot_policy.go create mode 100644 pkg/cloudprovider/snapshot_policy.go create mode 100644 pkg/compute/models/snapshotpolicy.go create mode 100644 pkg/compute/tasks/snapshot_policy_delete_task.go create mode 100644 pkg/compute/tasks/snapshotpolicy_create_task.go create mode 100644 pkg/mcclient/modules/mod_snapshotpolicy.go create mode 100644 pkg/multicloud/disk_base.go create mode 100644 pkg/multicloud/region_base.go create mode 100644 pkg/util/aliyun/snapshot_policy.go diff --git a/cmd/climc/shell/disks.go b/cmd/climc/shell/disks.go index f09a5045ab..829dad9cb0 100644 --- a/cmd/climc/shell/disks.go +++ b/cmd/climc/shell/disks.go @@ -273,4 +273,31 @@ func init() { printObject(disk) return nil }) + type DiskApplySnapshotPolicy struct { + ID string `help:"ID or name of disk" json:"-"` + Snapshotpolicy string `help:"ID or name of snapshot policy"` + } + R(&DiskApplySnapshotPolicy{}, "disk-apply-snapshot-policy", "Set disk snapshot policy", func(s *mcclient.ClientSession, args *DiskApplySnapshotPolicy) error { + params, err := options.StructToParams(args) + if err != nil { + return err + } + disk, err := modules.Disks.PerformAction(s, args.ID, "apply-snapshot-policy", params) + if err != nil { + return err + } + printObject(disk) + return nil + }) + type DiskCancelSnapshotPolicy struct { + ID string `help:"ID or name of disk"` + } + R(&DiskCancelSnapshotPolicy{}, "disk-cancel-snapshot-policy", "Cancel disk snapshot policy", func(s *mcclient.ClientSession, args *DiskCancelSnapshotPolicy) error { + disk, err := modules.Disks.PerformAction(s, args.ID, "cancel-snapshot-policy", nil) + if err != nil { + return err + } + printObject(disk) + return nil + }) } diff --git a/cmd/climc/shell/snapshot_policy.go b/cmd/climc/shell/snapshot_policy.go new file mode 100644 index 0000000000..8049cc1391 --- /dev/null +++ b/cmd/climc/shell/snapshot_policy.go @@ -0,0 +1,76 @@ +package shell + +import ( + "yunion.io/x/jsonutils" + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/mcclient/modules" + "yunion.io/x/onecloud/pkg/mcclient/options" +) + +func init() { + type SnapshotPolicyListOptions struct { + options.BaseListOptions + } + R(&SnapshotPolicyListOptions{}, "snapshot-policy-list", "List snapshot policy", func(s *mcclient.ClientSession, args *SnapshotPolicyListOptions) error { + params, err := options.ListStructToParams(args) + if err != nil { + return err + } + result, err := modules.SnapshotPoliciy.List(s, params) + if err != nil { + return err + } + printList(result, modules.SnapshotPoliciy.GetColumns(s)) + return nil + }) + + type SnapshotPolicyDeleteOptions struct { + ID string `help:"Delete snapshot id"` + } + R(&SnapshotPolicyDeleteOptions{}, "snapshot-policy-delete", "Delete snapshot policy", func(s *mcclient.ClientSession, args *SnapshotPolicyDeleteOptions) error { + result, err := modules.SnapshotPoliciy.Delete(s, args.ID, nil) + if err != nil { + return err + } + printObject(result) + return nil + }) + + type SnapshotPolicyCreateOptions struct { + NAME string + Manager string `help:"Manager id or name"` + Cloudregion string `help:"Cloudregion id or name"` + + RetentionDays int `help:"snapshot retention days"` + RepeatWeekdays []int `help:"snapshot create days on week"` + TimePoints []int `help:"snapshot create time points on one day` + } + + R(&SnapshotPolicyCreateOptions{}, "snapshot-policy-create", "Create snapshot policy", func(s *mcclient.ClientSession, args *SnapshotPolicyCreateOptions) error { + params := jsonutils.Marshal(args).(*jsonutils.JSONDict) + snapshot, err := modules.SnapshotPoliciy.Create(s, params) + if err != nil { + return err + } + printObject(snapshot) + return nil + }) + + type SnapshotPolicyApplyOptions struct { + ID string `help:"ID or Name of SnapshotPolicy" json:"-"` + Disk []string `help:"Disks id to apply snapshot policy"` + } + + R(&SnapshotPolicyApplyOptions{}, "snapshot-policy-apply", "Apply snapshot policy to disks", func(s *mcclient.ClientSession, args *SnapshotPolicyApplyOptions) error { + params, err := options.StructToParams(args) + if err != nil { + return err + } + snapshot, err := modules.SnapshotPoliciy.PerformAction(s, args.ID, "apply-to-disk", params) + if err != nil { + return err + } + printObject(snapshot) + return nil + }) +} diff --git a/pkg/apis/compute/snapshot.go b/pkg/apis/compute/snapshot.go index a3dd8b397f..e79d25ac03 100644 --- a/pkg/apis/compute/snapshot.go +++ b/pkg/apis/compute/snapshot.go @@ -30,3 +30,17 @@ type SSnapshotCreateInput struct { DiskType string `json:"disk_type"` CloudregionId string `json:"cloudregion_id"` } + +type SSnapshotPolicyCreateInput struct { + apis.Meta + + Name string `json:"name"` + ProjectId string `json:"project_id"` + + ManagerId string `json:"manager_id"` + CloudregionId string `json:"cloudregion_id"` + + RetentionDays int `json:"retention_days"` + RepeatWeekdays []int `json:"repeat_weekdays"` + TimePoints []int `json:"time_points"` +} diff --git a/pkg/apis/compute/snapshot_const.go b/pkg/apis/compute/snapshot_const.go index 64e10a64cb..a7a2f10fd7 100644 --- a/pkg/apis/compute/snapshot_const.go +++ b/pkg/apis/compute/snapshot_const.go @@ -25,4 +25,11 @@ const ( SNAPSHOT_READY = "ready" SNAPSHOT_DELETING = "deleting" SNAPSHOT_UNKNOWN = "unknown" + + SNAPSHOT_POLICY_CREATING = "creating" + SNAPSHOT_POLICY_READY = "ready" + SNAPSHOT_POLICY_CREATE_FAILED = "create_failed" + SNAPSHOT_POLICY_UNKNOWN = "unknown" + SNAPSHOT_POLICY_DELETING = "deleting" + SNAPSHOT_POLICY_DELETE_FAILED = "delete_failed" ) diff --git a/pkg/cloudcommon/db/opslog.go b/pkg/cloudcommon/db/opslog.go index 0a1339bc2b..bc0dd485c1 100644 --- a/pkg/cloudcommon/db/opslog.go +++ b/pkg/cloudcommon/db/opslog.go @@ -83,16 +83,20 @@ const ( ACT_SWITCHED = "switched" ACT_SWITCH_FAILED = "switch_failed" - ACT_SNAPSHOTING = "snapshoting" - ACT_SNAPSHOT_STREAM = "snapshot_stream" - ACT_SNAPSHOT_DONE = "snapshot" - ACT_SNAPSHOT_READY = "snapshot_ready" - ACT_SNAPSHOT_SYNC = "snapshot_sync" - ACT_SNAPSHOT_FAIL = "snapshot_fail" - ACT_SNAPSHOT_DELETING = "snapshot_deling" - ACT_SNAPSHOT_DELETE = "snapshot_del" - ACT_SNAPSHOT_DELETE_FAIL = "snapshot_del_fail" - ACT_SNAPSHOT_UNLINK = "snapshot_unlink" + ACT_SNAPSHOTING = "snapshoting" + ACT_SNAPSHOT_STREAM = "snapshot_stream" + ACT_SNAPSHOT_DONE = "snapshot" + ACT_SNAPSHOT_READY = "snapshot_ready" + ACT_SNAPSHOT_SYNC = "snapshot_sync" + ACT_SNAPSHOT_FAIL = "snapshot_fail" + ACT_SNAPSHOT_DELETING = "snapshot_deling" + ACT_SNAPSHOT_DELETE = "snapshot_del" + ACT_SNAPSHOT_DELETE_FAIL = "snapshot_del_fail" + ACT_SNAPSHOT_UNLINK = "snapshot_unlink" + ACT_APPLY_SNAPSHOT_POLICY = "apply_snapshot_policy" + ACT_APPLY_SNAPSHOT_POLICY_FAILED = "apply_snapshot_policy_failed" + ACT_CANCEL_SNAPSHOT_POLICY = "cancel_snapshot_policy" + ACT_CANCEL_SNAPSHOT_POLICY_FAILED = "cancel_snapshot_policy_failed" ACT_DISK_CLEAN_UP_SNAPSHOTS = "disk_clean_up_snapshots" ACT_DISK_CLEAN_UP_SNAPSHOTS_FAIL = "disk_clean_up_snapshots_fail" diff --git a/pkg/cloudprovider/resources.go b/pkg/cloudprovider/resources.go index 30d2d5930e..6864517843 100644 --- a/pkg/cloudprovider/resources.go +++ b/pkg/cloudprovider/resources.go @@ -72,6 +72,13 @@ type ICloudRegion interface { GetISnapshots() ([]ICloudSnapshot, error) GetISnapshotById(snapshotId string) (ICloudSnapshot, error) + CreateSnapshotPolicy(*SnapshotPolicyInput) (string, error) + DeleteSnapshotPolicy(string) error + ApplySnapshotPolicyToDisks(snapshotPolicyId string, diskIds []string) error + CancelSnapshotPolicyToDisks(diskIds []string) error + GetISnapshotPolicies() ([]ICloudSnapshotPolicy, error) + GetISnapshotPolicyById(snapshotPolicyId string) (ICloudSnapshotPolicy, error) + GetIHosts() ([]ICloudHost, error) GetIHostById(id string) (ICloudHost, error) @@ -345,6 +352,8 @@ type ICloudDisk interface { GetISnapshot(idStr string) (ICloudSnapshot, error) GetISnapshots() ([]ICloudSnapshot, error) + GetExtSnapshotPolicyId() string + Resize(ctx context.Context, newSizeMB int64) error Reset(ctx context.Context, snapshotId string) (string, error) @@ -361,6 +370,15 @@ type ICloudSnapshot interface { Delete() error } +type ICloudSnapshotPolicy interface { + ICloudResource + IVirtualResource + + GetRetentionDays() int + GetRepeatWeekdays() ([]int, error) + GetTimePoints() ([]int, error) +} + type ICloudVpc interface { ICloudResource diff --git a/pkg/cloudprovider/snapshot_policy.go b/pkg/cloudprovider/snapshot_policy.go new file mode 100644 index 0000000000..acb1d6266c --- /dev/null +++ b/pkg/cloudprovider/snapshot_policy.go @@ -0,0 +1,25 @@ +package cloudprovider + +import "strconv" + +type SnapshotPolicyInput struct { + RetentionDays int + RepeatWeekdays, TimePoints []int + PolicyName string +} + +func (spi *SnapshotPolicyInput) GetStringArrayRepeatWeekdays() []string { + return toStringArray(spi.RepeatWeekdays) +} + +func (spi *SnapshotPolicyInput) GetStringArrayTimePoints() []string { + return toStringArray(spi.TimePoints) +} + +func toStringArray(days []int) []string { + ret := make([]string, len(days)) + for i := 0; i < len(days); i++ { + ret[i] = strconv.Itoa(days[i]) + } + return ret +} diff --git a/pkg/compute/models/cloudproviders.go b/pkg/compute/models/cloudproviders.go index 1846c58986..7c11c0308b 100644 --- a/pkg/compute/models/cloudproviders.go +++ b/pkg/compute/models/cloudproviders.go @@ -981,6 +981,7 @@ func (self *SCloudprovider) RealDelete(ctx context.Context, userCred mcclient.To for _, manager := range []IPurgeableManager{ HostManager, SnapshotManager, + SnapshotPolicyManager, StorageManager, StoragecacheManager, LoadbalancerBackendManager, diff --git a/pkg/compute/models/cloudsync.go b/pkg/compute/models/cloudsync.go index 5c058489ff..d22604be15 100644 --- a/pkg/compute/models/cloudsync.go +++ b/pkg/compute/models/cloudsync.go @@ -763,6 +763,23 @@ func syncRegionSnapshots(ctx context.Context, userCred mcclient.TokenCredential, // db.OpsLog.LogEvent(provider, db.ACT_SYNC_HOST_COMPLETE, msg, userCred) } +func syncRegionSnapshotPolicies(ctx context.Context, userCred mcclient.TokenCredential, syncResults SSyncResultSet, provider *SCloudprovider, localRegion *SCloudregion, remoteRegion cloudprovider.ICloudRegion, syncRange *SSyncRange) { + snapshotPolicies, err := remoteRegion.GetISnapshotPolicies() + if err != nil { + log.Errorf("GetISnapshotPolicies for region %s failed %s", remoteRegion.GetName(), err) + return + } + + result := SnapshotPolicyManager.SyncSnapshotPolicies( + ctx, userCred, provider, localRegion, snapshotPolicies, provider.ProjectId) + syncResults.Add(SnapshotPolicyManager, result) + msg := result.Result() + log.Infof("SyncSnapshotPolicies for region %s result: %s", localRegion.Name, msg) + if result.IsError() { + return + } +} + func syncPublicCloudProviderInfo( ctx context.Context, userCred mcclient.TokenCredential, @@ -797,6 +814,8 @@ func syncPublicCloudProviderInfo( syncRegionVPCs(ctx, userCred, syncResults, provider, localRegion, remoteRegion, syncRange) syncRegionEips(ctx, userCred, syncResults, provider, localRegion, remoteRegion, syncRange) + // sync snapshot policies before sync disks + syncRegionSnapshotPolicies(ctx, userCred, syncResults, provider, localRegion, remoteRegion, syncRange) for j := 0; j < len(localZones); j += 1 { @@ -819,6 +838,7 @@ func syncPublicCloudProviderInfo( } } + // sync snapshots after sync disks syncRegionSnapshots(ctx, userCred, syncResults, provider, localRegion, remoteRegion, syncRange) syncRegionLoadbalancerAcls(ctx, userCred, syncResults, provider, localRegion, remoteRegion, syncRange) diff --git a/pkg/compute/models/disks.go b/pkg/compute/models/disks.go index 81938a4063..a42d5b2497 100644 --- a/pkg/compute/models/disks.go +++ b/pkg/compute/models/disks.go @@ -38,6 +38,7 @@ import ( "yunion.io/x/onecloud/pkg/cloudcommon/db/lockman" "yunion.io/x/onecloud/pkg/cloudcommon/db/quotas" "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/cloudcommon/validators" "yunion.io/x/onecloud/pkg/cloudprovider" "yunion.io/x/onecloud/pkg/compute/options" "yunion.io/x/onecloud/pkg/httperrors" @@ -89,7 +90,9 @@ type SDisk struct { DiskType string `width:"32" charset:"ascii" nullable:"true" list:"user" update:"admin"` // Column(VARCHAR(32, charset='ascii'), nullable=True) // # is persistent Nonpersistent bool `default:"false" list:"user"` // Column(Boolean, default=False) - AutoSnapshot bool `default:"false" nullable:"true" get:"user" update:"user"` + + AutoSnapshot bool `default:"false" nullable:"true" get:"user" update:"user"` + SnapshotPolicyId string `width:"128" charset:"ascii" nullable:"true" get:"user" list:"user" update:"user" create:"optional"` } func (manager *SDiskManager) GetContextManagers() [][]db.IModelManager { @@ -739,6 +742,51 @@ func (self *SDisk) PerformResize(ctx context.Context, userCred mcclient.TokenCre return nil, self.StartDiskResizeTask(ctx, userCred, int64(sizeMb), "", &pendingUsage, guest) } +func (self *SDisk) AllowPerformApplySnapshotPolicy(ctx context.Context, + userCred mcclient.TokenCredential, + query jsonutils.JSONObject, + data jsonutils.JSONObject) bool { + return self.IsOwner(userCred) || db.IsAdminAllowPerform(userCred, self, "apply-snapshot-policy") +} + +func (self *SDisk) PerformApplySnapshotPolicy( + ctx context.Context, userCred mcclient.TokenCredential, + query jsonutils.JSONObject, data jsonutils.JSONObject, +) (jsonutils.JSONObject, error) { + spv := validators.NewModelIdOrNameValidator("snapshotpolicy", "snapshotpolicy", userCred.GetProjectId()) + if err := spv.Validate(data.(*jsonutils.JSONDict)); err != nil { + return nil, err + } + sp := spv.Model.(*SSnapshotPolicy) + return nil, sp.StartApplySnapshotPolicyToDisks(ctx, userCred, []string{self.Id}) +} + +func (self *SDisk) AllowPerformCancelSnapshotPolicy(ctx context.Context, + userCred mcclient.TokenCredential, + query jsonutils.JSONObject, + data jsonutils.JSONObject) bool { + return self.IsOwner(userCred) || db.IsAdminAllowPerform(userCred, self, "cancel-snapshot-policy") +} + +func (self *SDisk) PerformCancelSnapshotPolicy( + ctx context.Context, userCred mcclient.TokenCredential, + query jsonutils.JSONObject, data jsonutils.JSONObject, +) (jsonutils.JSONObject, error) { + if len(self.SnapshotPolicyId) == 0 { + return nil, httperrors.NewBadRequestError("Disk dosen't apply any snapshot policy") + } + return nil, self.StartCancelSnapshotPolicyToDisks(ctx, userCred) +} + +func (self *SDisk) StartCancelSnapshotPolicyToDisks(ctx context.Context, userCred mcclient.TokenCredential) error { + if task, err := taskman.TaskManager.NewTask(ctx, "SnapshotPolicyCancelTask", self, userCred, nil, "", "", nil); err != nil { + return err + } else { + task.ScheduleRun(nil) + } + return nil +} + func (self *SDisk) GetIStorage() (cloudprovider.ICloudStorage, error) { storage := self.GetStorage() if storage == nil { @@ -1089,6 +1137,14 @@ func (self *SDisk) syncWithCloudDisk(ctx context.Context, userCred mcclient.Toke self.CreatedAt = createdAt } + extPolicyId := extDisk.GetExtSnapshotPolicyId() + if len(extPolicyId) > 0 { + isp, _ := SnapshotPolicyManager.FetchByExternalId(extPolicyId) + if isp != nil { + self.SnapshotPolicyId = isp.GetId() + } + } + return nil }) if err != nil { @@ -1136,6 +1192,14 @@ func (manager *SDiskManager) newFromCloudDisk(ctx context.Context, userCred mccl disk.CreatedAt = createAt } + extPolicyId := extDisk.GetExtSnapshotPolicyId() + if len(extPolicyId) > 0 { + isp, _ := SnapshotPolicyManager.FetchByExternalId(extPolicyId) + if isp != nil { + disk.SnapshotPolicyId = isp.GetId() + } + } + err = manager.TableSpec().Insert(&disk) if err != nil { log.Errorf("newFromCloudZone fail %s", err) @@ -1513,6 +1577,14 @@ func (self *SDisk) GetAttachedGuests() []SGuest { return ret } +func (self *SDisk) SetSnapshotPolicy(policyId string) error { + _, err := db.Update(self, func() error { + self.SnapshotPolicyId = policyId + return nil + }) + return err +} + func (self *SDisk) SetDiskReady(ctx context.Context, userCred mcclient.TokenCredential, reason string) { self.SetStatus(userCred, api.DISK_READY, reason) guests := self.GetAttachedGuests() diff --git a/pkg/compute/models/purge.go b/pkg/compute/models/purge.go index a5f049c090..0921ed0c47 100644 --- a/pkg/compute/models/purge.go +++ b/pkg/compute/models/purge.go @@ -399,6 +399,32 @@ func (snapshot *SSnapshot) purge(ctx context.Context, userCred mcclient.TokenCre return snapshot.RealDelete(ctx, userCred) } +func (manager *SSnapshotPolicyManager) purgeAll(ctx context.Context, userCred mcclient.TokenCredential, providerId string) error { + sps := make([]SSnapshotPolicy, 0) + err := fetchByManagerId(manager, providerId, &sps) + if err != nil { + return err + } + for i := range sps { + err := sps[i].purge(ctx, userCred) + if err != nil { + return err + } + } + return nil +} + +func (sp *SSnapshotPolicy) purge(ctx context.Context, userCred mcclient.TokenCredential) error { + lockman.LockObject(ctx, sp) + defer lockman.ReleaseObject(ctx, sp) + + err := sp.ValidateDeleteCondition(ctx) + if err != nil { + return err + } + return sp.RealDelete(ctx, userCred) +} + func (manager *SStoragecacheManager) purgeAll(ctx context.Context, userCred mcclient.TokenCredential, providerId string) error { scs := make([]SStoragecache, 0) err := fetchByManagerId(manager, providerId, &scs) diff --git a/pkg/compute/models/regiondrivers.go b/pkg/compute/models/regiondrivers.go index e5f55b1d31..6619168bd3 100644 --- a/pkg/compute/models/regiondrivers.go +++ b/pkg/compute/models/regiondrivers.go @@ -20,6 +20,7 @@ import ( "yunion.io/x/jsonutils" "yunion.io/x/log" + "yunion.io/x/onecloud/pkg/apis/compute" "yunion.io/x/onecloud/pkg/cloudcommon/db" "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" "yunion.io/x/onecloud/pkg/cloudprovider" @@ -73,8 +74,13 @@ type IRegionDriver interface { RequestDeleteLoadbalancerListenerRule(ctx context.Context, userCred mcclient.TokenCredential, lbr *SLoadbalancerListenerRule, task taskman.ITask) error ValidateCreateVpcData(ctx context.Context, userCred mcclient.TokenCredential, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) - ValidateCreateEipData(ctx context.Context, userCred mcclient.TokenCredential, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) + + ValidateCreateSnapshotPolicyData(ctx context.Context, userCred mcclient.TokenCredential, data *compute.SSnapshotPolicyCreateInput) error + RequestCreateSnapshotPolicy(ctx context.Context, userCred mcclient.TokenCredential, sp *SSnapshotPolicy, task taskman.ITask) error + RequestDeleteSnapshotPolicy(ctx context.Context, userCred mcclient.TokenCredential, sp *SSnapshotPolicy, task taskman.ITask) error + RequestApplySnapshotPolicy(ctx context.Context, userCred mcclient.TokenCredential, sp *SSnapshotPolicy, task taskman.ITask, diskIds []string) error + RequestCancelSnapshotPolicy(ctx context.Context, userCred mcclient.TokenCredential, region cloudprovider.ICloudRegion, task taskman.ITask, diskIds []string) error } var regionDrivers map[string]IRegionDriver diff --git a/pkg/compute/models/snapshotpolicy.go b/pkg/compute/models/snapshotpolicy.go new file mode 100644 index 0000000000..ec3f9061b4 --- /dev/null +++ b/pkg/compute/models/snapshotpolicy.go @@ -0,0 +1,406 @@ +// Copyright 2019 Yunion +// +// 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 models + +import ( + "context" + "fmt" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/util/compare" + "yunion.io/x/pkg/utils" + + "yunion.io/x/onecloud/pkg/apis/compute" + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/lockman" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/cloudcommon/validators" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/mcclient" +) + +type SSnapshotPolicyManager struct { + db.SVirtualResourceBaseManager +} + +type SSnapshotPolicy struct { + db.SVirtualResourceBase + SManagedResourceBase + SCloudregionResourceBase + + RetentionDays int `nullable:"false" list:"user" get:"user" create:"required"` + + // {repeat_weekdays: [1,2,3,4,5,6,7], time_points: [0...23]} + RepeatWeekdays string `charset:"utf8" list:"user" get:"user" create:"required"` + TimePoints string `charset:"utf8" list:"user" get:"user" create:"required"` +} + +var SnapshotPolicyManager *SSnapshotPolicyManager + +func init() { + SnapshotPolicyManager = &SSnapshotPolicyManager{ + SVirtualResourceBaseManager: db.NewVirtualResourceBaseManager( + SSnapshotPolicy{}, + "snapshotpolicies_tbl", + "snapshotpolicy", + "snapshotpolicies", + ), + } +} + +func (manager *SSnapshotPolicyManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerProjId string, query jsonutils.JSONObject, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) { + input := &compute.SSnapshotPolicyCreateInput{} + err := data.Unmarshal(input) + if err != nil { + return nil, httperrors.NewInputParameterError("Unmarshal input failed %s", err) + } + input.ProjectId = ownerProjId + + err = db.NewNameValidator(manager, ownerProjId, input.Name) + if err != nil { + return nil, err + } + + managerIdV := validators.NewModelIdOrNameValidator("manager", "cloudprovider", "") + if err := managerIdV.Validate(data); err != nil { + return nil, err + } + input.ManagerId, _ = data.GetString("manager_id") + + cloudregionV := validators.NewModelIdOrNameValidator("cloudregion", "cloudregion", ownerProjId) + err = cloudregionV.Validate(data) + if err != nil { + return nil, err + } + cloudregion := cloudregionV.Model.(*SCloudregion) + input.CloudregionId = cloudregion.GetId() + + err = cloudregion.GetDriver().ValidateCreateSnapshotPolicyData(ctx, userCred, input) + if err != nil { + return nil, err + } + data = input.JSON(input) + return data, nil +} + +func (self *SSnapshotPolicy) PostCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerProjId string, query jsonutils.JSONObject, data jsonutils.JSONObject) { + self.StartCreateSnapshotPolicy(ctx, userCred, ownerProjId, query, data) +} + +func (self *SSnapshotPolicy) StartCreateSnapshotPolicy(ctx context.Context, userCred mcclient.TokenCredential, ownerProjId string, query jsonutils.JSONObject, data jsonutils.JSONObject) error { + if task, err := taskman.TaskManager.NewTask(ctx, "SnapshotPolicyCreateTask", self, userCred, nil, "", "", nil); err != nil { + return err + } else { + task.ScheduleRun(nil) + } + return nil +} + +func (self *SSnapshotPolicy) CustomizeDelete(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) error { + self.SetStatus(userCred, compute.SNAPSHOT_POLICY_DELETING, "") + return self.StartSnapshotPolicyDeleteTask(ctx, userCred, jsonutils.NewDict(), "") +} + +func (self *SSnapshotPolicy) StartSnapshotPolicyDeleteTask(ctx context.Context, userCred mcclient.TokenCredential, params *jsonutils.JSONDict, parentTaskId string) error { + task, err := taskman.TaskManager.NewTask(ctx, "SnapshotPolicyDeleteTask", self, userCred, params, parentTaskId, "", nil) + if err != nil { + return err + } + task.ScheduleRun(nil) + return nil +} + +func (self *SSnapshotPolicy) GetCustomizeColumns(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) *jsonutils.JSONDict { + ret := self.SCloudregionResourceBase.GetCustomizeColumns(ctx, userCred, query) + ret.Update(self.SVirtualResourceBase.GetCustomizeColumns(ctx, userCred, query)) + return ret +} + +func (self *SSnapshotPolicy) GetIRegion() (cloudprovider.ICloudRegion, error) { + provider, err := self.GetDriver() + if err != nil { + return nil, fmt.Errorf("No cloudprovider for sp %s: %s", self.Name, err) + } + region := self.GetRegion() + if region == nil { + return nil, fmt.Errorf("failed to find region for sp %s", self.Name) + } + return provider.GetIRegionById(region.ExternalId) +} + +func (self *SSnapshotPolicy) GenerateCreateSpParams() (*cloudprovider.SnapshotPolicyInput, error) { + idays, err := jsonutils.ParseString(self.RepeatWeekdays) + if err != nil { + return nil, fmt.Errorf("SnapshotPolicy %s Parse repeat weekdays error %s", self.Name, err) + } + weekdays := idays.(*jsonutils.JSONArray) + intWeekdays := make([]int, weekdays.Length()) + for i, v := range weekdays.Value() { + t, err := v.Int() + if err != nil { + return nil, fmt.Errorf("parse weekdays error %s", weekdays) + } + intWeekdays[i] = int(t) + } + + idays, err = jsonutils.ParseString(self.TimePoints) + if err != nil { + return nil, fmt.Errorf("SnapshotPolicy %s Parse time points error %s", self.Name, err) + } + timePoints := idays.(*jsonutils.JSONArray) + intTimePoints := make([]int, timePoints.Length()) + for i, v := range timePoints.Value() { + t, err := v.Int() + if err != nil { + return nil, fmt.Errorf("parse weekdays error %s", timePoints) + } + intTimePoints[i] = int(t) + } + + return &cloudprovider.SnapshotPolicyInput{ + RetentionDays: self.RetentionDays, + RepeatWeekdays: intWeekdays, + TimePoints: intTimePoints, + PolicyName: self.Name, + }, nil +} + +func (manager *SSnapshotPolicyManager) SyncSnapshotPolicies(ctx context.Context, userCred mcclient.TokenCredential, provider *SCloudprovider, region *SCloudregion, snapshots []cloudprovider.ICloudSnapshotPolicy, projectId string) compare.SyncResult { + syncOwnerProjId := projectId + + lockman.LockClass(ctx, manager, syncOwnerProjId) + defer lockman.ReleaseClass(ctx, manager, syncOwnerProjId) + syncResult := compare.SyncResult{} + dbSnapshotPolicies, err := manager.getProviderSnapshotPolicies(region, provider) + if err != nil { + syncResult.Error(err) + return syncResult + } + removed := make([]SSnapshotPolicy, 0) + commondb := make([]SSnapshotPolicy, 0) + commonext := make([]cloudprovider.ICloudSnapshotPolicy, 0) + added := make([]cloudprovider.ICloudSnapshotPolicy, 0) + + err = compare.CompareSets(dbSnapshotPolicies, snapshots, &removed, &commondb, &commonext, &added) + if err != nil { + syncResult.Error(err) + return syncResult + } + for i := 0; i < len(removed); i += 1 { + err = removed[i].syncRemoveCloudSnapshot(ctx, userCred) + if err != nil { + syncResult.DeleteError(err) + } else { + syncResult.Delete() + } + } + for i := 0; i < len(commondb); i += 1 { + err = commondb[i].SyncWithCloudSnapshotPolicy(ctx, userCred, commonext[i], projectId, region) + if err != nil { + syncResult.UpdateError(err) + } else { + syncMetadata(ctx, userCred, &commondb[i], commonext[i]) + syncResult.Update() + } + } + for i := 0; i < len(added); i += 1 { + local, err := manager.newFromCloudSnapshotPolicy(ctx, userCred, added[i], region, syncOwnerProjId, provider) + if err != nil { + syncResult.AddError(err) + } else { + syncMetadata(ctx, userCred, local, added[i]) + syncResult.Add() + } + } + return syncResult +} + +func (manager *SSnapshotPolicyManager) getProviderSnapshotPolicies(region *SCloudregion, provider *SCloudprovider) ([]SSnapshotPolicy, error) { + if region == nil || provider == nil { + return nil, fmt.Errorf("Region is nil or provider is nil") + } + snapshotPolicies := make([]SSnapshotPolicy, 0) + q := manager.Query().Equals("cloudregion_id", region.Id).Equals("manager_id", provider.Id) + err := db.FetchModelObjects(manager, q, &snapshotPolicies) + if err != nil { + return nil, err + } + return snapshotPolicies, nil +} + +func (self *SSnapshotPolicy) syncRemoveCloudSnapshot(ctx context.Context, userCred mcclient.TokenCredential) error { + lockman.LockObject(ctx, self) + defer lockman.ReleaseObject(ctx, self) + + err := self.ValidateDeleteCondition(ctx) + if err != nil { + err = self.SetStatus(userCred, api.SNAPSHOT_POLICY_UNKNOWN, "sync to delete") + } else { + err = self.RealDelete(ctx, userCred) + } + return err +} + +func (self *SSnapshotPolicy) RealDelete(ctx context.Context, userCred mcclient.TokenCredential) error { + return db.DeleteModel(ctx, userCred, self) +} + +func (self *SSnapshotPolicy) SyncWithCloudSnapshotPolicy(ctx context.Context, userCred mcclient.TokenCredential, ext cloudprovider.ICloudSnapshotPolicy, projectId string, region *SCloudregion) error { + diff, err := db.UpdateWithLock(ctx, self, func() error { + self.Name = ext.GetName() + self.Status = ext.GetStatus() + self.RetentionDays = ext.GetRetentionDays() + + arw, err := ext.GetRepeatWeekdays() + if err != nil { + return err + } + self.RepeatWeekdays = jsonutils.Marshal(arw).String() + + atp, err := ext.GetTimePoints() + if err != nil { + return err + } + self.TimePoints = jsonutils.Marshal(atp).String() + return nil + }) + db.OpsLog.LogSyncUpdate(self, diff, userCred) + SyncCloudProject(userCred, self, projectId, ext, self.ManagerId) + return err +} +func (manager *SSnapshotPolicyManager) newFromCloudSnapshotPolicy( + ctx context.Context, userCred mcclient.TokenCredential, + ext cloudprovider.ICloudSnapshotPolicy, region *SCloudregion, + projectId string, provider *SCloudprovider, +) (*SSnapshotPolicy, error) { + + snapshotPolicy := SSnapshotPolicy{} + snapshotPolicy.SetModelManager(manager) + + newName, err := db.GenerateName(manager, projectId, ext.GetName()) + if err != nil { + return nil, err + } + snapshotPolicy.Name = newName + snapshotPolicy.Status = ext.GetStatus() + snapshotPolicy.ExternalId = ext.GetGlobalId() + snapshotPolicy.ManagerId = provider.Id + snapshotPolicy.CloudregionId = region.Id + snapshotPolicy.RetentionDays = ext.GetRetentionDays() + arw, err := ext.GetRepeatWeekdays() + if err != nil { + return nil, err + } + snapshotPolicy.RepeatWeekdays = jsonutils.Marshal(arw).String() + atp, err := ext.GetTimePoints() + if err != nil { + return nil, err + } + snapshotPolicy.TimePoints = jsonutils.Marshal(atp).String() + + err = manager.TableSpec().Insert(&snapshotPolicy) + if err != nil { + log.Errorf("newFromCloudEip fail %s", err) + return nil, err + } + + SyncCloudProject(userCred, &snapshotPolicy, projectId, ext, snapshotPolicy.ManagerId) + db.OpsLog.LogEvent(&snapshotPolicy, db.ACT_CREATE, snapshotPolicy.GetShortDesc(ctx), userCred) + return &snapshotPolicy, nil +} + +func (self *SSnapshotPolicy) AllowPerformApplyToDisks(ctx context.Context, + userCred mcclient.TokenCredential, + query jsonutils.JSONObject, + data jsonutils.JSONObject) bool { + return self.IsOwner(userCred) || db.IsAdminAllowPerform(userCred, self, "apply-to-disks") +} + +func (self *SSnapshotPolicy) PerformApplyToDisks( + ctx context.Context, userCred mcclient.TokenCredential, + query jsonutils.JSONObject, data jsonutils.JSONObject, +) (jsonutils.JSONObject, error) { + diskIds, err := self.preCheck(ctx, userCred, query, data) + if err != nil { + return nil, err + } + return nil, self.StartApplySnapshotPolicyToDisks(ctx, userCred, diskIds) +} + +func (self *SSnapshotPolicy) StartApplySnapshotPolicyToDisks(ctx context.Context, userCred mcclient.TokenCredential, diskIds []string) error { + params := jsonutils.NewDict() + params.Set("disk_ids", jsonutils.Marshal(diskIds)) + if task, err := taskman.TaskManager.NewTask(ctx, "SnapshotPolicyApplyTask", self, userCred, params, "", "", nil); err != nil { + return err + } else { + task.ScheduleRun(nil) + } + return nil +} + +// func (self *SSnapshotPolicy) AllowPerformCancelToDisks(ctx context.Context, +// userCred mcclient.TokenCredential, +// query jsonutils.JSONObject, +// data jsonutils.JSONObject) bool { +// return self.IsOwner(userCred) || db.IsAdminAllowPerform(userCred, self, "apply-to-disks") +// } + +// func (self *SSnapshotPolicy) PerformCancelToDisks( +// ctx context.Context, userCred mcclient.TokenCredential, +// query jsonutils.JSONObject, data jsonutils.JSONObject, +// ) (jsonutils.JSONObject, error) { +// diskIds, err := self.preCheck(ctx, userCred, query, data) +// if err != nil { +// return nil, err +// } +// return nil, self.StartCancelSnapshotPolicyToDisks(ctx, userCred, diskIds) +// } + +func (self *SSnapshotPolicy) preCheck( + ctx context.Context, userCred mcclient.TokenCredential, + query jsonutils.JSONObject, data jsonutils.JSONObject, +) ([]string, error) { + if self.Status != compute.SNAPSHOT_POLICY_READY { + return nil, httperrors.NewInvalidStatusError("Snapshot policy status %s can't do apply", self.Status) + } + jsonDiskIds, err := data.Get("disks") + if err != nil { + return nil, httperrors.NewMissingParameterError("disks") + } + ids, ok := jsonDiskIds.(*jsonutils.JSONArray) + if !ok { + return nil, httperrors.NewInputParameterError("disk_ids %s", jsonDiskIds) + } + diskIds := ids.GetStringArray() + disks := make([]string, 0) + err = DiskManager.Query("id").Equals("cloudregion_id", self.CloudregionId). + Equals("manager_id", self.ManagerId).In("id", diskIds).All(&disks) + if err != nil { + return nil, httperrors.NewInternalServerError("Query disks error %s", err) + } + if len(disks) < len(diskIds) { + notFoundDisks := make([]string, 0) + for _, id := range diskIds { + if !utils.IsInStringArray(id, disks) { + notFoundDisks = append(notFoundDisks, id) + } + } + return nil, httperrors.NewNotFoundError("Disks %v not found", notFoundDisks) + } + return diskIds, nil +} diff --git a/pkg/compute/regiondrivers/aliyun.go b/pkg/compute/regiondrivers/aliyun.go index d04085d11f..cf3f5883f4 100644 --- a/pkg/compute/regiondrivers/aliyun.go +++ b/pkg/compute/regiondrivers/aliyun.go @@ -18,10 +18,12 @@ import ( "context" "fmt" "regexp" + "sort" "yunion.io/x/jsonutils" "yunion.io/x/pkg/utils" + "yunion.io/x/onecloud/pkg/apis/compute" api "yunion.io/x/onecloud/pkg/apis/compute" "yunion.io/x/onecloud/pkg/cloudcommon/db" "yunion.io/x/onecloud/pkg/cloudcommon/validators" @@ -422,3 +424,47 @@ func (self *SAliyunRegionDriver) ValidateUpdateLoadbalancerListenerData(ctx cont } return self.SManagedVirtualizationRegionDriver.ValidateUpdateLoadbalancerListenerData(ctx, userCred, data, lblis, backendGroup) } + +func daysValidate(days []int, min, max int) ([]int, error) { + if len(days) == 0 { + return days, nil + } + sort.Ints(days) + + var tmp *int + for i := 0; i < len(days); i++ { + if days[i] < min || days[i] > max { + return days, fmt.Errorf("Day %d out of range", days[i]) + } + if tmp != nil && *tmp == days[i] { + return days, fmt.Errorf("Has repeat day %v", days) + } else { + tmp = &days[i] + } + } + return days, nil +} + +func (self *SAliyunRegionDriver) ValidateCreateSnapshotPolicyData(ctx context.Context, userCred mcclient.TokenCredential, data *compute.SSnapshotPolicyCreateInput) error { + var err error + if data.RetentionDays < -1 || data.RetentionDays == 0 || data.RetentionDays > 65535 { + return httperrors.NewInputParameterError("Retention days must in 1~65535 or -1") + } + + if len(data.RepeatWeekdays) == 0 { + return httperrors.NewMissingParameterError("repeat_weekdays") + } + data.RepeatWeekdays, err = daysValidate(data.RepeatWeekdays, 1, 7) + if err != nil { + return httperrors.NewInputParameterError(err.Error()) + } + + if len(data.TimePoints) == 0 { + return httperrors.NewInputParameterError("time_points") + } + data.TimePoints, err = daysValidate(data.TimePoints, 0, 23) + if err != nil { + return httperrors.NewInputParameterError(err.Error()) + } + return nil +} diff --git a/pkg/compute/regiondrivers/base.go b/pkg/compute/regiondrivers/base.go index b615d090df..38f7118e72 100644 --- a/pkg/compute/regiondrivers/base.go +++ b/pkg/compute/regiondrivers/base.go @@ -18,6 +18,7 @@ import ( "context" "fmt" + "yunion.io/x/onecloud/pkg/apis/compute" "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" "yunion.io/x/onecloud/pkg/cloudprovider" "yunion.io/x/onecloud/pkg/compute/models" @@ -118,3 +119,23 @@ func (self *SBaseRegionDriver) RequestCreateLoadbalancerListenerRule(ctx context func (self *SBaseRegionDriver) RequestDeleteLoadbalancerListenerRule(ctx context.Context, userCred mcclient.TokenCredential, lbr *models.SLoadbalancerListenerRule, task taskman.ITask) error { return fmt.Errorf("Not Implement RequestDeleteLoadbalancerListenerRule") } + +func (self *SBaseRegionDriver) ValidateCreateSnapshotPolicyData(ctx context.Context, userCred mcclient.TokenCredential, data *compute.SSnapshotPolicyCreateInput) error { + return fmt.Errorf("Not Implement ValidateCreateSnapshotPolicyData") +} + +func (self *SBaseRegionDriver) RequestCreateSnapshotPolicy(ctx context.Context, userCred mcclient.TokenCredential, sp *models.SSnapshotPolicy, task taskman.ITask) error { + return fmt.Errorf("Not Implement RequestCreateSnapshotPolicy") +} + +func (self *SBaseRegionDriver) RequestDeleteSnapshotPolicy(ctx context.Context, userCred mcclient.TokenCredential, sp *models.SSnapshotPolicy, task taskman.ITask) error { + return fmt.Errorf("Not Implement RequestDeleteSnapshotPolicy") +} + +func (self *SBaseRegionDriver) RequestApplySnapshotPolicy(ctx context.Context, userCred mcclient.TokenCredential, sp *models.SSnapshotPolicy, task taskman.ITask, diskIds []string) error { + return fmt.Errorf("Not Implement RequestApplySnapshotPolicy") +} + +func (self *SBaseRegionDriver) RequestCancelSnapshotPolicy(ctx context.Context, userCred mcclient.TokenCredential, region cloudprovider.ICloudRegion, task taskman.ITask, diskIds []string) error { + return fmt.Errorf("Not Implement RequestApplySnapshotPolicy") +} diff --git a/pkg/compute/regiondrivers/managedvirtual.go b/pkg/compute/regiondrivers/managedvirtual.go index b4e2e94f20..08d2305457 100644 --- a/pkg/compute/regiondrivers/managedvirtual.go +++ b/pkg/compute/regiondrivers/managedvirtual.go @@ -18,6 +18,7 @@ import ( "context" "database/sql" "fmt" + "time" "yunion.io/x/jsonutils" "yunion.io/x/log" @@ -832,3 +833,76 @@ func (self *SManagedVirtualizationRegionDriver) ValidateCreateVpcData(ctx contex func (self *SManagedVirtualizationRegionDriver) ValidateCreateEipData(ctx context.Context, userCred mcclient.TokenCredential, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) { return data, nil } + +func (self *SManagedVirtualizationRegionDriver) RequestCreateSnapshotPolicy(ctx context.Context, userCred mcclient.TokenCredential, sp *models.SSnapshotPolicy, task taskman.ITask) error { + taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { + iRegion, err := sp.GetIRegion() + if err != nil { + return nil, err + } + input, err := sp.GenerateCreateSpParams() + if err != nil { + return nil, err + } + policyId, err := iRegion.CreateSnapshotPolicy(input) + if err != nil { + return nil, err + } + sp.SetExternalId(userCred, policyId) + if err != nil { + return nil, err + } + + iPolicy, err := iRegion.GetISnapshotPolicyById(policyId) + if err != nil { + return nil, err + } + err = cloudprovider.WaitStatus(iPolicy, api.SNAPSHOT_POLICY_READY, 10*time.Second, 300*time.Second) + if err != nil { + return nil, err + } + return nil, nil + }) + return nil +} + +func (self *SManagedVirtualizationRegionDriver) RequestDeleteSnapshotPolicy(ctx context.Context, userCred mcclient.TokenCredential, sp *models.SSnapshotPolicy, task taskman.ITask) error { + taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { + iRegion, err := sp.GetIRegion() + if err != nil { + return nil, err + } + err = iRegion.DeleteSnapshotPolicy(sp.GetExternalId()) + if err != nil { + return nil, err + } + return nil, nil + }) + return nil +} + +func (self *SManagedVirtualizationRegionDriver) RequestApplySnapshotPolicy(ctx context.Context, userCred mcclient.TokenCredential, sp *models.SSnapshotPolicy, task taskman.ITask, diskIds []string) error { + taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { + iRegion, err := sp.GetIRegion() + if err != nil { + return nil, err + } + err = iRegion.ApplySnapshotPolicyToDisks(sp.GetExternalId(), diskIds) + if err != nil { + return nil, err + } + return nil, nil + }) + return nil +} + +func (self *SManagedVirtualizationRegionDriver) RequestCancelSnapshotPolicy(ctx context.Context, userCred mcclient.TokenCredential, region cloudprovider.ICloudRegion, task taskman.ITask, diskIds []string) error { + taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { + err := region.CancelSnapshotPolicyToDisks(diskIds) + if err != nil { + return nil, err + } + return nil, nil + }) + return nil +} diff --git a/pkg/compute/service/handlers.go b/pkg/compute/service/handlers.go index 8b730a9954..ebb68845b5 100644 --- a/pkg/compute/service/handlers.go +++ b/pkg/compute/service/handlers.go @@ -83,6 +83,7 @@ func InitHandlers(app *appsrv.Application) { models.DnsRecordManager, models.ElasticipManager, models.SnapshotManager, + models.SnapshotPolicyManager, models.BaremetalagentManager, models.LoadbalancerManager, models.LoadbalancerListenerManager, diff --git a/pkg/compute/tasks/snapshot_policy_delete_task.go b/pkg/compute/tasks/snapshot_policy_delete_task.go new file mode 100644 index 0000000000..4a75ce1025 --- /dev/null +++ b/pkg/compute/tasks/snapshot_policy_delete_task.go @@ -0,0 +1,54 @@ +package tasks + +import ( + "context" + "fmt" + + "yunion.io/x/jsonutils" + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/cloudcommon/notifyclient" + "yunion.io/x/onecloud/pkg/compute/models" + "yunion.io/x/onecloud/pkg/util/logclient" +) + +type SnapshotPolicyDeleteTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(SnapshotPolicyDeleteTask{}) +} + +func (self *SnapshotPolicyDeleteTask) taskFail(ctx context.Context, sp *models.SSnapshotPolicy, reason string) { + sp.SetStatus(self.GetUserCred(), api.SNAPSHOT_POLICY_DELETE_FAILED, reason) + db.OpsLog.LogEvent(sp, db.ACT_DELOCATE_FAIL, reason, self.UserCred) + logclient.AddActionLogWithStartable(self, sp, logclient.ACT_DELOCATE, reason, self.UserCred, false) + notifyclient.NotifySystemError(sp.Id, sp.Name, api.SNAPSHOT_POLICY_DELETE_FAILED, reason) + self.SetStageFailed(ctx, reason) +} + +func (self *SnapshotPolicyDeleteTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + sp := obj.(*models.SSnapshotPolicy) + region := sp.GetRegion() + if region == nil { + self.taskFail(ctx, sp, fmt.Sprintf("failed to find region for sp %s", sp.Name)) + return + } + self.SetStage("OnSnapshotPolicyDeleteComplete", nil) + if err := region.GetDriver().RequestDeleteSnapshotPolicy(ctx, self.GetUserCred(), sp, self); err != nil { + self.taskFail(ctx, sp, err.Error()) + } +} + +func (self *SnapshotPolicyDeleteTask) OnSnapshotPolicyDeleteComplete(ctx context.Context, sp *models.SSnapshotPolicy, data jsonutils.JSONObject) { + db.OpsLog.LogEvent(sp, db.ACT_DELETE, sp.GetShortDesc(ctx), self.UserCred) + logclient.AddActionLogWithStartable(self, sp, logclient.ACT_DELOCATE, nil, self.UserCred, true) + sp.RealDelete(ctx, self.UserCred) + self.SetStageComplete(ctx, nil) +} + +func (self *SnapshotPolicyDeleteTask) OnSnapshotPolicyDeleteCompleteFailed(ctx context.Context, sp *models.SSnapshotPolicy, data jsonutils.JSONObject) { + self.taskFail(ctx, sp, data.String()) +} diff --git a/pkg/compute/tasks/snapshotpolicy_create_task.go b/pkg/compute/tasks/snapshotpolicy_create_task.go new file mode 100644 index 0000000000..20bfe3125a --- /dev/null +++ b/pkg/compute/tasks/snapshotpolicy_create_task.go @@ -0,0 +1,202 @@ +package tasks + +import ( + "context" + "fmt" + + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/cloudcommon/notifyclient" + "yunion.io/x/onecloud/pkg/compute/models" + "yunion.io/x/onecloud/pkg/util/logclient" +) + +type SnapshotPolicyCreateTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(SnapshotPolicyCreateTask{}) + taskman.RegisterTask(SnapshotPolicyApplyTask{}) + taskman.RegisterTask(SnapshotPolicyCancelTask{}) +} + +func (self *SnapshotPolicyCreateTask) taskFail(ctx context.Context, sp *models.SSnapshotPolicy, reason string) { + sp.SetStatus(self.UserCred, compute.SNAPSHOT_POLICY_CREATE_FAILED, "") + db.OpsLog.LogEvent(sp, db.ACT_ALLOCATE_FAIL, reason, self.UserCred) + logclient.AddActionLogWithStartable(self, sp, logclient.ACT_CREATE, false, self.UserCred, false) + notifyclient.NotifySystemError(sp.GetId(), sp.Name, compute.SNAPSHOT_POLICY_CREATE_FAILED, reason) + self.SetStageFailed(ctx, reason) +} + +func (self *SnapshotPolicyCreateTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + snapshotPolicy := obj.(*models.SSnapshotPolicy) + + region := snapshotPolicy.GetRegion() + if region == nil { + self.taskFail(ctx, snapshotPolicy, fmt.Sprintf("failed to find region for snapshot policy %s", snapshotPolicy.Name)) + return + } + self.SetStage("OnSnapshotPolicyCreate", nil) + if err := region.GetDriver().RequestCreateSnapshotPolicy(ctx, self.GetUserCred(), snapshotPolicy, self); err != nil { + self.taskFail(ctx, snapshotPolicy, err.Error()) + } +} + +func (self *SnapshotPolicyCreateTask) OnSnapshotPolicyCreate( + ctx context.Context, sp *models.SSnapshotPolicy, data jsonutils.JSONObject, +) { + sp.SetStatus(self.UserCred, compute.SNAPSHOT_POLICY_READY, "") + db.OpsLog.LogEvent(sp, db.ACT_ALLOCATE, sp.GetShortDesc(ctx), self.UserCred) + logclient.AddActionLogWithStartable(self, sp, logclient.ACT_CREATE, nil, self.UserCred, true) + self.SetStageComplete(ctx, nil) +} + +func (self *SnapshotPolicyCreateTask) OnSnapshotPolicyCreateFailed( + ctx context.Context, sp *models.SSnapshotPolicy, data jsonutils.JSONObject, +) { + self.taskFail(ctx, sp, data.String()) +} + +// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +type SnapshotPolicyApplyTask struct { + taskman.STask +} + +func (self *SnapshotPolicyApplyTask) taskFail(ctx context.Context, sp *models.SSnapshotPolicy, reason string) { + stringIds, _ := getDiskIds(self) + disks := make([]models.SDisk, 0) + models.DiskManager.Query().In("id", stringIds).All(&disks) + for i := 0; i < len(disks); i++ { + disks[i].SetModelManager(models.DiskManager) + db.OpsLog.LogEvent(&disks[i], db.ACT_APPLY_SNAPSHOT_POLICY_FAILED, reason, self.UserCred) + logclient.AddActionLogWithStartable(self, &disks[i], logclient.ACT_APPLY_SNAPSHOT_POLICY, reason, self.UserCred, false) + } + self.SetStageFailed(ctx, reason) +} + +func getDiskIds(task *SnapshotPolicyApplyTask) ([]string, error) { + diskIds, err := task.Params.GetArray("disk_ids") + if err != nil { + return nil, fmt.Errorf("Missing parasm disk_ids") + } + stringIds := make([]string, len(diskIds)) + for i := 0; i < len(diskIds); i++ { + stringIds[i], _ = diskIds[i].GetString() + } + return stringIds, nil +} + +func (self *SnapshotPolicyApplyTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + snapshotPolicy := obj.(*models.SSnapshotPolicy) + + region := snapshotPolicy.GetRegion() + if region == nil { + self.taskFail(ctx, snapshotPolicy, fmt.Sprintf("failed to find region for snapshot policy %s", snapshotPolicy.Name)) + return + } + + stringIds, err := getDiskIds(self) + if err != nil { + self.taskFail(ctx, snapshotPolicy, err.Error()) + return + } + + diskExt, err := models.DiskManager.Query("external_id").In("id", stringIds).AllStringMap() + if err != nil { + self.taskFail(ctx, snapshotPolicy, fmt.Sprintf("Fetch disks external_id failed %s", err)) + return + } + + diskExtIds := make([]string, 0) + for i := 0; i < len(diskExt); i++ { + val, ok := diskExt[i]["external_id"] + if ok { + diskExtIds = append(diskExtIds, val) + } + } + + self.SetStage("OnSnapshotPolicyApply", nil) + if err := region.GetDriver().RequestApplySnapshotPolicy(ctx, self.GetUserCred(), snapshotPolicy, self, diskExtIds); err != nil { + self.taskFail(ctx, snapshotPolicy, err.Error()) + } +} + +func (self *SnapshotPolicyApplyTask) OnSnapshotPolicyApply(ctx context.Context, sp *models.SSnapshotPolicy, data jsonutils.JSONObject) { + stringIds, _ := getDiskIds(self) + disks := make([]models.SDisk, 0) + err := models.DiskManager.Query().In("id", stringIds).All(&disks) + if err != nil { + self.taskFail(ctx, sp, fmt.Sprintf("Fetch disks failed %s", err)) + return + } + for i := 0; i < len(disks); i++ { + disks[i].SetModelManager(models.DiskManager) + disks[i].SetSnapshotPolicy(sp.Id) + db.OpsLog.LogEvent(&disks[i], db.ACT_APPLY_SNAPSHOT_POLICY, nil, self.UserCred) + logclient.AddActionLogWithStartable(self, &disks[i], logclient.ACT_APPLY_SNAPSHOT_POLICY, nil, self.UserCred, true) + } + self.SetStageComplete(ctx, nil) +} + +func (self *SnapshotPolicyApplyTask) OnSnapshotPolicyApplyFailed(ctx context.Context, sp *models.SSnapshotPolicy, data jsonutils.JSONObject) { + self.taskFail(ctx, sp, data.String()) +} + +// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +type SnapshotPolicyCancelTask struct { + taskman.STask +} + +func (self *SnapshotPolicyCancelTask) taskFail(ctx context.Context, disk *models.SDisk, reason string) { + db.OpsLog.LogEvent(disk, db.ACT_CANCEL_SNAPSHOT_POLICY_FAILED, reason, self.UserCred) + logclient.AddActionLogWithStartable(self, disk, logclient.ACT_CANCEL_SNAPSHOT_POLICY, reason, self.UserCred, false) + self.SetStageFailed(ctx, reason) +} + +func (self *SnapshotPolicyCancelTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + disk := obj.(*models.SDisk) + storage := disk.GetStorage() + if storage == nil { + self.taskFail(ctx, disk, fmt.Sprintf("failed to find storage for disk %s", disk.Name)) + return + } + region := storage.GetRegion() + if region == nil { + self.taskFail(ctx, disk, fmt.Sprintf("failed to find region for disk %s", disk.Name)) + return + } + + iSnapshotPolicy, err := models.SnapshotPolicyManager.FetchById(disk.SnapshotPolicyId) + if err != nil { + self.taskFail(ctx, disk, fmt.Sprintf("failed to find snapshot policy for disk %s, %s", disk.Name, err)) + return + } + snapshotPolicy := iSnapshotPolicy.(*models.SSnapshotPolicy) + iRegion, err := snapshotPolicy.GetIRegion() + if err != nil { + self.taskFail(ctx, disk, fmt.Sprintf("failed to find region for snapshot policy %s", snapshotPolicy.Name)) + return + } + self.SetStage("OnSnapshotPolicyCancel", nil) + if err := region.GetDriver().RequestCancelSnapshotPolicy( + ctx, self.GetUserCred(), iRegion, self, []string{disk.ExternalId}); err != nil { + self.taskFail(ctx, disk, err.Error()) + } +} + +func (self *SnapshotPolicyCancelTask) OnSnapshotPolicyCancel(ctx context.Context, disk *models.SDisk, data jsonutils.JSONObject) { + disk.SetSnapshotPolicy("") + db.OpsLog.LogEvent(disk, db.ACT_CANCEL_SNAPSHOT_POLICY, "", self.UserCred) + logclient.AddActionLogWithStartable(self, disk, logclient.ACT_CANCEL_SNAPSHOT_POLICY, "", self.UserCred, true) + self.SetStageComplete(ctx, nil) +} + +func (self *SnapshotPolicyCancelTask) OnSnapshotPolicyCancelFailed(ctx context.Context, disk *models.SDisk, data jsonutils.JSONObject) { + self.taskFail(ctx, disk, data.String()) +} diff --git a/pkg/mcclient/modules/mod_snapshotpolicy.go b/pkg/mcclient/modules/mod_snapshotpolicy.go new file mode 100644 index 0000000000..8c85641448 --- /dev/null +++ b/pkg/mcclient/modules/mod_snapshotpolicy.go @@ -0,0 +1,27 @@ +// Copyright 2019 Yunion +// +// 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 modules + +var ( + SnapshotPoliciy ResourceManager +) + +func init() { + SnapshotPoliciy = NewComputeManager("snapshotpolicy", "snapshotpolicies", + []string{"ID", "Name", "Status", "Retention_Days", "Repeat_Weekdays", "Time_Points"}, + []string{}) + + registerCompute(&SnapshotPoliciy) +} diff --git a/pkg/mcclient/modules/mod_snapshots.go b/pkg/mcclient/modules/mod_snapshots.go index 1d92526de2..5227640356 100644 --- a/pkg/mcclient/modules/mod_snapshots.go +++ b/pkg/mcclient/modules/mod_snapshots.go @@ -24,5 +24,5 @@ func init() { "Disk_id", "Guest_id", "Created_at"}, []string{"Storage_id", "Storage_type", "Create_by", "Location", "Out_of_chain", "disk_type", "provider"}) - registerComputeV2(&Snapshots) + registerCompute(&Snapshots) } diff --git a/pkg/multicloud/disk_base.go b/pkg/multicloud/disk_base.go new file mode 100644 index 0000000000..ea352b5eac --- /dev/null +++ b/pkg/multicloud/disk_base.go @@ -0,0 +1,7 @@ +package multicloud + +type SDisk struct{} + +func (self *SDisk) GetExtSnapshotPolicyId() string { + return "" +} diff --git a/pkg/multicloud/region_base.go b/pkg/multicloud/region_base.go new file mode 100644 index 0000000000..e30dcacdc6 --- /dev/null +++ b/pkg/multicloud/region_base.go @@ -0,0 +1,33 @@ +package multicloud + +import ( + "fmt" + + "yunion.io/x/onecloud/pkg/cloudprovider" +) + +type SRegion struct{} + +func (r *SRegion) CreateSnapshotPolicy(input *cloudprovider.SnapshotPolicyInput) (string, error) { + return "", fmt.Errorf("CreateSnapshotPolicy not implement") +} + +func (r *SRegion) GetISnapshotPolicyById(snapshotPolicyId string) (cloudprovider.ICloudSnapshotPolicy, error) { + return nil, fmt.Errorf("GetISnapshotPolicyById not implement") +} + +func (self *SRegion) GetISnapshotPolicies() ([]cloudprovider.ICloudSnapshotPolicy, error) { + return nil, fmt.Errorf("GetISnapshotPolicies not implement") +} + +func (self *SRegion) DeleteSnapshotPolicy(string) error { + return fmt.Errorf("DeleteSnapshotPolicy not implement") +} + +func (self *SRegion) ApplySnapshotPolicyToDisks(snapshotPolicyId string, diskIds []string) error { + return fmt.Errorf("ApplySnapshotPolicyToDisks not implement") +} + +func (self *SRegion) CancelSnapshotPolicyToDisks(diskIds []string) error { + return fmt.Errorf("ApplySnapshotPolicyToDisks not implement") +} diff --git a/pkg/util/aliyun/disk.go b/pkg/util/aliyun/disk.go index 272465578b..180690f370 100644 --- a/pkg/util/aliyun/disk.go +++ b/pkg/util/aliyun/disk.go @@ -25,6 +25,7 @@ import ( api "yunion.io/x/onecloud/pkg/apis/compute" "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" ) type SMountInstances struct { @@ -37,6 +38,7 @@ type STags struct { type SDisk struct { storage *SStorage + multicloud.SDisk AttachedTime time.Time AutoSnapshotPolicyId string @@ -394,6 +396,10 @@ func (self *SDisk) GetCreatedAt() time.Time { return self.CreationTime } +func (self *SDisk) GetExtSnapshotPolicyId() string { + return self.AutoSnapshotPolicyId +} + func (self *SDisk) GetExpiredAt() time.Time { return convertExpiredAt(self.ExpiredTime) } diff --git a/pkg/util/aliyun/region.go b/pkg/util/aliyun/region.go index dfe5512f6d..3bfa53ff1e 100644 --- a/pkg/util/aliyun/region.go +++ b/pkg/util/aliyun/region.go @@ -30,9 +30,12 @@ import ( api "yunion.io/x/onecloud/pkg/apis/compute" "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" ) type SRegion struct { + multicloud.SRegion + client *SAliyunClient sdkClient *sdk.Client ossClient *oss.Client diff --git a/pkg/util/aliyun/snapshot_policy.go b/pkg/util/aliyun/snapshot_policy.go new file mode 100644 index 0000000000..694786ee87 --- /dev/null +++ b/pkg/util/aliyun/snapshot_policy.go @@ -0,0 +1,263 @@ +package aliyun + +import ( + "fmt" + "sort" + "strconv" + + "yunion.io/x/jsonutils" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" +) + +type SSnapshotPolicyType string + +const ( + Creating SSnapshotPolicyType = "Creating" + Available SSnapshotPolicyType = "Available" + Normal SSnapshotPolicyType = "Normal" +) + +type SSnapshotPolicy struct { + region *SRegion + + AutoSnapshotPolicyName string + AutoSnapshotPolicyId string + RepeatWeekdays string + TimePoints string + RetentionDays int + Status SSnapshotPolicyType +} + +func (self *SSnapshotPolicy) GetId() string { + return self.AutoSnapshotPolicyId +} + +func (self *SSnapshotPolicy) GetName() string { + return self.AutoSnapshotPolicyName +} + +func (self *SSnapshotPolicy) GetStatus() string { + // XXX: aliyun文档与实际返回值不符 + if self.Status == Normal || self.Status == Available { + return api.SNAPSHOT_POLICY_READY + } else if self.Status == Creating { + return api.SNAPSHOT_POLICY_CREATING + } else { + return api.SNAPSHOT_POLICY_UNKNOWN + } +} + +func (self *SSnapshotPolicy) Refresh() error { + if snapshotPolicies, total, err := self.region.GetSnapshotPolicies(self.AutoSnapshotPolicyId, 0, 1); err != nil { + return err + } else if total != 1 { + return cloudprovider.ErrNotFound + } else if err := jsonutils.Update(self, snapshotPolicies[0]); err != nil { + return err + } + return nil +} + +func (self *SSnapshotPolicy) IsEmulated() bool { + return false +} + +func (self *SSnapshotPolicy) GetMetadata() *jsonutils.JSONDict { + return nil +} + +func (self *SSnapshotPolicy) GetGlobalId() string { + return self.AutoSnapshotPolicyId +} + +func (self *SSnapshotPolicy) GetProjectId() string { + return "" +} + +func (self *SSnapshotPolicy) GetRetentionDays() int { + return self.RetentionDays +} + +func sliceAtoi(sa []string) ([]int, error) { + si := make([]int, 0, len(sa)) + for _, a := range sa { + i, err := strconv.Atoi(a) + if err != nil { + return si, err + } + si = append(si, i) + } + return si, nil +} + +func stringToIntDays(days []string) ([]int, error) { + idays, err := sliceAtoi(days) + if err != nil { + return nil, err + } + sort.Ints(idays) + return idays, nil +} + +func parsePolicy(policy string) ([]int, error) { + tp, err := jsonutils.ParseString(policy) + if err != nil { + return nil, fmt.Errorf("Parse policy %s error %s", policy, err) + } + atp, ok := tp.(*jsonutils.JSONArray) + if !ok { + return nil, fmt.Errorf("Policy %s Wrong format", tp) + } + return stringToIntDays(atp.GetStringArray()) +} + +func (self *SSnapshotPolicy) GetRepeatWeekdays() ([]int, error) { + return parsePolicy(self.RepeatWeekdays) +} + +func (self *SSnapshotPolicy) GetTimePoints() ([]int, error) { + return parsePolicy(self.TimePoints) +} + +func (self *SRegion) GetISnapshotPolicies() ([]cloudprovider.ICloudSnapshotPolicy, error) { + snapshotPolicies, total, err := self.GetSnapshotPolicies("", 0, 50) + if err != nil { + return nil, err + } + for len(snapshotPolicies) < total { + var parts []SSnapshotPolicy + parts, total, err = self.GetSnapshotPolicies("", len(snapshotPolicies), 50) + if err != nil { + return nil, err + } + snapshotPolicies = append(snapshotPolicies, parts...) + } + ret := make([]cloudprovider.ICloudSnapshotPolicy, len(snapshotPolicies)) + for i := 0; i < len(snapshotPolicies); i += 1 { + ret[i] = &snapshotPolicies[i] + } + return ret, nil +} + +func (self *SRegion) GetSnapshotPolicies(policyId string, offset int, limit int) ([]SSnapshotPolicy, int, error) { + params := make(map[string]string) + + params["RegionId"] = self.RegionId + params["PageSize"] = fmt.Sprintf("%d", limit) + params["PageNumber"] = fmt.Sprintf("%d", (offset/limit)+1) + + if len(policyId) > 0 { + params["AutoSnapshotPolicyId"] = policyId + } + + body, err := self.ecsRequest("DescribeAutoSnapshotPolicyEx", params) + if err != nil { + return nil, 0, fmt.Errorf("GetSnapshotPolicys fail %s", err) + } + + snapshotPolicies := make([]SSnapshotPolicy, 0) + if err := body.Unmarshal(&snapshotPolicies, "AutoSnapshotPolicies", "AutoSnapshotPolicy"); err != nil { + return nil, 0, fmt.Errorf("Unmarshal snapshot policies details fail %s", err) + } + total, _ := body.Int("TotalCount") + for i := 0; i < len(snapshotPolicies); i += 1 { + snapshotPolicies[i].region = self + } + return snapshotPolicies, int(total), nil +} + +func (self *SSnapshotPolicy) Delete() error { + if self.region == nil { + return fmt.Errorf("Not init region for snapshotPolicy %s", self.AutoSnapshotPolicyId) + } + return self.region.DeleteSnapshotPolicy(self.AutoSnapshotPolicyId) +} + +func (self *SRegion) DeleteSnapshotPolicy(snapshotPolicyId string) error { + params := make(map[string]string) + params["autoSnapshotPolicyId"] = snapshotPolicyId + params["regionId"] = self.RegionId + _, err := self.ecsRequest("DeleteAutoSnapshotPolicy", params) + return err +} + +func (self *SRegion) GetISnapshotPolicyById(snapshotPolicyId string) (cloudprovider.ICloudSnapshotPolicy, error) { + policies, _, err := self.GetSnapshotPolicies(snapshotPolicyId, 0, 1) + if err != nil { + return nil, err + } + if len(policies) == 0 { + return nil, cloudprovider.ErrNotFound + } + return &policies[0], nil +} + +func (self *SRegion) CreateSnapshotPolicy(input *cloudprovider.SnapshotPolicyInput) (string, error) { + if input.RepeatWeekdays == nil { + return "", fmt.Errorf("Can't create snapshot policy with nil repeatWeekdays") + } + if input.TimePoints == nil { + return "", fmt.Errorf("Can't create snapshot policy with nil timePoints") + } + params := make(map[string]string) + params["RegionId"] = self.RegionId + params["repeatWeekdays"] = jsonutils.Marshal(input.GetStringArrayRepeatWeekdays()).String() + params["timePoints"] = jsonutils.Marshal(input.GetStringArrayTimePoints()).String() + params["retentionDays"] = strconv.Itoa(input.RetentionDays) + params["autoSnapshotPolicyName"] = input.PolicyName + if body, err := self.ecsRequest("CreateAutoSnapshotPolicy", params); err != nil { + return "", fmt.Errorf("CreateAutoSnapshotPolicy fail %s", err) + } else { + return body.GetString("AutoSnapshotPolicyId") + } +} + +func (self *SRegion) UpdateSnapshotPolicy( + snapshotPolicyId string, retentionDays *int, + repeatWeekdays, timePoints *jsonutils.JSONArray, policyName string, +) error { + params := make(map[string]string) + params["RegionId"] = self.RegionId + if len(policyName) > 0 { + params["autoSnapshotPolicyName"] = policyName + } + if retentionDays != nil { + params["retentionDays"] = strconv.Itoa(*retentionDays) + } + if repeatWeekdays != nil { + params["repeatWeekdays"] = repeatWeekdays.String() + } + if timePoints != nil { + params["timePoints"] = timePoints.String() + } + _, err := self.ecsRequest("ModifyAutoSnapshotPolicyEx", params) + if err != nil { + return fmt.Errorf("ModifyAutoSnapshotPolicyEx Fail %s", err) + } + return nil +} + +func (self *SRegion) ApplySnapshotPolicyToDisks(snapshotPolicyId string, diskIds []string) error { + params := make(map[string]string) + params["RegionId"] = self.RegionId + params["autoSnapshotPolicyId"] = snapshotPolicyId + params["diskIds"] = jsonutils.Marshal(diskIds).String() + _, err := self.ecsRequest("ApplyAutoSnapshotPolicy", params) + if err != nil { + return fmt.Errorf("ApplyAutoSnapshotPolicy Fail %s", err) + } + return nil +} + +func (self *SRegion) CancelSnapshotPolicyToDisks(diskIds []string) error { + params := make(map[string]string) + params["RegionId"] = self.RegionId + params["diskIds"] = jsonutils.Marshal(diskIds).String() + _, err := self.ecsRequest("CancelAutoSnapshotPolicy", params) + if err != nil { + return fmt.Errorf("CancelAutoSnapshotPolicy Fail %s", err) + } + return nil +} diff --git a/pkg/util/aws/disk.go b/pkg/util/aws/disk.go index 18002c7e47..d24a220a94 100644 --- a/pkg/util/aws/disk.go +++ b/pkg/util/aws/disk.go @@ -29,6 +29,7 @@ import ( "yunion.io/x/onecloud/pkg/apis/billing" api "yunion.io/x/onecloud/pkg/apis/compute" "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" ) type SMountInstances struct { @@ -41,6 +42,7 @@ type STags struct { type SDisk struct { storage *SStorage + multicloud.SDisk RegionId string ZoneId string // AvailabilityZone diff --git a/pkg/util/aws/region.go b/pkg/util/aws/region.go index 4dae8f5ed1..9353ec8edd 100644 --- a/pkg/util/aws/region.go +++ b/pkg/util/aws/region.go @@ -29,6 +29,7 @@ import ( api "yunion.io/x/onecloud/pkg/apis/compute" "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" ) var RegionLocations = map[string]string{ @@ -54,6 +55,8 @@ var RegionLocations = map[string]string{ } type SRegion struct { + multicloud.SRegion + client *SAwsClient ec2Client *ec2.EC2 iamClient *iam.IAM diff --git a/pkg/util/azure/classic_disk.go b/pkg/util/azure/classic_disk.go index 99ed130b54..d150df9f79 100644 --- a/pkg/util/azure/classic_disk.go +++ b/pkg/util/azure/classic_disk.go @@ -25,10 +25,12 @@ import ( billing_api "yunion.io/x/onecloud/pkg/apis/billing" api "yunion.io/x/onecloud/pkg/apis/compute" "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" ) type SClassicDisk struct { storage *SClassicStorage + multicloud.SDisk DiskName string Caching string diff --git a/pkg/util/azure/disk.go b/pkg/util/azure/disk.go index f4097d86c6..f5ff2e76f4 100644 --- a/pkg/util/azure/disk.go +++ b/pkg/util/azure/disk.go @@ -26,6 +26,7 @@ import ( billing_api "yunion.io/x/onecloud/pkg/apis/billing" api "yunion.io/x/onecloud/pkg/apis/compute" "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" ) type DiskSku struct { @@ -58,6 +59,7 @@ type DiskProperties struct { type SDisk struct { storage *SStorage + multicloud.SDisk ManagedBy string `json:"managedBy,omitempty"` Sku DiskSku `json:"sku,omitempty"` diff --git a/pkg/util/azure/region.go b/pkg/util/azure/region.go index 3970e7664e..e8a859c311 100644 --- a/pkg/util/azure/region.go +++ b/pkg/util/azure/region.go @@ -23,6 +23,7 @@ import ( api "yunion.io/x/onecloud/pkg/apis/compute" "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" "yunion.io/x/onecloud/pkg/util/seclib2" ) @@ -36,6 +37,7 @@ type SVMSize struct { } type SRegion struct { + multicloud.SRegion client *SAzureClient izones []cloudprovider.ICloudZone diff --git a/pkg/util/esxi/manager.go b/pkg/util/esxi/manager.go index f404794698..fdbe27c611 100644 --- a/pkg/util/esxi/manager.go +++ b/pkg/util/esxi/manager.go @@ -34,6 +34,7 @@ import ( api "yunion.io/x/onecloud/pkg/apis/compute" "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" ) const ( @@ -42,6 +43,7 @@ const ( type SESXiClient struct { cloudprovider.SFakeOnPremiseRegion + multicloud.SRegion providerId string providerName string diff --git a/pkg/util/esxi/vdisk.go b/pkg/util/esxi/vdisk.go index c3e0d1cc1d..061eccf205 100644 --- a/pkg/util/esxi/vdisk.go +++ b/pkg/util/esxi/vdisk.go @@ -28,14 +28,18 @@ import ( api "yunion.io/x/onecloud/pkg/apis/compute" "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" ) type SVirtualDisk struct { + multicloud.SDisk + SVirtualDevice } func NewVirtualDisk(vm *SVirtualMachine, dev types.BaseVirtualDevice, index int) SVirtualDisk { return SVirtualDisk{ + multicloud.SDisk{}, NewVirtualDevice(vm, dev, index), } } diff --git a/pkg/util/huawei/disk.go b/pkg/util/huawei/disk.go index 17a6252dd5..b74b838ad6 100644 --- a/pkg/util/huawei/disk.go +++ b/pkg/util/huawei/disk.go @@ -25,6 +25,7 @@ import ( billing_api "yunion.io/x/onecloud/pkg/apis/billing" api "yunion.io/x/onecloud/pkg/apis/compute" "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" ) /* @@ -88,6 +89,7 @@ type VolumeImageMetadata struct { // https://support.huaweicloud.com/api-evs/zh-cn_topic_0124881427.html type SDisk struct { storage *SStorage + multicloud.SDisk ID string `json:"id"` Name string `json:"name"` diff --git a/pkg/util/huawei/region.go b/pkg/util/huawei/region.go index fa7c2e132d..30d1d96e67 100644 --- a/pkg/util/huawei/region.go +++ b/pkg/util/huawei/region.go @@ -26,6 +26,7 @@ import ( api "yunion.io/x/onecloud/pkg/apis/compute" "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" "yunion.io/x/onecloud/pkg/util/huawei/client" "yunion.io/x/onecloud/pkg/util/huawei/obs" ) @@ -37,6 +38,8 @@ type Locales struct { // https://support.huaweicloud.com/api-iam/zh-cn_topic_0067148043.html type SRegion struct { + multicloud.SRegion + client *SHuaweiClient ecsClient *client.Client obsClient *obs.ObsClient // 对象存储client.请勿直接引用。 diff --git a/pkg/util/logclient/logclient.go b/pkg/util/logclient/logclient.go index fc2b7415b8..0ccbdd650a 100644 --- a/pkg/util/logclient/logclient.go +++ b/pkg/util/logclient/logclient.go @@ -138,6 +138,8 @@ const ( ACT_LB_ADD_LISTENER_RULE = "添加负载均衡转发规则" ACT_LB_REMOVE_LISTENER_RULE = "移除负载均衡转发规则" ACT_DELETE_BACKUP = "删除备份机" + ACT_APPLY_SNAPSHOT_POLICY = "绑定快照策略" + ACT_CANCEL_SNAPSHOT_POLICY = "取消快照策略" ACT_IMAGE_SAVE = "上传镜像" ACT_IMAGE_PROBE = "镜像检测" diff --git a/pkg/util/openstack/disk.go b/pkg/util/openstack/disk.go index d12e7f9bc5..bcf918e51a 100644 --- a/pkg/util/openstack/disk.go +++ b/pkg/util/openstack/disk.go @@ -26,6 +26,7 @@ import ( billing_api "yunion.io/x/onecloud/pkg/apis/billing" api "yunion.io/x/onecloud/pkg/apis/compute" "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" ) const ( @@ -84,6 +85,7 @@ type VolumeImageMetadata struct { type SDisk struct { storage *SStorage + multicloud.SDisk ID string Name string diff --git a/pkg/util/openstack/region.go b/pkg/util/openstack/region.go index 1c92b1edea..cfe99d2cff 100644 --- a/pkg/util/openstack/region.go +++ b/pkg/util/openstack/region.go @@ -24,10 +24,13 @@ import ( api "yunion.io/x/onecloud/pkg/apis/compute" "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" "yunion.io/x/onecloud/pkg/util/httputils" ) type SRegion struct { + multicloud.SRegion + client *SOpenStackClient Name string diff --git a/pkg/util/qcloud/disk.go b/pkg/util/qcloud/disk.go index 0ba185359d..1856e66c50 100644 --- a/pkg/util/qcloud/disk.go +++ b/pkg/util/qcloud/disk.go @@ -29,6 +29,7 @@ import ( billing_api "yunion.io/x/onecloud/pkg/apis/billing" api "yunion.io/x/onecloud/pkg/apis/compute" "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" ) type Placement struct { @@ -38,6 +39,7 @@ type Placement struct { type SDisk struct { storage *SStorage + multicloud.SDisk Attached bool AutoRenewFlagError bool diff --git a/pkg/util/qcloud/localdisk.go b/pkg/util/qcloud/localdisk.go index 8c28d93acd..eaa8e31c17 100644 --- a/pkg/util/qcloud/localdisk.go +++ b/pkg/util/qcloud/localdisk.go @@ -22,9 +22,12 @@ import ( api "yunion.io/x/onecloud/pkg/apis/compute" "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" ) type SLocalDisk struct { + multicloud.SDisk + storage *SLocalStorage DiskId string DiskSize float32 diff --git a/pkg/util/qcloud/region.go b/pkg/util/qcloud/region.go index 324199c26d..cbd8512221 100644 --- a/pkg/util/qcloud/region.go +++ b/pkg/util/qcloud/region.go @@ -26,9 +26,12 @@ import ( api "yunion.io/x/onecloud/pkg/apis/compute" "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" ) type SRegion struct { + multicloud.SRegion + client *SQcloudClient cosClient *cos.Client diff --git a/pkg/util/ucloud/disk.go b/pkg/util/ucloud/disk.go index 72abbc7b71..119ca938a2 100644 --- a/pkg/util/ucloud/disk.go +++ b/pkg/util/ucloud/disk.go @@ -27,11 +27,13 @@ import ( billing_api "yunion.io/x/onecloud/pkg/apis/billing" api "yunion.io/x/onecloud/pkg/apis/compute" "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" ) // https://docs.ucloud.cn/api/udisk-api/describe_udisk type SDisk struct { storage *SStorage + multicloud.SDisk Status string `json:"Status"` DeviceName string `json:"DeviceName"` diff --git a/pkg/util/ucloud/region.go b/pkg/util/ucloud/region.go index 426d46b3ad..6d74326ceb 100644 --- a/pkg/util/ucloud/region.go +++ b/pkg/util/ucloud/region.go @@ -18,14 +18,17 @@ import ( "fmt" "sort" "strings" + "yunion.io/x/jsonutils" "yunion.io/x/pkg/util/secrules" api "yunion.io/x/onecloud/pkg/apis/compute" "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" ) type SRegion struct { + multicloud.SRegion client *SUcloudClient RegionID string diff --git a/pkg/util/zstack/disk.go b/pkg/util/zstack/disk.go index c23efbab3d..11c372f731 100644 --- a/pkg/util/zstack/disk.go +++ b/pkg/util/zstack/disk.go @@ -8,12 +8,15 @@ import ( "yunion.io/x/jsonutils" "yunion.io/x/log" - "yunion.io/x/onecloud/pkg/cloudprovider" api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" ) type SDisk struct { + multicloud.SDisk + localStorage *SLocalStorage storage *SStorage region *SRegion diff --git a/pkg/util/zstack/region.go b/pkg/util/zstack/region.go index cb4867cae9..766e1f11aa 100644 --- a/pkg/util/zstack/region.go +++ b/pkg/util/zstack/region.go @@ -6,13 +6,15 @@ import ( "yunion.io/x/jsonutils" "yunion.io/x/log" - "yunion.io/x/onecloud/pkg/cloudprovider" "yunion.io/x/pkg/util/secrules" api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" ) type SRegion struct { + multicloud.SRegion client *SZStackClient Name string