From 99687f759eeba27478dce3372f9b7707f4197a1b Mon Sep 17 00:00:00 2001 From: Rain Date: Mon, 12 Aug 2019 21:01:10 +0800 Subject: [PATCH] local snapshotpolicy --- cmd/climc/shell/disks.go | 27 - cmd/climc/shell/snapshot_policy.go | 50 +- cmd/climc/shell/snapshotpolicy_disk.go | 2 +- docs/index.yaml | 15 + docs/parameters/snapshotpolicy.yaml | 28 + docs/schemas/snapshotpolicy.yaml | 189 ++++ docs/snapshotpolicy/bind-disk.yaml | 16 + docs/snapshotpolicy/snapshotpolicies.yaml | 32 + docs/snapshotpolicy/snapshotpolicy.yaml | 22 + docs/snapshotpolicy/snapshotpolicydisk.yaml | 11 + docs/snapshotpolicy/snapshotpolicydisk2.yaml | 11 + docs/snapshotpolicy/snapshotpolicydisk3.yaml | 23 + docs/snapshotpolicy/unbind-disk.yaml | 16 + pkg/apis/compute/snapshot.go | 6 - pkg/apis/compute/snapshot_const.go | 14 +- pkg/cloudprovider/resources.go | 3 +- pkg/compute/models/disks.go | 86 +- pkg/compute/models/purge.go | 28 +- pkg/compute/models/regiondrivers.go | 13 +- pkg/compute/models/snapshotpolicy.go | 923 +++++++++++------- pkg/compute/models/snapshotpolicycache.go | 415 ++++++++ pkg/compute/models/snapshotpolicydisks.go | 381 +++++++- pkg/compute/options/options.go | 5 + pkg/compute/regiondrivers/aliyun.go | 44 +- pkg/compute/regiondrivers/base.go | 43 +- pkg/compute/regiondrivers/kvm.go | 64 +- pkg/compute/regiondrivers/managedvirtual.go | 125 ++- pkg/compute/regiondrivers/qcloud.go | 25 +- pkg/compute/tasks/disk_delete_task.go | 12 +- .../tasks/snapshot_policy_delete_task.go | 13 +- .../tasks/snapshotpolicy_create_task.go | 173 ---- pkg/compute/tasks/snapshotpolicy_disk_task.go | 210 ++++ .../modules/mod_snapshotpolicydisk.go | 12 +- pkg/multicloud/aliyun/disk.go | 7 +- pkg/multicloud/aliyun/snapshot_policy.go | 45 +- pkg/multicloud/disk_base.go | 4 +- pkg/multicloud/qcloud/disk.go | 4 + pkg/multicloud/qcloud/snapshot_policy.go | 42 +- pkg/multicloud/region_base.go | 4 + pkg/util/bitmap/bitmap.go | 15 + pkg/util/bitmap/bitmap_test.go | 14 +- pkg/util/validate/days.go | 39 + pkg/util/validate/days_test.go | 63 ++ 43 files changed, 2449 insertions(+), 825 deletions(-) create mode 100644 docs/parameters/snapshotpolicy.yaml create mode 100644 docs/schemas/snapshotpolicy.yaml create mode 100644 docs/snapshotpolicy/bind-disk.yaml create mode 100644 docs/snapshotpolicy/snapshotpolicies.yaml create mode 100644 docs/snapshotpolicy/snapshotpolicy.yaml create mode 100644 docs/snapshotpolicy/snapshotpolicydisk.yaml create mode 100644 docs/snapshotpolicy/snapshotpolicydisk2.yaml create mode 100644 docs/snapshotpolicy/snapshotpolicydisk3.yaml create mode 100644 docs/snapshotpolicy/unbind-disk.yaml create mode 100644 pkg/compute/models/snapshotpolicycache.go delete mode 100644 pkg/compute/tasks/snapshotpolicy_create_task.go create mode 100644 pkg/compute/tasks/snapshotpolicy_disk_task.go create mode 100644 pkg/util/validate/days.go create mode 100644 pkg/util/validate/days_test.go diff --git a/cmd/climc/shell/disks.go b/cmd/climc/shell/disks.go index ee7c44cef4..51789e815e 100644 --- a/cmd/climc/shell/disks.go +++ b/cmd/climc/shell/disks.go @@ -259,33 +259,6 @@ 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 - }) type DiskChangeOwnerOptions struct { ID string `help:"Disk to change owner" json:"-"` diff --git a/cmd/climc/shell/snapshot_policy.go b/cmd/climc/shell/snapshot_policy.go index af7dfe5905..0f5a3f6532 100644 --- a/cmd/climc/shell/snapshot_policy.go +++ b/cmd/climc/shell/snapshot_policy.go @@ -16,6 +16,7 @@ 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" @@ -51,9 +52,7 @@ func init() { }) type SnapshotPolicyCreateOptions struct { - NAME string - Manager string `help:"Manager id or name"` - Cloudregion string `help:"Cloudregion id or name"` + NAME string RetentionDays int `help:"snapshot retention days"` RepeatWeekdays []int `help:"snapshot create days on week"` @@ -70,21 +69,36 @@ func init() { return nil }) - type SnapshotPolicyApplyOptions struct { - ID string `help:"ID or Name of SnapshotPolicy" json:"-"` - Disk []string `help:"Disks id to apply snapshot policy"` + type SnapshotPolicyBindDisksOptions struct { + ID string `help:"ID"` + Disk []string `help:"ids of disk"` } - 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 - }) + R(&SnapshotPolicyBindDisksOptions{}, "snapshot-policy-bind-disk", "bind snapshotpolicy to disks", + func(s *mcclient.ClientSession, opts *SnapshotPolicyBindDisksOptions) error { + params, err := options.StructToParams(opts) + if err != nil { + return err + } + sp, err := modules.SnapshotPoliciy.PerformAction(s, opts.ID, "bind-disks", params) + if err != nil { + return err + } + printObject(sp) + return nil + }) + + R(&SnapshotPolicyBindDisksOptions{}, "snapshot-policy-unbind-disk", "bind snapshotpolicy to disks", + func(s *mcclient.ClientSession, opts *SnapshotPolicyBindDisksOptions) error { + params, err := options.StructToParams(opts) + if err != nil { + return err + } + sp, err := modules.SnapshotPoliciy.PerformAction(s, opts.ID, "unbind-disks", params) + if err != nil { + return err + } + printObject(sp) + return nil + }) } diff --git a/cmd/climc/shell/snapshotpolicy_disk.go b/cmd/climc/shell/snapshotpolicy_disk.go index 84b7011bea..2b5b6e97fb 100644 --- a/cmd/climc/shell/snapshotpolicy_disk.go +++ b/cmd/climc/shell/snapshotpolicy_disk.go @@ -16,6 +16,7 @@ package shell import ( "yunion.io/x/jsonutils" + "yunion.io/x/onecloud/pkg/mcclient" "yunion.io/x/onecloud/pkg/mcclient/modules" ) @@ -76,5 +77,4 @@ func init() { return nil }, ) - } diff --git a/docs/index.yaml b/docs/index.yaml index 9476555378..a26bc749cb 100644 --- a/docs/index.yaml +++ b/docs/index.yaml @@ -254,6 +254,21 @@ paths: /natsentries/{snatentryId}: $ref: "./natgateway/snatentry.yaml" + /snapshotpolicies: + $ref: "./snapshotpolicy/snapshotpolicies.yaml" + /snapshotpolicies/{snapshotpolicyId}: + $ref: "./snapshotpolicy/snapshotpolicy.yaml" + /disks/{disk_id}/snapshotpolicies: + $ref: "./snapshotpolicy/snapshotpolicydisk.yaml" + /snapshotpolicies/{snapshotpolicy_id}/disks: + $ref: "./snapshotpolicy/snapshotpolicydisk2.yaml" + /disks/{disk_id}/snapshotpolicies/{snapshotpolicy_id}: + $ref: "./snapshotpolicy/snapshotpolicydisk3.yaml" + /snapshotpolicies/{snapshotpolicyId}/bind-disks: + $ref: "./snapshotpolicy/bind-disk.yaml" + /snapshotpolicies/{snapshotpolicyId}/unbind-disks: + $ref: "./snapshotpolicy/unbind-disk.yaml" + /loadbalancers: $ref: "./loadbalancer/loadbalancers.yaml" /loadbalancers/{loadbalancerId}: diff --git a/docs/parameters/snapshotpolicy.yaml b/docs/parameters/snapshotpolicy.yaml new file mode 100644 index 0000000000..a971785e11 --- /dev/null +++ b/docs/parameters/snapshotpolicy.yaml @@ -0,0 +1,28 @@ +snapshotpolicyId: + name: snapshotpolicyId + required: true + in: path + description: 自动快照策略的ID +diskId: + name: diskId + in: path + description: 磁盘的ID +retention_days: + name: retention_days + in: query + type: integer + description: 快照的保存时间(永久保存选-1) +repeat_weekdays: + name: repeat_weekdays + in: query + type: array + items: + type: integer + description: 做快照的时间(周几) +time_points: + name: time_points + in: query + type: array + items: + type: integer + description: 做快照的时间(每天几点) \ No newline at end of file diff --git a/docs/schemas/snapshotpolicy.yaml b/docs/schemas/snapshotpolicy.yaml new file mode 100644 index 0000000000..95ec75470e --- /dev/null +++ b/docs/schemas/snapshotpolicy.yaml @@ -0,0 +1,189 @@ +CanDelete: + type: string + example: true + description: 可否删除 +CanUpdate: + type: string + example: false + description: 可否更新 +CreatedAt: + type: string + example: 2019-05-30T08:46:12.000000Z + description: 资源创建时间 +UpdatedAt: + type: string + example: 2019-05-30T08:46:12.000000Z + description: 资源最近一次更新时间 + +SnapshotPolicy: + type: object + description: 自动快照策略 + properties: + id: + type: string + example: ea27c0ce-5870-49f3-8d57-f53e63f40361 + description: 自动快照策略ID + readOnly: true + name: + type: string + description: 自动快照策略名称 + example: snapshotpolicy-jo5pskhg + retention_days: + type: integer + example: 10 + description: 快照保存的时间(-1为永久保存) + repeat_weekdays: + type: array + items: + type: integer + example: [1, 2] + description: 做快照的时间(每周的周几) + time_points: + type: array + items: + type: integer + example: [2] + description: 做快照的时间(每天的几点) + is_activated: + type: boolean + example: true + description: 是否启用快照策略 + binding_disk_count: + type: integer + example: 3 + description: 自动快照策略绑定的磁盘数量 + can_delete: + $ref: '#/CanDelete' + can_update: + $ref: '#/CanUpdate' + created_at: + $ref: '#/CreatedAt' + updated_at: + $ref: '#/UpdatedAt' + is_emulated: + type: boolean + example: false + description: 是否是虚拟出来的实例 + status: + type: string + example: init + description: 状态 + update_version: + type: integer + example: 2 + description: 资源被更新的次数 + +SnapshotPolicyCreate: + type: object + properties: + snapshotpolicy: + type: object + required: + - name + - retention_days + - repeat_weekdays + - time_points + - is_activated + properties: + name: + type: string + example: snapshotpolicy-jo5pskhg + description: 自动快照策略名称 + retention_days: + type: integer + example: 10 + description: 快照保存的时间(-1为永久保存) + repeat_weekdays: + type: array + items: + type: integer + example: [1, 2] + description: 做快照的时间(每周的周几) + time_points: + type: array + items: + type: integer + example: [2] + description: 做快照的时间(每天的几点) + is_activated: + type: boolean + example: true + description: 是否启用快照策略 + +SnapshotPolicyListResponse: + type: object + properties: + limit: + type: integer + example: 20 + total: + type: integer + example: 12 + snapshotpolicies: + type: array + items: + $ref: '#/SnapshotPolicy' + + +SnapshotPolicyResponse: + type: object + properties: + snapshotpolicy: + type: object + $ref: '#/SnapshotPolicy' + +SnapshotPolicyDisk: + type: object + properties: + can_delete: + $ref: '#/CanDelete' + can_update: + $ref: '#/CanUpdate' + created_at: + $ref: '#/CreatedAt' + updated_at: + $ref: '#/UpdatedAt' + status: + type: string + example: init + description: 状态 + snapshotpolicy_id: + type: string + example: -be8b-4f0f-8885-797930266c6539210785 + description: 自动快照策略ID + disk_id: + type: string + example: f7749379-34b1-4219-8835-257615cf34f6 + description: 磁盘ID + +SnapshotPolicyDiskResponse: + type: object + properties: + snapshotpolicydisk: + type: object + $ref: '#/SnapshotPolicyDisk' + +SnapshotPolicyDiskListResponse: + type: object + properties: + total: + type: integer + example: 20 + snapshotpolicydisks: + type: array + items: + $ref: '#/SnapshotPolicyDisk' + +SnapshotPolicyBindDisk: + type: object + required: + - disk.0 + properties: + disk.0: + type: string + example: vv100455-79ef-43a3-8fc3-23adc74b8b87 + description: 待绑定的disk的ID + disk.1: + type: string + example: dd100399-79ef-43a3-8fc3-23adc74b8b87 + description: 待绑定的disk的ID diff --git a/docs/snapshotpolicy/bind-disk.yaml b/docs/snapshotpolicy/bind-disk.yaml new file mode 100644 index 0000000000..e9cff33f4d --- /dev/null +++ b/docs/snapshotpolicy/bind-disk.yaml @@ -0,0 +1,16 @@ +post: + summary: 批量绑定磁盘 + parameters: + - $ref: '../parameters/snapshotpolicy.yaml#/snapshotpolicyId' + - in: body + name: disks + required: true + schema: + $ref: '../schemas/snapshotpolicy.yaml#/SnapshotPolicyBindDisk' + responses: + 200: + description: 自动快照策略 + schema: + $ref: '../schemas/snapshotpolicy.yaml#/SnapshotPolicyResponse' + tags: + - snapshotpolicy \ No newline at end of file diff --git a/docs/snapshotpolicy/snapshotpolicies.yaml b/docs/snapshotpolicy/snapshotpolicies.yaml new file mode 100644 index 0000000000..cccdbc2606 --- /dev/null +++ b/docs/snapshotpolicy/snapshotpolicies.yaml @@ -0,0 +1,32 @@ +get: + summary: 按指定条件列出快照策略 + parameters: + - $ref: "../parameters/common.yaml#/offset" + - $ref: "../parameters/common.yaml#/limit" + - $ref: "../parameters/snapshotpolicy.yaml#/retention_days" + - $ref: "../parameters/snapshotpolicy.yaml#/repeat_weekdays" + - $ref: "../parameters/snapshotpolicy.yaml#/time_points" + + responses: + 200: + description: 快照策略列表信息 + schema: + $ref: "../schemas/snapshotpolicy.yaml#/SnapshotPolicyListResponse" + tags: + - snapshotpolicy + +post: + summary: 创建自动快照策略 + parameters: + - in: body + name: snapshotpolicy + required: true + schema: + $ref: '../schemas/snapshotpolicy.yaml#/SnapshotPolicyCreate' + responses: + 200: + description: 新建自动快照策略的信息 + schema: + $ref: '../schemas/snapshotpolicy.yaml#/SnapshotPolicyResponse' + tags: + - snapshotpolicy diff --git a/docs/snapshotpolicy/snapshotpolicy.yaml b/docs/snapshotpolicy/snapshotpolicy.yaml new file mode 100644 index 0000000000..787914d86a --- /dev/null +++ b/docs/snapshotpolicy/snapshotpolicy.yaml @@ -0,0 +1,22 @@ +get: + summary: 获取指定自动快照策略信息 + parameters: + - $ref: "../parameters/snapshotpolicy.yaml#/snapshotpolicyId" + responses: + 200: + description: 自动快照策略信息 + schema: + $ref: "../schemas/snapshotpolicy.yaml#/SnapshotPolicyResponse" + tags: + - snapshotpolicy + +delete: + summary: 删除指定自动快照 + parameters: + - $ref: '../parameters/snapshotpolicy.yaml#/snapshotpolicyId' + responses: + 200: + schema: + $ref: '../schemas/snapshotpolicy.yaml#/SnapshotPolicyResponse' + tags: + - snapshotpolicy diff --git a/docs/snapshotpolicy/snapshotpolicydisk.yaml b/docs/snapshotpolicy/snapshotpolicydisk.yaml new file mode 100644 index 0000000000..c75e9b0975 --- /dev/null +++ b/docs/snapshotpolicy/snapshotpolicydisk.yaml @@ -0,0 +1,11 @@ +get: + summary: 列出指定磁盘下所有绑定的自动快照策略 + parameters: + - $ref: '../parameters/snapshotpolicy.yaml#/diskId' + responses: + 200: + description: 自动快照策略ID列表 + schema: + $ref: '../schemas/snapshotpolicy.yaml#/SnapshotPolicyDiskListResponse' + tags: + - snapshotpolicy diff --git a/docs/snapshotpolicy/snapshotpolicydisk2.yaml b/docs/snapshotpolicy/snapshotpolicydisk2.yaml new file mode 100644 index 0000000000..8e84ce04ef --- /dev/null +++ b/docs/snapshotpolicy/snapshotpolicydisk2.yaml @@ -0,0 +1,11 @@ +get: + summary: 列出指定自动快照策略所有绑定的磁盘 + parameters: + - $ref: '../parameters/snapshotpolicy.yaml#/snapshotpolicyId' + responses: + 200: + description: 自动快照策略ID列表 + schema: + $ref: '../schemas/snapshotpolicy.yaml#/SnapshotPolicyDiskListResponse' + tags: + - snapshotpolicy diff --git a/docs/snapshotpolicy/snapshotpolicydisk3.yaml b/docs/snapshotpolicy/snapshotpolicydisk3.yaml new file mode 100644 index 0000000000..5d98955b2c --- /dev/null +++ b/docs/snapshotpolicy/snapshotpolicydisk3.yaml @@ -0,0 +1,23 @@ +post: + summary: 绑定指定磁盘和自动快照策略 + parameters: + - $ref: '../parameters/snapshotpolicy.yaml#/diskId' + - $ref: '../parameters/snapshotpolicy.yaml#/snapshotpolicyId' + responses: + 200: + schema: + $ref: '../schemas/snapshotpolicy.yaml#/SnapshotPolicyDiskResponse' + tags: + - snapshotpolicy + +delete: + summary: 解绑指定磁盘和自动快照策略 + parameters: + - $ref: '../parameters/snapshotpolicy.yaml#/diskId' + - $ref: '../parameters/snapshotpolicy.yaml#/snapshotpolicyId' + responses: + 200: + schema: + $ref: '../schemas/snapshotpolicy.yaml#/SnapshotPolicyDiskResponse' + tags: + - snapshotpolicy \ No newline at end of file diff --git a/docs/snapshotpolicy/unbind-disk.yaml b/docs/snapshotpolicy/unbind-disk.yaml new file mode 100644 index 0000000000..b8ab408786 --- /dev/null +++ b/docs/snapshotpolicy/unbind-disk.yaml @@ -0,0 +1,16 @@ +post: + summary: 批量解绑磁盘 + parameters: + - $ref: '../parameters/snapshotpolicy.yaml#/snapshotpolicyId' + - in: body + name: disks + required: true + schema: + $ref: '../schemas/snapshotpolicy.yaml#/SnapshotPolicyBindDisk' + responses: + 200: + description: 自动快照策略 + schema: + $ref: '../schemas/snapshotpolicy.yaml#/SnapshotPolicyResponse' + tags: + - snapshotpolicy diff --git a/pkg/apis/compute/snapshot.go b/pkg/apis/compute/snapshot.go index 4977adcde2..f766bc20eb 100644 --- a/pkg/apis/compute/snapshot.go +++ b/pkg/apis/compute/snapshot.go @@ -41,9 +41,6 @@ type SSnapshotPolicyCreateInput struct { ProjectId string `json:"project_id"` DomainId string `json:"domain_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"` @@ -56,9 +53,6 @@ type SSnapshotPolicyCreateInternalInput struct { ProjectId string DomainId string - ManagerId string - CloudregionId string - RetentionDays int RepeatWeekdays uint8 TimePoints uint32 diff --git a/pkg/apis/compute/snapshot_const.go b/pkg/apis/compute/snapshot_const.go index a7a2f10fd7..65e51ec875 100644 --- a/pkg/apis/compute/snapshot_const.go +++ b/pkg/apis/compute/snapshot_const.go @@ -26,10 +26,20 @@ const ( SNAPSHOT_DELETING = "deleting" SNAPSHOT_UNKNOWN = "unknown" - SNAPSHOT_POLICY_CREATING = "creating" + SNAPSHOT_POLICY_CREATING = "creating" + SNAPSHOT_POLICY_READY = "ready" - SNAPSHOT_POLICY_CREATE_FAILED = "create_failed" + SNAPSHOT_POLICY_UPDATING = "updating" SNAPSHOT_POLICY_UNKNOWN = "unknown" SNAPSHOT_POLICY_DELETING = "deleting" SNAPSHOT_POLICY_DELETE_FAILED = "delete_failed" + + SNAPSHOT_POLICY_APPLY = "applying" + SNAPSHOT_POLICY_APPLY_FAILED = "apply_failed" + SNAPSHOT_POLICY_CANCEL = "canceling" + SNAPSHOT_POLICY_CANCEL_FAILED = "cancel_failed" + + SNAPSHOT_POLICY_DISK_READY = "ready" + SNAPSHOT_POLICY_DISK_DELETING = "deleting" + SNAPSHOT_POLICY_DISK_DELETE_FAILED = "delete_failed" ) diff --git a/pkg/cloudprovider/resources.go b/pkg/cloudprovider/resources.go index 93da3ed59f..118fe365a1 100644 --- a/pkg/cloudprovider/resources.go +++ b/pkg/cloudprovider/resources.go @@ -79,6 +79,7 @@ type ICloudRegion interface { GetISnapshotById(snapshotId string) (ICloudSnapshot, error) CreateSnapshotPolicy(*SnapshotPolicyInput) (string, error) + UpdateSnapshotPolicy(*SnapshotPolicyInput, string) error DeleteSnapshotPolicy(string) error ApplySnapshotPolicyToDisks(snapshotPolicyId string, diskId string) error CancelSnapshotPolicyToDisks(snapshotPolicyId string, diskId string) error @@ -373,7 +374,7 @@ type ICloudDisk interface { GetISnapshot(idStr string) (ICloudSnapshot, error) GetISnapshots() ([]ICloudSnapshot, error) - GetExtSnapshotPolicyIds() []string + GetExtSnapshotPolicyIds() ([]string, error) Resize(ctx context.Context, newSizeMB int64) error Reset(ctx context.Context, snapshotId string) (string, error) diff --git a/pkg/compute/models/disks.go b/pkg/compute/models/disks.go index dc58f66fd0..b8639df768 100644 --- a/pkg/compute/models/disks.go +++ b/pkg/compute/models/disks.go @@ -24,6 +24,7 @@ import ( "yunion.io/x/jsonutils" "yunion.io/x/log" + "yunion.io/x/pkg/errors" "yunion.io/x/pkg/tristate" "yunion.io/x/pkg/util/compare" "yunion.io/x/pkg/util/fileutils" @@ -340,6 +341,14 @@ func (self *SDisk) GetRuningGuestCount() (int, error) { Filter(sqlchemy.Equals(guests.Field("status"), api.VM_RUNNING)).CountWithError() } +func (self *SDisk) DetachAfterDelete(ctx context.Context, userCred mcclient.TokenCredential) error { + err := SnapshotPolicyDiskManager.SyncDetachByDisk(ctx, userCred, nil, self) + if err != nil { + return errors.Wrap(err, "detach after delete failed") + } + return nil +} + func (self *SDisk) CustomizeCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) error { input := new(api.DiskCreateInput) if err := data.Unmarshal(input); err != nil { @@ -1191,7 +1200,11 @@ func (self *SDisk) syncRemoveCloudDisk(ctx context.Context, userCred mcclient.To self.SetStatus(userCred, api.DISK_UNKNOWN, "missing original disk after sync") return err } - // todo detach joint modle about snapshotpolicy and disk + // detach joint modle aboutsnapshotpolicy and disk + err = SnapshotPolicyDiskManager.SyncDetachByDisk(ctx, userCred, nil, self) + if err != nil { + return err + } return self.RealDelete(ctx, userCred) } @@ -1238,8 +1251,6 @@ func (self *SDisk) syncWithCloudDisk(ctx context.Context, userCred mcclient.Toke self.CreatedAt = createdAt } - // todo sync disk's snapshotpolicy - return nil }) if err != nil { @@ -1247,6 +1258,15 @@ func (self *SDisk) syncWithCloudDisk(ctx context.Context, userCred mcclient.Toke return err } + // sync disk's snapshotpolicy + snapshotpolicies, err := extDisk.GetExtSnapshotPolicyIds() + if err != nil { + return errors.Wrapf(err, "Get snapshot policies of ICloudDisk %s.", extDisk.GetId()) + } + err = SnapshotPolicyDiskManager.SyncByDisk(ctx, userCred, snapshotpolicies, syncOwnerId, self, storage) + if err != nil { + return err + } db.OpsLog.LogSyncUpdate(self, diff, userCred) SyncCloudProject(userCred, self, syncOwnerId, extDisk, storage.ManagerId) @@ -1287,14 +1307,22 @@ func (manager *SDiskManager) newFromCloudDisk(ctx context.Context, userCred mccl disk.CreatedAt = createAt } - // todo create new joint model about snapshotpolicy and disk - err = manager.TableSpec().Insert(&disk) if err != nil { log.Errorf("newFromCloudZone fail %s", err) return nil, err } + // create new joint model aboutsnapshotpolicy and disk + snapshotpolicies, err := extDisk.GetExtSnapshotPolicyIds() + if err != nil { + return nil, errors.Wrapf(err, "Get snapshot policies of ICloudDisk %s.", extDisk.GetId()) + } + err = SnapshotPolicyDiskManager.SyncAttachDiskExt(ctx, userCred, snapshotpolicies, syncOwnerId, &disk, storage) + if err != nil { + return nil, err + } + SyncCloudProject(userCred, &disk, syncOwnerId, extDisk, storage.ManagerId) db.OpsLog.LogEvent(&disk, db.ACT_CREATE, disk.GetShortDesc(ctx), userCred) @@ -1544,7 +1572,12 @@ func (self *SDisk) RealDelete(ctx context.Context, userCred mcclient.TokenCreden guestdisk.Detach(ctx, userCred) } } - return self.SSharableVirtualResourceBase.Delete(ctx, userCred) + err := self.SSharableVirtualResourceBase.Delete(ctx, userCred) + if err != nil { + return err + } + + return self.DetachAfterDelete(ctx, userCred) } func (self *SDisk) AllowPerformPurge(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { @@ -1587,7 +1620,9 @@ func (self *SDisk) CustomizeDelete(ctx context.Context, userCred mcclient.TokenC jsonutils.QueryBoolean(query, "override_pending_delete", false)) } -func (self *SDisk) getMoreDetails(extra *jsonutils.JSONDict) *jsonutils.JSONDict { +func (self *SDisk) getMoreDetails(ctx context.Context, userCred mcclient.TokenCredential, + extra *jsonutils.JSONDict) *jsonutils. + JSONDict { if cloudprovider := self.GetCloudprovider(); cloudprovider != nil { extra.Add(jsonutils.NewString(cloudprovider.Provider), "provider") } @@ -1623,6 +1658,39 @@ func (self *SDisk) getMoreDetails(extra *jsonutils.JSONDict) *jsonutils.JSONDict pendingDeletedAt := self.PendingDeletedAt.Add(time.Second * time.Duration(options.Options.PendingDeleteExpireSeconds)) extra.Add(jsonutils.NewString(timeutils.FullIsoTime(pendingDeletedAt)), "auto_delete_at") } + // the binded snapshot policy list + sds, err := SnapshotPolicyDiskManager.FetchAllByDiskID(ctx, userCred, self.Id) + if err != nil { + return extra + } + spIds := make([]string, len(sds)) + for i := range sds { + spIds[i] = sds[i].SnapshotpolicyId + } + sps, err := SnapshotPolicyManager.FetchAllByIds(spIds) + if err != nil { + return extra + } + if len(sps) == 0 { + extra.Add(jsonutils.NewString(""), "snapshotpolicy_status") + } else { + extra.Add(jsonutils.NewString(sds[0].Status), "snapshotpolicy_status") + } + // check status + // construction for snapshotpolicies attached to disk + snapshotpoliciesJson := jsonutils.NewArray() + for i := range sps { + spsJson := jsonutils.Marshal(sps[i]) + spsDict := spsJson.(*jsonutils.JSONDict) + repeatWeekdays := SnapshotPolicyManager.RepeatWeekdaysToIntArray(sps[i].RepeatWeekdays) + timePoints := SnapshotPolicyManager.TimePointsToIntArray(sps[i].TimePoints) + spsDict.Remove("repeat_weekdays") + spsDict.Remove("time_points") + spsDict.Add(jsonutils.Marshal(repeatWeekdays), "repeat_weekdays") + spsDict.Add(jsonutils.Marshal(timePoints), "time_points") + snapshotpoliciesJson.Add(spsDict) + } + extra.Add(snapshotpoliciesJson, "snapshotpolicies") return extra } @@ -1631,12 +1699,12 @@ func (self *SDisk) GetExtraDetails(ctx context.Context, userCred mcclient.TokenC if err != nil { return nil, err } - return self.getMoreDetails(extra), nil + return self.getMoreDetails(ctx, userCred, extra), nil } func (self *SDisk) GetCustomizeColumns(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) *jsonutils.JSONDict { extra := self.SSharableVirtualResourceBase.GetCustomizeColumns(ctx, userCred, query) - return self.getMoreDetails(extra) + return self.getMoreDetails(ctx, userCred, extra) } func (self *SDisk) StartDiskResizeTask(ctx context.Context, userCred mcclient.TokenCredential, sizeMb int64, parentTaskId string, pendingUsage quotas.IQuota) error { diff --git a/pkg/compute/models/purge.go b/pkg/compute/models/purge.go index ce2ea37740..4ab3f45164 100644 --- a/pkg/compute/models/purge.go +++ b/pkg/compute/models/purge.go @@ -625,14 +625,16 @@ 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) +func (manager *SSnapshotPolicyCacheManager) purgeAll(ctx context.Context, userCred mcclient.TokenCredential, + providerId string) error { + // delete snapshot policy cache belong to manager + spCaches := make([]SSnapshotPolicyCache, 0) + err := fetchByManagerId(SnapshotPolicyCacheManager, providerId, &spCaches) if err != nil { return err } - for i := range sps { - err := sps[i].purge(ctx, userCred) + for i := range spCaches { + err := spCaches[i].purge(ctx, userCred) if err != nil { return err } @@ -640,15 +642,19 @@ func (manager *SSnapshotPolicyManager) purgeAll(ctx context.Context, userCred mc 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) +func (spc *SSnapshotPolicyCache) purge(ctx context.Context, userCred mcclient.TokenCredential) error { + lockman.LockObject(ctx, spc) + defer lockman.LockObject(ctx, spc) + err := spc.ValidateDeleteCondition(ctx) if err != nil { return err } - return sp.RealDelete(ctx, userCred) + return spc.RealDetele(ctx, userCred) +} + +func (manager *SSnapshotPolicyManager) purgeAll(ctx context.Context, userCred mcclient.TokenCredential, providerId string) error { + // delete snapshot policy cache belong to manager + return SnapshotPolicyCacheManager.purgeAll(ctx, userCred, providerId) } func (manager *SStoragecacheManager) purgeAll(ctx context.Context, userCred mcclient.TokenCredential, providerId string) error { diff --git a/pkg/compute/models/regiondrivers.go b/pkg/compute/models/regiondrivers.go index bb86c143c9..be642307a6 100644 --- a/pkg/compute/models/regiondrivers.go +++ b/pkg/compute/models/regiondrivers.go @@ -20,7 +20,6 @@ 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" @@ -79,12 +78,14 @@ type IRegionDriver interface { ValidateCreateEipData(ctx context.Context, userCred mcclient.TokenCredential, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) // Region Driver Snapshot Policy Apis - ValidateCreateSnapshotPolicyData(context.Context, mcclient.TokenCredential, *compute.SSnapshotPolicyCreateInput, mcclient.IIdentityProvider, *jsonutils.JSONDict) 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 + //ValidateCreateSnapshotPolicyData(context.Context, mcclient.TokenCredential, *compute.SSnapshotPolicyCreateInput, mcclient.IIdentityProvider, *jsonutils.JSONDict) error + RequestUpdateSnapshotPolicy(ctx context.Context, userCred mcclient.TokenCredential, sp *SSnapshotPolicy, input cloudprovider.SnapshotPolicyInput, task taskman.ITask) error + RequestApplySnapshotPolicy(ctx context.Context, userCred mcclient.TokenCredential, task taskman.ITask, disk *SDisk, sp *SSnapshotPolicy, data jsonutils.JSONObject) error + RequestCancelSnapshotPolicy(ctx context.Context, userCred mcclient.TokenCredential, task taskman.ITask, disk *SDisk, sp *SSnapshotPolicy, data jsonutils.JSONObject) error + RequestPreSnapshotPolicyApply(ctx context.Context, userCred mcclient.TokenCredential, task taskman.ITask, disk *SDisk, sp *SSnapshotPolicy, data jsonutils.JSONObject) error // Region Driver Snapshot Policy joint Disk Apis - ValidateCreateSnapshopolicyDiskData(ctx context.Context, userCred mcclient.TokenCredential, diskID string) error + ValidateCreateSnapshopolicyDiskData(ctx context.Context, userCred mcclient.TokenCredential, disk *SDisk, snapshotPolicy *SSnapshotPolicy) error // Region Driver Snapshot Apis ValidateSnapshotDelete(ctx context.Context, snapshot *SSnapshot) error @@ -94,8 +95,6 @@ type IRegionDriver interface { SnapshotIsOutOfChain(disk *SDisk) bool GetDiskResetParams(snapshot *SSnapshot) *jsonutils.JSONDict OnDiskReset(ctx context.Context, userCred mcclient.TokenCredential, disk *SDisk, snapshot *SSnapshot, data jsonutils.JSONObject) error - RequestApplySnapshotPolicy(ctx context.Context, userCred mcclient.TokenCredential, sp *SSnapshotPolicy, task taskman.ITask, diskId string) error - RequestCancelSnapshotPolicy(ctx context.Context, userCred mcclient.TokenCredential, sp *SSnapshotPolicy, task taskman.ITask, diskId string) error OnSnapshotDelete(ctx context.Context, snapshot *SSnapshot, task taskman.ITask, data jsonutils.JSONObject) error //Nat gateway diff --git a/pkg/compute/models/snapshotpolicy.go b/pkg/compute/models/snapshotpolicy.go index 4626d88b97..7fa2b11144 100644 --- a/pkg/compute/models/snapshotpolicy.go +++ b/pkg/compute/models/snapshotpolicy.go @@ -17,23 +17,23 @@ package models import ( "context" "fmt" - "yunion.io/x/jsonutils" "yunion.io/x/log" + "yunion.io/x/pkg/errors" "yunion.io/x/pkg/tristate" "yunion.io/x/pkg/util/compare" - "yunion.io/x/pkg/utils" "yunion.io/x/sqlchemy" 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/compute/options" "yunion.io/x/onecloud/pkg/httperrors" "yunion.io/x/onecloud/pkg/mcclient" "yunion.io/x/onecloud/pkg/util/bitmap" + "yunion.io/x/onecloud/pkg/util/validate" ) type SSnapshotPolicyManager struct { @@ -42,10 +42,10 @@ type SSnapshotPolicyManager struct { type SSnapshotPolicy struct { db.SVirtualResourceBase - db.SExternalizedResourceBase - - SManagedResourceBase - SCloudregionResourceBase + //db.SExternalizedResourceBase + // + //SManagedResourceBase + //SCloudregionResourceBase RetentionDays int `nullable:"false" list:"user" get:"user" create:"required"` @@ -70,360 +70,36 @@ func init() { SnapshotPolicyManager.SetVirtualObject(SnapshotPolicyManager) } -func (manager *SSnapshotPolicyManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) { +func (manager *SSnapshotPolicyManager) ValidateListConditions(ctx context.Context, userCred mcclient.TokenCredential, + query *jsonutils.JSONDict) (*jsonutils.JSONDict, error) { + input := &api.SSnapshotPolicyCreateInput{} - err := data.Unmarshal(input) + err := query.Unmarshal(input) if err != nil { return nil, httperrors.NewInputParameterError("Unmarshal input failed %s", err) } - input.ProjectId = ownerId.GetProjectId() - input.DomainId = ownerId.GetProjectDomainId() - - err = db.NewNameValidator(manager, ownerId, input.Name) - if err != nil { - return nil, err + if query.Contains("repeat_weekdays") { + query.Remove("repeat_weekdays") + query.Add(jsonutils.NewInt(int64(manager.RepeatWeekdaysParseIntArray(input.RepeatWeekdays))), "repeat_weekdays") } - - cloudregionV := validators.NewModelIdOrNameValidator("cloudregion", "cloudregion", ownerId) - err = cloudregionV.Validate(data) - if err != nil { - return nil, err + if query.Contains("time_points") { + query.Remove("time_points") + query.Add(jsonutils.NewInt(int64(manager.RepeatWeekdaysParseIntArray(input.RepeatWeekdays))), "time_points") } - cloudregion := cloudregionV.Model.(*SCloudregion) - input.CloudregionId = cloudregion.GetId() - - err = cloudregion.GetDriver().ValidateCreateSnapshotPolicyData(ctx, userCred, input, ownerId, data) - if err != nil { - return nil, err - } - internalInput := manager.sSnapshotPolicyCreateInputToInternal(input) - data = internalInput.JSON(internalInput) - return data, nil + return query, nil } -func (manager *SSnapshotPolicyManager) sSnapshotPolicyCreateInputToInternal(input *api.SSnapshotPolicyCreateInput) *api.SSnapshotPolicyCreateInternalInput { - ret := api.SSnapshotPolicyCreateInternalInput{ - Meta: input.Meta, - Name: input.Name, - ProjectId: input.ProjectId, - DomainId: input.DomainId, - ManagerId: input.ManagerId, - CloudregionId: input.CloudregionId, - RetentionDays: input.RetentionDays, - } - - ret.RepeatWeekdays = manager.RepeatWeekdaysParseIntArray(input.RepeatWeekdays) - ret.TimePoints = manager.TimePointsParseIntArray(input.TimePoints) - return &ret -} - -func (manager *SSnapshotPolicyManager) sSnapshotPolicyCreateInputFromInternal(input *api.SSnapshotPolicyCreateInternalInput) *api.SSnapshotPolicyCreateInput { - return nil -} - -func (self *SSnapshotPolicy) PostCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) { - self.StartCreateSnapshotPolicy(ctx, userCred, ownerId, query, data) -} - -func (self *SSnapshotPolicy) StartCreateSnapshotPolicy(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, 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, api.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)) - - // more - weekdays := SnapshotPolicyManager.RepeatWeekdaysToIntArray(self.RepeatWeekdays) - timePoints := SnapshotPolicyManager.TimePointsToIntArray(self.TimePoints) - ret.Add(jsonutils.Marshal(weekdays), "repeat_weekdays") - ret.Add(jsonutils.Marshal(timePoints), "time_points") - return ret -} - -func (self *SSnapshotPolicy) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*jsonutils.JSONDict, error) { - ret := jsonutils.NewDict() - - // more - weekdays := SnapshotPolicyManager.RepeatWeekdaysToIntArray(self.RepeatWeekdays) - timePoints := SnapshotPolicyManager.TimePointsToIntArray(self.TimePoints) - ret.Add(jsonutils.Marshal(weekdays), "repeat_weekdays") - ret.Add(jsonutils.Marshal(timePoints), "time_points") - return ret, nil -} - -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 *SSnapshotPolicyManager) RepeatWeekdaysParseIntArray(nums []int) uint8 { - return uint8(bitmap.IntArray2Uint(nums)) -} - -func (self *SSnapshotPolicyManager) RepeatWeekdaysToIntArray(n uint8) []int { - return bitmap.Uint2IntArray(uint32(n)) -} - -func (self *SSnapshotPolicyManager) TimePointsParseIntArray(nums []int) uint32 { - return bitmap.IntArray2Uint(nums) -} - -func (self *SSnapshotPolicyManager) TimePointsToIntArray(n uint32) []int { - return bitmap.Uint2IntArray(n) -} - -func (self *SSnapshotPolicy) GenerateCreateSpParams() (*cloudprovider.SnapshotPolicyInput, error) { - intWeekdays := SnapshotPolicyManager.RepeatWeekdaysToIntArray(self.RepeatWeekdays) - intTimePoints := SnapshotPolicyManager.TimePointsToIntArray(self.TimePoints) - - 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, syncOwnerId mcclient.IIdentityProvider) compare.SyncResult { - lockman.LockClass(ctx, manager, db.GetLockClassKey(manager, syncOwnerId)) - defer lockman.ReleaseClass(ctx, manager, db.GetLockClassKey(manager, syncOwnerId)) - 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], syncOwnerId, 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, syncOwnerId, 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, ownerId mcclient.IIdentityProvider, 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 = SnapshotPolicyManager.RepeatWeekdaysParseIntArray(arw) - - atp, err := ext.GetTimePoints() - if err != nil { - return err - } - self.TimePoints = SnapshotPolicyManager.TimePointsParseIntArray(atp) - return nil - }) - db.OpsLog.LogSyncUpdate(self, diff, userCred) - SyncCloudProject(userCred, self, ownerId, ext, self.ManagerId) - return err -} - -func (manager *SSnapshotPolicyManager) newFromCloudSnapshotPolicy( - ctx context.Context, userCred mcclient.TokenCredential, - ext cloudprovider.ICloudSnapshotPolicy, region *SCloudregion, - syncOwnerId mcclient.IIdentityProvider, provider *SCloudprovider, -) (*SSnapshotPolicy, error) { - - snapshotPolicy := SSnapshotPolicy{} - snapshotPolicy.SetModelManager(manager, &snapshotPolicy) - - newName, err := db.GenerateName(manager, syncOwnerId, 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 = SnapshotPolicyManager.RepeatWeekdaysParseIntArray(arw) - atp, err := ext.GetTimePoints() - if err != nil { - return nil, err - } - snapshotPolicy.TimePoints = SnapshotPolicyManager.TimePointsParseIntArray(atp) - - err = manager.TableSpec().Insert(&snapshotPolicy) - if err != nil { - log.Errorf("newFromCloudEip fail %s", err) - return nil, err - } - - SyncCloudProject(userCred, &snapshotPolicy, syncOwnerId, 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) preCheck( - ctx context.Context, userCred mcclient.TokenCredential, - query jsonutils.JSONObject, data jsonutils.JSONObject, -) ([]string, error) { - if self.Status != api.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 +func (sp *SSnapshotPolicy) AllowUpdateItem(ctx context.Context, userCred mcclient.TokenCredential) bool { + return false } +// ==================================================== fetch ========================================================== func (manager *SSnapshotPolicyManager) GetSnapshotPoliciesAt(week, timePoint uint32) ([]string, error) { q := manager.Query("id") q = q.Filter(sqlchemy.Equals(sqlchemy.AND_Val("", q.Field("repeat_weekdays"), 1< options.Options.RetentionDaysLimit { + return nil, httperrors.NewInputParameterError("Retention days must in 1~%d or -1", options.Options.RetentionDaysLimit) + } + + if len(input.RepeatWeekdays) == 0 { + return nil, httperrors.NewMissingParameterError("repeat_weekdays") + } + + if len(input.RepeatWeekdays) > options.Options.RepeatWeekdaysLimit { + return nil, httperrors.NewInputParameterError("repeat_weekdays only contains %d days at most", + options.Options.RepeatWeekdaysLimit) + } + input.RepeatWeekdays, err = validate.DaysCheck(input.RepeatWeekdays, 1, 7) + if err != nil { + return nil, httperrors.NewInputParameterError(err.Error()) + } + + if len(input.TimePoints) == 0 { + return nil, httperrors.NewMissingParameterError("time_points") + } + if len(input.TimePoints) > options.Options.TimePointsLimit { + return nil, httperrors.NewInputParameterError("time_points only contains %d points at most", options.Options.TimePointsLimit) + } + input.TimePoints, err = validate.DaysCheck(input.TimePoints, 0, 23) + if err != nil { + return nil, httperrors.NewInputParameterError(err.Error()) + } + + internalInput := manager.sSnapshotPolicyCreateInputToInternal(input) + data = internalInput.JSON(internalInput) + return data, nil +} + +// ==================================================== update ========================================================= + +func (self *SSnapshotPolicy) AllowPerformUpdate(ctx context.Context, userCred mcclient.TokenCredential, + query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + // no fo now + return false + //return self.IsOwner(userCred) || db.IsAdminAllowPerform(userCred, self, "update") +} + +func (self *SSnapshotPolicy) PerformUpdate(ctx context.Context, userCred mcclient.TokenCredential, + query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + //check param + input := &api.SSnapshotPolicyCreateInput{} + err := data.Unmarshal(input) + if err != nil { + return nil, httperrors.NewInputParameterError("Unmarshel input failed %s", err) + } + err = self.UpdateParamCheck(input) + if err != nil { + return nil, err + } + return nil, self.StartSnapshotPolicyUpdateTask(ctx, userCred, input) +} + +func (self *SSnapshotPolicy) StartSnapshotPolicyUpdateTask(ctx context.Context, userCred mcclient.TokenCredential, + input *api.SSnapshotPolicyCreateInput) error { + + params := jsonutils.NewDict() + params.Add(jsonutils.Marshal(input), "input") + self.SetStatus(userCred, api.SNAPSHOT_POLICY_UPDATING, "") + if task, err := taskman.TaskManager.NewTask(ctx, "SnapshotPolicyUpdateTask", self, userCred, params, + "", "", nil); err == nil { + return err + } else { + task.ScheduleRun(nil) + } + return nil +} + +// UpdateParamCheck check if update parameters are correct and need to update +func (self *SSnapshotPolicy) UpdateParamCheck(input *api.SSnapshotPolicyCreateInput) error { + var err error + updateNum := 0 + + if input.RetentionDays != 0 { + if input.RetentionDays < -1 || input.RetentionDays > 65535 { + return httperrors.NewInputParameterError("Retention days must in 1~65535 or -1") + } + if input.RetentionDays != self.RetentionDays { + updateNum++ + } + } + + if input.RepeatWeekdays != nil && len(input.RepeatWeekdays) != 0 { + input.RepeatWeekdays, err = validate.DaysCheck(input.RepeatWeekdays, 1, 7) + if err != nil { + return httperrors.NewInputParameterError(err.Error()) + } + if self.RepeatWeekdays != SnapshotPolicyManager.RepeatWeekdaysParseIntArray(input.RepeatWeekdays) { + updateNum++ + } + } + + if input.TimePoints != nil && len(input.TimePoints) != 0 { + input.TimePoints, err = validate.DaysCheck(input.TimePoints, 0, 23) + if err != nil { + return httperrors.NewInputParameterError(err.Error()) + } + if self.TimePoints != SnapshotPolicyManager.TimePointsParseIntArray(input.TimePoints) { + updateNum++ + } + } + + if updateNum == 0 { + return httperrors.NewInputParameterError("Do not need to update") + } + return nil +} + +// ==================================================== delete ========================================================= + +func (self *SSnapshotPolicy) DetachAfterDelete(ctx context.Context, userCred mcclient.TokenCredential) error { + err := SnapshotPolicyDiskManager.SyncDetachBySnapshotpolicy(ctx, userCred, nil, self) + if err != nil { + return errors.Wrap(err, "detach after delete failed") + } + return nil +} + +func (self *SSnapshotPolicy) CustomizeDelete(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils. + JSONObject, data jsonutils.JSONObject) error { + + // check if self bind to some disks + sds, err := SnapshotPolicyDiskManager.FetchAllBySnapshotpolicyID(ctx, userCred, self.GetId()) + if err != nil { + return errors.Wrap(err, "fetch bind info failed") + } + if len(sds) != 0 { + return httperrors.NewBadRequestError("Couldn't delete snapshot policy binding to disks") + } + self.SetStatus(userCred, api.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.getMoreDetails(ctx, userCred, query) + return ret +} + +func (self *SSnapshotPolicy) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, + query jsonutils.JSONObject) (*jsonutils.JSONDict, error) { + + return self.getMoreDetails(ctx, userCred, query) +} + +func (self *SSnapshotPolicy) getMoreDetails(ctx context.Context, userCred mcclient.TokenCredential, + query jsonutils.JSONObject) (*jsonutils.JSONDict, error) { + + ret := query.(*jsonutils.JSONDict) + // more + weekdays := SnapshotPolicyManager.RepeatWeekdaysToIntArray(self.RepeatWeekdays) + timePoints := SnapshotPolicyManager.TimePointsToIntArray(self.TimePoints) + ret.Add(jsonutils.Marshal(weekdays), "repeat_weekdays") + ret.Add(jsonutils.Marshal(timePoints), "time_points") + count, err := SnapshotPolicyDiskManager.FetchDiskCountBySPID(self.Id) + if err != nil { + return ret, err + } + ret.Add(jsonutils.NewInt(int64(count)), "binding_disk_count") + return ret, nil +} + +// ==================================================== sync =========================================================== +func (manager *SSnapshotPolicyManager) SyncSnapshotPolicies(ctx context.Context, userCred mcclient.TokenCredential, + provider *SCloudprovider, region *SCloudregion, cloudSPs []cloudprovider.ICloudSnapshotPolicy, + syncOwnerId mcclient.IIdentityProvider) compare.SyncResult { + + lockman.LockClass(ctx, manager, db.GetLockClassKey(manager, syncOwnerId)) + defer lockman.ReleaseClass(ctx, manager, db.GetLockClassKey(manager, syncOwnerId)) + syncResult := compare.SyncResult{} + + // Fetch allsnapshotpolicy caches + spCaches, err := SnapshotPolicyCacheManager.FetchAllByRegionProvider(region.GetId(), provider.GetId()) + if err != nil { + syncResult.Error(err) + return syncResult + } + spIdSet, spIds := make(map[string]struct{}), make([]string, 0, 2) + for _, spCache := range spCaches { + if _, ok := spIdSet[spCache.SnapshotpolicyId]; !ok { + spIds = append(spIds, spCache.SnapshotpolicyId) + spIdSet[spCache.SnapshotpolicyId] = struct{}{} + } + } + + // Fetch allsnapshotpolicy of caches above + snapshotPolicies, err := manager.FetchAllByIds(spIds) + if err != nil { + syncResult.Error(err) + return syncResult + } + + // structure two sets (externalID, snapshotpolicyCache), (snapshotPolicyID, snapshotPolicy) + spSet, spCacheSet := make(map[string]*SSnapshotPolicy), make(map[string]*SSnapshotPolicyCache) + for i := range snapshotPolicies { + spSet[snapshotPolicies[i].GetId()] = &snapshotPolicies[i] + } + for i := range spCaches { + externalId := spCaches[i].ExternalId + if len(externalId) != 0 { + spCacheSet[spCaches[i].ExternalId] = &spCaches[i] + } + } + + // start sync + // add forsnapshotpolicy and cache + // delete forsnapshotpolicy cache + added := make([]cloudprovider.ICloudSnapshotPolicy, 0, 1) + removed := make([]*SSnapshotPolicyCache, 0, 1) + for _, cloudSP := range cloudSPs { + spCache, ok := spCacheSet[cloudSP.GetGlobalId()] + if !ok { + added = append(added, cloudSP) + continue + } + snapshotPolicy := spSet[spCache.SnapshotpolicyId] + if !snapshotPolicy.Equals(cloudSP) { + removed = append(removed, spCache) + added = append(added, cloudSP) + } + } + + for i := range removed { + // changesnapshotpolicy cache + err := removed[i].RealDetele(ctx, userCred) + if err != nil { + syncResult.DeleteError(err) + } + } + + for i := range added { + locol, err := manager.newFromCloudSnapshotPolicy(ctx, userCred, added[i], region, syncOwnerId, provider) + if err != nil { + syncResult.AddError(err) + } else { + syncMetadata(ctx, userCred, locol, added[i]) + syncResult.Add() + } + } + return compare.SyncResult{} +} + +func (manager *SSnapshotPolicyManager) newFromCloudSnapshotPolicy( + ctx context.Context, userCred mcclient.TokenCredential, + ext cloudprovider.ICloudSnapshotPolicy, region *SCloudregion, + syncOwnerId mcclient.IIdentityProvider, provider *SCloudprovider, +) (*SSnapshotPolicy, error) { + + snapshotPolicy := SSnapshotPolicy{} + snapshotPolicy.SetModelManager(manager, &snapshotPolicy) + + newName, err := db.GenerateName(manager, syncOwnerId, ext.GetName()) + if err != nil { + return nil, err + } + snapshotPolicy.Name = newName + snapshotPolicy.Status = ext.GetStatus() + snapshotPolicy.RetentionDays = ext.GetRetentionDays() + arw, err := ext.GetRepeatWeekdays() + if err != nil { + return nil, err + } + snapshotPolicy.RepeatWeekdays = SnapshotPolicyManager.RepeatWeekdaysParseIntArray(arw) + atp, err := ext.GetTimePoints() + if err != nil { + return nil, err + } + snapshotPolicy.TimePoints = SnapshotPolicyManager.TimePointsParseIntArray(atp) + + snapshotPolicy.IsActivated = tristate.NewFromBool(ext.IsActivated()) + + err = manager.TableSpec().Insert(&snapshotPolicy) + if err != nil { + log.Errorf("newFromCloudEip fail %s", err) + return nil, err + } + + // add cache + _, err = SnapshotPolicyCacheManager.NewCacheWithExternalId(ctx, userCred, snapshotPolicy.GetId(), + ext.GetGlobalId(), region.GetId(), provider.GetId()) + if err != nil { + //snapshotpolicy has been exist so that created is successful although cache created is fail. + // disk will be sync aftersnapshotpolicy sync, cache must be right so that this sync is fail + log.Errorf("snapshotpolicy %s created successfully but corresponding cache created fail", snapshotPolicy.GetId()) + return nil, errors.Wrapf(err, "snapshotpolicy %s created successfully but corresponding cache created fail", + snapshotPolicy.GetId()) + } + + SyncCloudProject(userCred, &snapshotPolicy, syncOwnerId, ext, provider.GetId()) + + db.OpsLog.LogEvent(&snapshotPolicy, db.ACT_CREATE, snapshotPolicy.GetShortDesc(ctx), userCred) + return &snapshotPolicy, nil +} + +func (self *SSnapshotPolicy) Equals(cloudSP cloudprovider.ICloudSnapshotPolicy) bool { + rws, err := cloudSP.GetRepeatWeekdays() + if err != nil { + return false + } + tps, err := cloudSP.GetTimePoints() + if err != nil { + return false + } + repeatWeekdays := SnapshotPolicyManager.RepeatWeekdaysParseIntArray(rws) + timePoints := SnapshotPolicyManager.TimePointsParseIntArray(tps) + + return self.RetentionDays == cloudSP.GetRetentionDays() && self.RepeatWeekdays == repeatWeekdays && self. + TimePoints == timePoints && self.IsActivated.Bool() == cloudSP.IsActivated() +} + +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) Delete(ctx context.Context, userCred mcclient.TokenCredential) error { + return nil +} + +func (self *SSnapshotPolicy) RealDelete(ctx context.Context, userCred mcclient.TokenCredential) error { + return db.DeleteModel(ctx, userCred, self) +} + +// ==================================================== utils ========================================================== + +func (manager *SSnapshotPolicyManager) sSnapshotPolicyCreateInputToInternal(input *api.SSnapshotPolicyCreateInput, +) *api.SSnapshotPolicyCreateInternalInput { + ret := api.SSnapshotPolicyCreateInternalInput{ + Meta: input.Meta, + Name: input.Name, + ProjectId: input.ProjectId, + DomainId: input.DomainId, + RetentionDays: input.RetentionDays, + } + + ret.RepeatWeekdays = manager.RepeatWeekdaysParseIntArray(input.RepeatWeekdays) + ret.TimePoints = manager.TimePointsParseIntArray(input.TimePoints) + return &ret +} + +func (manager *SSnapshotPolicyManager) sSnapshotPolicyCreateInputFromInternal(input *api. + SSnapshotPolicyCreateInternalInput) *api.SSnapshotPolicyCreateInput { + return nil +} + +func (self *SSnapshotPolicyManager) RepeatWeekdaysParseIntArray(nums []int) uint8 { + return uint8(bitmap.IntArray2Uint(nums)) +} + +func (self *SSnapshotPolicyManager) RepeatWeekdaysToIntArray(n uint8) []int { + return bitmap.Uint2IntArray(uint32(n)) +} + +func (self *SSnapshotPolicyManager) TimePointsParseIntArray(nums []int) uint32 { + return bitmap.IntArray2Uint(nums) +} + +func (self *SSnapshotPolicyManager) TimePointsToIntArray(n uint32) []int { + return bitmap.Uint2IntArray(n) +} + +func (self *SSnapshotPolicy) GenerateCreateSpParams() *cloudprovider.SnapshotPolicyInput { + intWeekdays := SnapshotPolicyManager.RepeatWeekdaysToIntArray(self.RepeatWeekdays) + intTimePoints := SnapshotPolicyManager.TimePointsToIntArray(self.TimePoints) + + return &cloudprovider.SnapshotPolicyInput{ + RetentionDays: self.RetentionDays, + RepeatWeekdays: intWeekdays, + TimePoints: intTimePoints, + PolicyName: self.Name, + } +} + +// ==================================================== action ========================================================= +func (manager *SSnapshotPolicy) AllowPerformBindDisks(ctx context.Context, userCred mcclient.TokenCredential, + query jsonutils.JSONObject) bool { + + return manager.IsOwner(userCred) || db.IsAdminAllowPerform(userCred, manager, "bind-disks") +} + +func (sp *SSnapshotPolicy) PerformBindDisks(ctx context.Context, userCred mcclient.TokenCredential, + query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + + disks := jsonutils.GetArrayOfPrefix(data, "disk") + if len(disks) == 0 { + return nil, httperrors.NewMissingParameterError("disk.0 disk.1 ... ") + } + + //database + diskSlice := make([]*SDisk, len(disks)) + for i := range disks { + diskId, _ := disks[i].GetString() + disk := DiskManager.FetchDiskById(diskId) + if disk == nil { + return nil, httperrors.NewInputParameterError("no such disk %s", diskId) + } + disk.SetModelManager(DiskManager, disk) + diskSlice[i] = disk + } + + taskDisk := make([]*SDisk, 0, len(diskSlice)) + taskSpd := make([]*SSnapshotPolicyDisk, 0, len(diskSlice)) + for _, disk := range diskSlice { + spd, err := SnapshotPolicyDiskManager.newSnapshotpolicyDisk(ctx, userCred, sp, disk) + if err == ErrExistSD { + if spd.Status == "init" { + taskDisk = append(taskDisk, disk) + taskSpd = append(taskSpd, spd) + } + continue + } + if err != nil { + return nil, fmt.Errorf("oper for database error") + } + taskDisk = append(taskDisk, disk) + taskSpd = append(taskSpd, spd) + } + + for i := range taskDisk { + // field 'need_detach' is not needed, because the the subject is snapshot policy not disk + taskdata := jsonutils.NewDict() + taskdata.Add(jsonutils.Marshal(taskSpd[i]), "snapshotPolicyDisk") + taskdata.Add(jsonutils.Marshal(sp), "snapshotPolicy") + if task, err := taskman.TaskManager.NewTask(ctx, "SnapshotPolicyApplyTask", taskDisk[i], userCred, nil, + "", "", nil); err != nil { + continue + } else { + task.ScheduleRun(taskdata) + } + } + + return nil, nil +} + +func (manager *SSnapshotPolicy) AllowPerformUnbindDisks(ctx context.Context, userCred mcclient.TokenCredential, + query jsonutils.JSONObject) bool { + + return manager.IsOwner(userCred) || db.IsAdminAllowPerform(userCred, manager, "bind-disks") +} + +func (sp *SSnapshotPolicy) PerformUnbindDisks(ctx context.Context, userCred mcclient.TokenCredential, + query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + + disks := jsonutils.GetArrayOfPrefix(data, "disk") + if len(disks) == 0 { + return nil, httperrors.NewMissingParameterError("disk.0 disk.1 ... ") + } + + diskSlice := make([]*SDisk, len(disks)) + for i := range disks { + diskId, _ := disks[i].GetString() + disk := DiskManager.FetchDiskById(diskId) + if disk == nil { + return nil, httperrors.NewInputParameterError("no such disk %s", diskId) + } + disk.SetModelManager(DiskManager, disk) + diskSlice[i] = disk + } + + taskDisk := make([]*SDisk, 0, len(diskSlice)) + taskSpd := make([]*SSnapshotPolicyDisk, 0, len(diskSlice)) + for _, disk := range diskSlice { + spd, err := SnapshotPolicyDiskManager.FetchBySnapshotPolicyDisk(sp.Id, disk.GetId()) + if err != nil { + continue + } + if spd == nil { + continue + } + taskSpd = append(taskSpd, spd) + taskDisk = append(taskDisk, disk) + } + + for i := range taskDisk { + taskdata := jsonutils.NewDict() + taskdata.Add(jsonutils.NewString(sp.Id), "snapshot_policy_id") + taskdata.Add(jsonutils.Marshal(taskSpd[i]), "snapshotPolicyDisk") + taskSpd[i].SetStatus(userCred, api.SNAPSHOT_POLICY_DISK_DELETING, "") + if task, err := taskman.TaskManager.NewTask(ctx, "SnapshotPolicyCancelTask", taskDisk[i], userCred, nil, "", "", + nil); err != nil { + continue + } else { + task.ScheduleRun(taskdata) + } + } + return nil, nil +} diff --git a/pkg/compute/models/snapshotpolicycache.go b/pkg/compute/models/snapshotpolicycache.go new file mode 100644 index 0000000000..69d343ac0a --- /dev/null +++ b/pkg/compute/models/snapshotpolicycache.go @@ -0,0 +1,415 @@ +// 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" + "database/sql" + "fmt" + "strings" + "time" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/util/stringutils" + "yunion.io/x/sqlchemy" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/appsrv" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/lockman" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/mcclient" +) + +type SSnapshotPolicyCacheManager struct { + db.SResourceBaseManager +} + +type SSnapshotPolicyCache struct { + db.SResourceBase + SManagedResourceBase + + Id string `width:"128" charset:"ascii" primary:"true" list:"user"` + SnapshotpolicyId string `width:"128" charset:"ascii" create:"required"` + CloudregionId string `width:"128" charset:"ascii" create:"required"` + ExternalId string `width:"256" charset:"utf8" index:"true" list:"admin" create:"admin_optional"` +} + +var SnapshotPolicyCacheManager *SSnapshotPolicyCacheManager + +func init() { + SnapshotPolicyCacheManager = &SSnapshotPolicyCacheManager{ + db.NewResourceBaseManager( + SSnapshotPolicyCache{}, + "snapshotpolicycache_tbl", + "snapshotpolicycache", + "snapshotpolicycaches", + ), + } + SnapshotPolicyCacheManager.SetVirtualObject(SnapshotPolicyCacheManager) +} + +func (spc *SSnapshotPolicyCache) BeforeInsert() { + if len(spc.Id) == 0 { + spc.Id = stringutils.UUID4() + } +} + +func (spcm *SSnapshotPolicyCacheManager) FilterById(q *sqlchemy.SQuery, idStr string) *sqlchemy.SQuery { + return q.Equals("id", idStr) +} + +func (spcm *SSnapshotPolicyCacheManager) GetIStandaloneModelManager() db.IStandaloneModelManager { + return spcm.GetVirtualObject().(db.IStandaloneModelManager) +} + +func (spc *SSnapshotPolicyCache) GetIStandaloneModel() db.IStandaloneModel { + return spc.GetVirtualObject().(db.IStandaloneModel) +} + +func (spc *SSnapshotPolicyCache) ClearSchedDescCache() error { + return nil +} + +func (spcm *SSnapshotPolicyCacheManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, + query jsonutils.JSONObject) (*sqlchemy.SQuery, error) { + q, err := spcm.SResourceBaseManager.ListItemFilter(ctx, q, userCred, query) + if err != nil { + return nil, err + } + if defsecgroup, _ := query.GetString("snapshotpolicy"); len(defsecgroup) > 0 { + secgroup, err := SecurityGroupManager.FetchByIdOrName(userCred, defsecgroup) + if err != nil { + if err == sql.ErrNoRows { + return nil, httperrors.NewResourceNotFoundError2(SecurityGroupManager.Keyword(), defsecgroup) + } else { + return nil, httperrors.NewGeneralError(err) + } + } + q = q.Equals("snapshotpolicy_id", secgroup.GetId()) + } + return q, nil +} + +func (spc *SSnapshotPolicyCache) GetIRegion() (cloudprovider.ICloudRegion, error) { + provider, err := spc.GetDriver() + if err != nil { + return nil, err + } + if region := CloudregionManager.FetchRegionById(spc.CloudregionId); region != nil { + return provider.GetIRegionById(region.ExternalId) + } + return nil, fmt.Errorf("failed to find iregion for snapshotpolicycache %s: cloudregion %s manager %s", spc.Id, + spc.CloudregionId, spc.ManagerId) +} + +func (spc *SSnapshotPolicyCache) GetSnapshotPolicy() (*SSnapshotPolicy, error) { + model, err := SnapshotPolicyManager.FetchById(spc.SnapshotpolicyId) + if err != nil { + return nil, fmt.Errorf("failed to fetchsnapshotpolicy by %s", spc.SnapshotpolicyId) + } + return model.(*SSnapshotPolicy), nil +} + +func (spc *SSnapshotPolicyCache) SetExternalId(userCred mcclient.TokenCredential, externalId string) error { + diff, err := db.Update(spc, func() error { + spc.ExternalId = externalId + return nil + }) + if err != nil { + return err + } + db.OpsLog.LogEvent(spc, db.ACT_UPDATE, diff, userCred) + return nil +} + +func (spc SSnapshotPolicyCache) GetExternalId() string { + return spc.ExternalId +} + +// =============================================== detach and delete =================================================== + +func (spc *SSnapshotPolicyCache) RealDetele(ctx context.Context, userCred mcclient.TokenCredential) error { + return db.DeleteModel(ctx, userCred, spc) +} + +// ================================================= new and regist ==================================================== + +func (spcm *SSnapshotPolicyCacheManager) NewCache(ctx context.Context, userCred mcclient.TokenCredential, + snapshotPolicyId, regionId, providerId string) (*SSnapshotPolicyCache, error) { + + snapshotPolicyCache := SSnapshotPolicyCache{ + SnapshotpolicyId: snapshotPolicyId, + CloudregionId: regionId, + } + snapshotPolicyCache.ManagerId = providerId + + err := snapshotPolicyCache.CreateCloudSnapshotPolicy() + if err != nil { + return nil, err + } + + // should have lock + if err := spcm.TableSpec().Insert(&snapshotPolicyCache); err != nil { + return nil, errors.Wrapf(err, "insert snapshotpolicycache failed") + } + return &snapshotPolicyCache, nil +} + +func (spcm *SSnapshotPolicyCacheManager) NewCacheWithExternalId(ctx context.Context, userCred mcclient.TokenCredential, + snapshotPolicyId, externalId, regionId, providerId string) (*SSnapshotPolicyCache, error) { + + snapshotPolicyCache := SSnapshotPolicyCache{ + SnapshotpolicyId: snapshotPolicyId, + CloudregionId: regionId, + ExternalId: externalId, + } + snapshotPolicyCache.ManagerId = providerId + + // should have lock + if err := spcm.TableSpec().Insert(&snapshotPolicyCache); err != nil { + return nil, errors.Wrapf(err, "insert snapshotpolicycache failed") + } + return &snapshotPolicyCache, nil +} + +func (spcm *SSnapshotPolicyCacheManager) Register(ctx context.Context, userCred mcclient.TokenCredential, snapshotPolicyId, + regionId string, providerId string) (*SSnapshotPolicyCache, error) { + + // Many request about same snapshot policy with same region and provider coming will cause many same cache + // building without lock, so we must Lock. + lockman.LockRawObject(ctx, snapshotPolicyId, regionId+providerId) + defer lockman.ReleaseRawObject(ctx, snapshotPolicyId, regionId+providerId) + snapshotPolicyCache, err := spcm.FetchSnapshotPolicyCache(snapshotPolicyId, regionId, providerId) + // error + if err != nil { + return nil, err + } + + // no cache + if snapshotPolicyCache != nil { + return snapshotPolicyCache, nil + } + + return spcm.NewCache(ctx, userCred, snapshotPolicyId, regionId, providerId) +} + +func (spcm *SSnapshotPolicyCacheManager) RegisterWithExternalID(ctx context.Context, userCred mcclient.TokenCredential, + snapshotPolicyId, externalId, regionId, providerId string) (*SSnapshotPolicyCache, error) { + + snapshotPolicyCache, err := spcm.FetchSnapshotPolicyCache(snapshotPolicyId, regionId, providerId) + // error + if err != nil { + return nil, err + } + + // no cache + if snapshotPolicyCache != nil { + return snapshotPolicyCache, nil + } + + return spcm.NewCacheWithExternalId(ctx, userCred, snapshotPolicyId, externalId, regionId, providerId) +} + +// ==================================================== fetch ========================================================= + +func (spcm *SSnapshotPolicyCacheManager) FetchSnapshotpolicyCaheById(cacheId string) (*SSnapshotPolicyCache, error) { + q := spcm.FilterById(spcm.Query(), cacheId) + return spcm.fetchByQuery(q) +} + +func (spcm *SSnapshotPolicyCacheManager) FetchSnapshotPolicyCache(snapshotPolicyId, regionId, providerId string) (*SSnapshotPolicyCache, error) { + + q := spcm.Query() + q.Filter(sqlchemy.AND(sqlchemy.Equals(q.Field("snapshotpolicy_id"), snapshotPolicyId), + sqlchemy.Equals(q.Field("cloudregion_id"), regionId), + sqlchemy.Equals(q.Field("manager_id"), providerId))) + + return spcm.fetchByQuery(q) +} + +func (spcm *SSnapshotPolicyCacheManager) FetchSnapshotPolicyCacheByExtId(externalId, regionId, + providerId string) (*SSnapshotPolicyCache, error) { + + q := spcm.Query() + q.Filter(sqlchemy.AND(sqlchemy.Equals(q.Field("external_id"), externalId), + sqlchemy.Equals(q.Field("cloudregion_id"), regionId), + sqlchemy.Equals(q.Field("manager_id"), providerId))) + + return spcm.fetchByQuery(q) +} + +func (spcm *SSnapshotPolicyCacheManager) FetchAllByExtIds(externalIds []string, regionId, + providerId string) ([]SSnapshotPolicyCache, error) { + + q := spcm.Query().In("external_id", externalIds).Equals("cloudregion_id", regionId).Equals("manager_id", providerId) + return spcm.fetchAllByQuery(q) +} + +func (spcm *SSnapshotPolicyCacheManager) FetchAllBySnpId(snapshotPolicyId string) ([]SSnapshotPolicyCache, error) { + + q := spcm.Query().Equals("snapshotpolicy_id", snapshotPolicyId) + return spcm.fetchAllByQuery(q) +} + +func (spcm *SSnapshotPolicyCacheManager) FetchAllByRegionProvider(cloudregionId, + managerId string) ([]SSnapshotPolicyCache, error) { + + q := spcm.Query().Equals("cloudregion_id", cloudregionId).Equals("manager_id", managerId) + return spcm.fetchAllByQuery(q) + +} + +func (spcm *SSnapshotPolicyCacheManager) fetchAllByQuery(q *sqlchemy.SQuery) ([]SSnapshotPolicyCache, error) { + caches := make([]SSnapshotPolicyCache, 0, 1) + if err := db.FetchModelObjects(spcm, q, &caches); err != nil { + return nil, err + } + return caches, nil +} + +func (spcm *SSnapshotPolicyCacheManager) fetchByQuery(q *sqlchemy.SQuery) (*SSnapshotPolicyCache, error) { + count, err := q.CountWithError() + if err != nil { + return nil, err + } + if count == 0 { + return nil, nil + } + snapshotPolicyCache := SSnapshotPolicyCache{} + // if exist, only one + q.First(&snapshotPolicyCache) + snapshotPolicyCache.SetModelManager(spcm, &snapshotPolicyCache) + return &snapshotPolicyCache, nil +} + +// ============================================== cloud operation ====================================================== +// This function should call in the task. + +type sOperaResult struct { + err error + snapshotPolicyId string +} + +func (spcm *SSnapshotPolicyCacheManager) UpdateCloudSnapshotPolicy(snapshotPolicyId string, + input *cloudprovider.SnapshotPolicyInput) error { + //todo maybe + + return fmt.Errorf("Not implement") +} + +func (spcm *SSnapshotPolicyCacheManager) DeleteCloudSnapshotPolices(ctx context.Context, + userCred mcclient.TokenCredential, snapshotPolicyId string) error { + + spCaches, err := spcm.FetchAllBySnpId(snapshotPolicyId) + if err != nil { + return errors.Wrapf(err, "fetch all snapshotPolicyCaches ofsnapshotpolicy %s failed", snapshotPolicyId) + } + + if len(spCaches) == 0 { + return nil + } + + wm := appsrv.NewWorkerManager("delete-cloud-snapshotpolices", len(spCaches), 1, false) + retChan := make(chan sOperaResult) + + for i := range spCaches { + spc := spCaches[i] + wm.Run(func() { + err := spc.DeleteCloudSnapshotPolicy() + if err != nil { + retChan <- sOperaResult{err, spc.GetId()} + return + } + err = spc.RealDetele(ctx, userCred) + if err != nil { + retChan <- sOperaResult{errors.Wrap(err, "delete cache in database failed"), spc.GetId()} + return + } + retChan <- sOperaResult{nil, spc.GetId()} + }, nil, func(e error) { + retChan <- sOperaResult{e, spc.GetId()} + }) + } + + failedRecord := make([]string, 0) + for i := 0; i < len(spCaches); i++ { + ret := <-retChan + if ret.err != nil { + failedRecord = append(failedRecord, fmt.Sprintf("%s failed because that %s", ret.snapshotPolicyId, + ret.err.Error())) + } + } + + if len(failedRecord) != 0 { + return fmt.Errorf("delete: " + strings.Join(failedRecord, "; ")) + } + return nil +} + +func (spc *SSnapshotPolicyCache) CreateCloudSnapshotPolicy() error { + // create correspondingsnapshotpolicy in cloud + iregion, err := spc.GetIRegion() + if err != nil { + return err + } + snapshotPolicy, err := spc.GetSnapshotPolicy() + if err != nil { + return err + } + + externalId, err := iregion.CreateSnapshotPolicy(snapshotPolicy.GenerateCreateSpParams()) + if err != nil { + return errors.Wrap(err, "createsnapshotpolicy failed") + } + spc.ExternalId = externalId + + iPolicy, err := iregion.GetISnapshotPolicyById(externalId) + if err != nil { + return err + } + err = cloudprovider.WaitStatus(iPolicy, api.SNAPSHOT_POLICY_READY, 10*time.Second, 300*time.Second) + if err != nil { + return err + } + return nil +} + +func (spc *SSnapshotPolicyCache) DeleteCloudSnapshotPolicy() error { + if len(spc.ExternalId) > 0 { + iregion, err := spc.GetIRegion() + if err != nil { + return err + } + return iregion.DeleteSnapshotPolicy(spc.ExternalId) + } + return nil +} + +func (spc *SSnapshotPolicyCache) UpdateCloudSnapshotPolicy(input *cloudprovider.SnapshotPolicyInput) error { + iregion, err := spc.GetIRegion() + if err != nil { + return err + } + + err = iregion.UpdateSnapshotPolicy(input, spc.ExternalId) + if err != nil { + return errors.Wrap(err, "createsnapshotpolicy failed") + } + + return nil +} diff --git a/pkg/compute/models/snapshotpolicydisks.go b/pkg/compute/models/snapshotpolicydisks.go index 9f7ac2933f..b4d919cd25 100644 --- a/pkg/compute/models/snapshotpolicydisks.go +++ b/pkg/compute/models/snapshotpolicydisks.go @@ -15,14 +15,21 @@ package models import ( + "bytes" "context" + "database/sql" + "fmt" + "strings" "yunion.io/x/jsonutils" "yunion.io/x/log" "yunion.io/x/pkg/errors" + "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/httperrors" "yunion.io/x/onecloud/pkg/mcclient" ) @@ -30,11 +37,11 @@ type SSnapshotPolicyDiskManager struct { db.SVirtualJointResourceBaseManager } -func (manager *SSnapshotPolicyDiskManager) GetMasterFieldName() string { +func (m *SSnapshotPolicyDiskManager) GetMasterFieldName() string { return "disk_id" } -func (manager *SSnapshotPolicyDiskManager) GetSlaveFieldName() string { +func (m *SSnapshotPolicyDiskManager) GetSlaveFieldName() string { return "snapshotpolicy_id" } @@ -62,66 +69,382 @@ type SSnapshotPolicyDisk struct { SnapshotpolicyId string `width:"36" charset:"ascii" nullable:"false" list:"user" create:"required" index:"true"` DiskId string `width:"36" charset:"ascii" nullable:"false" list:"user" create:"required" index:"true"` + Status string `width:"36" charset:"ascii" nullable:"false" default:"init" list:"user" create:"optional"` } -func (self *SSnapshotPolicyDisk) Detach(ctx context.Context, userCred mcclient.TokenCredential) error { - return db.DetachJoint(ctx, userCred, self) +func (sd *SSnapshotPolicyDisk) SetStatus(userCred mcclient.TokenCredential, status string, reason string) error { + if sd.Status == status { + return nil + } + oldStatus := sd.Status + _, err := db.Update(sd, func() error { + sd.Status = status + return nil + }) + if err != nil { + return err + } + if userCred != nil { + notes := fmt.Sprintf("%s=>%s", oldStatus, status) + if len(reason) > 0 { + notes = fmt.Sprintf("%s: %s", notes, reason) + } + db.OpsLog.LogEvent(sd, db.ACT_UPDATE_STATUS, notes, userCred) + } + return nil } -func (self *SSnapshotPolicyDiskManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) { - diskId, _ := data.GetString(self.GetMasterFieldName()) - disk := DiskManager.FetchDiskById(diskId) - err := disk.GetStorage().GetRegion().GetDriver().ValidateCreateSnapshopolicyDiskData(ctx, userCred, diskId) +func (self *SSnapshotPolicyDisk) GetCustomizeColumns(ctx context.Context, userCred mcclient.TokenCredential, + query jsonutils.JSONObject) *jsonutils.JSONDict { + + ret, _ := self.getMoreDetails(ctx, userCred, query) + return ret +} + +func (self *SSnapshotPolicyDisk) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, + query jsonutils.JSONObject) (*jsonutils.JSONDict, error) { + + return self.getMoreDetails(ctx, userCred, query) +} + +func (self *SSnapshotPolicyDisk) getMoreDetails(ctx context.Context, userCred mcclient.TokenCredential, + query jsonutils.JSONObject) (*jsonutils.JSONDict, error) { + + disk := DiskManager.FetchDiskById(self.DiskId) + ret := jsonutils.NewDict() + ret.Add(jsonutils.Marshal(disk), "disk") + return ret, nil +} + +// ==================================================== fetch ========================================================== + +func (m *SSnapshotPolicyDiskManager) FetchBySnapshotPolicyDisk(spId, diskId string) (*SSnapshotPolicyDisk, error) { + q := m.Query().Equals("snapshotpolicy_id", spId).Equals("disk_id", diskId) + ret := make([]SSnapshotPolicyDisk, 0, 1) + err := db.FetchModelObjects(m, q, &ret) if err != nil { return nil, err } - return data, nil + if len(ret) == 0 { + return nil, fmt.Errorf("Not Found") + } + return &ret[0], nil } -func (self *SSnapshotPolicyDiskManager) FetchAllSnapshotPolicyOfDisk(ctx context.Context, userCred mcclient.TokenCredential, diskID string) ([]SSnapshotPolicyDisk, error) { - q := self.Query() - q.Equals(self.GetMasterFieldName(), diskID) +func (m *SSnapshotPolicyDiskManager) FetchAllByDiskID(ctx context.Context, userCred mcclient.TokenCredential, + diskID string) ([]SSnapshotPolicyDisk, error) { + + return m.fetchAll(ctx, userCred, m.GetMasterFieldName(), diskID) +} + +func (m *SSnapshotPolicyDiskManager) FetchAllBySnapshotpolicyID(ctx context.Context, userCred mcclient.TokenCredential, + snapshotPolicyID string) ([]SSnapshotPolicyDisk, error) { + + return m.fetchAll(ctx, userCred, m.GetSlaveFieldName(), snapshotPolicyID) +} + +func (m *SSnapshotPolicyDiskManager) FetchDiskCountBySPID(snapshotpolicyID string) (int, error) { + + q := m.Query().Equals("snapshotpolicy_id", snapshotpolicyID) + return q.CountWithError() +} + +func (m *SSnapshotPolicyDiskManager) fetchAll(ctx context.Context, userCred mcclient.TokenCredential, + fieldName string, fieldValue string) ([]SSnapshotPolicyDisk, error) { + + q := m.Query() + q.Equals(fieldName, fieldValue) ret := make([]SSnapshotPolicyDisk, 0) - err := db.FetchModelObjects(self, q, &ret) + err := db.FetchModelObjects(m, q, &ret) if err != nil { return nil, err } return ret, nil } -func (self *SSnapshotPolicyDisk) PostCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) { - diskID := self.DiskId - model, err := DiskManager.FetchById(diskID) - if err != nil { - log.Errorf("Fetch disk by ID %s failed", diskID) +// ==================================================== sync =========================================================== + +// SyncDetachByDisk will detach all snapshot policies fo cloudRelations +// if cloudRelations is nil, it will detach all shapshot policies which has been attached to disk. +func (m *SSnapshotPolicyDiskManager) SyncDetachByDisk(ctx context.Context, userCred mcclient.TokenCredential, + cloudRelations []SSnapshotPolicyDisk, disk *SDisk) error { + snapshotPolicyDisks := cloudRelations + var err error + if snapshotPolicyDisks == nil { + snapshotPolicyDisks, err = m.FetchAllByDiskID(ctx, userCred, disk.GetId()) + if err != nil { + return errors.Wrapf(err, "Fetach allsnapshotpolicy of disk %s in database", disk.GetId()) + } } - disk := model.(*SDisk) - snapshotPolicyID := self.SnapshotpolicyId - taskData := jsonutils.NewDict() - taskData.Add(jsonutils.NewString(snapshotPolicyID), "snapshot_policy_id") - task, err := taskman.TaskManager.NewTask(ctx, "SnapshotPolicyApplyTask", disk, userCred, taskData, "", "", nil) + failResult := make([]string, 0, 1) + for i := range snapshotPolicyDisks { + err = snapshotPolicyDisks[i].DetachByDisk(ctx, userCred, disk) + if err != nil { + failResult = append(failResult, snapshotPolicyDisks[i].GetId()) + } + } + if len(failResult) != 0 { + errInfo := "detach failed which IDs are: " + return errors.Error(errInfo + strings.Join(failResult, ", ")) + } + return nil +} + +// SyncDetachBySnapshotpolicy detach all sn +func (m *SSnapshotPolicyDiskManager) SyncDetachBySnapshotpolicy(ctx context.Context, + userCred mcclient.TokenCredential, cloudRelations []SSnapshotPolicyDisk, snapshotPolicy *SSnapshotPolicy) error { + + snapshotPolicyDisks := cloudRelations + var err error + if snapshotPolicyDisks == nil { + snapshotPolicyDisks, err = m.FetchAllBySnapshotpolicyID(ctx, userCred, snapshotPolicy.GetId()) + if err != nil { + return errors.Wrapf(err, "Fetach all bysnapshotpolicy %s in database", snapshotPolicy.GetId()) + } + } + failResult := make([]string, 0, 1) + for i := range snapshotPolicyDisks { + err = snapshotPolicyDisks[i].DetachBySnapshotpolicy(ctx, userCred, snapshotPolicy) + if err != nil { + failResult = append(failResult, snapshotPolicyDisks[i].GetId()) + } + } + if len(failResult) != 0 { + errInfo := "detach failed which IDs are " + return errors.Error(errInfo + strings.Join(failResult, ", ")) + } + return nil +} + +func (m *SSnapshotPolicyDiskManager) SyncByDisk(ctx context.Context, userCred mcclient.TokenCredential, + extSnapshotpolicies []string, syncOwnerID mcclient.IIdentityProvider, disk *SDisk, storage *SStorage) error { + + sds, err := m.FetchAllByDiskID(ctx, userCred, disk.GetId()) + if err != nil { + return errors.Wrapf(err, "Fetach allsnapshotpolicy of disk %s in database", disk.GetId()) + } + + //fetch snapshotPolicy Cache to find the snapshotpolicyID corresponding to extSnapshotpolicyID + spCaches, err := SnapshotPolicyCacheManager.FetchAllByExtIds(extSnapshotpolicies, storage.GetRegion().GetId(), + storage.ManagerId) + if err != nil { + return errors.Wrapf(err, "fetachsnapshotpolicy caches failed") + } + cloudRelationsSet := make(map[string]struct{}) + for i := range spCaches { + cloudRelationsSet[spCaches[i].SnapshotpolicyId] = struct{}{} + } + + removed := make([]SSnapshotPolicyDisk, 0, 1) + added := make([]string, 0, 1) + for i := range sds { + if _, ok := cloudRelationsSet[sds[i].SnapshotpolicyId]; !ok { + removed = append(removed, sds[i]) + } + delete(cloudRelationsSet, sds[i].SnapshotpolicyId) + } + for k := range cloudRelationsSet { + added = append(added, k) + } + err = m.SyncDetachByDisk(ctx, userCred, removed, disk) + if err != nil { + return err + } + err = m.SyncAttachDisk(ctx, userCred, added, syncOwnerID, disk) + if err != nil { + return err + } + return nil +} + +func (m *SSnapshotPolicyDiskManager) SyncAttachDisk(ctx context.Context, userCred mcclient.TokenCredential, + Snapshotpolicies []string, syncOwnerID mcclient.IIdentityProvider, disk *SDisk) error { + + lockman.LockClass(ctx, m, db.GetLockClassKey(m, syncOwnerID)) + defer lockman.ReleaseClass(ctx, m, db.GetLockClassKey(m, syncOwnerID)) + + failRecord := make([]string, 0, 1) + for _, spId := range Snapshotpolicies { + snapshotpolicyDisk, err := db.FetchJointByIds(m, disk.GetId(), spId, jsonutils.JSONNull) + if err != nil && err != sql.ErrNoRows { + failRecord = append(failRecord, + fmt.Sprintf("Get SnapshotpolicyDisk whose diskid %s snapshotpolicyid %s failed", + disk.GetId(), spId)) + continue + } + if snapshotpolicyDisk != nil { + continue + } + + sd := SSnapshotPolicyDisk{} + sd.DiskId = disk.GetId() + sd.SnapshotpolicyId = spId + sd.Status = compute.SNAPSHOT_POLICY_DISK_READY + err = m.TableSpec().Insert(&sd) + if err != nil { + failRecord = append(failRecord, fmt.Sprintf("attachsnapshotpolicy %s to disk %s failed", + spId, disk.GetId())) + continue + } + } + if len(failRecord) == 0 { + return nil + } + buf := bytes.NewBufferString("sync attach extSnapshotpolicies to disk ") + buf.WriteString(disk.GetId()) + buf.WriteString("failed because that ") + for i := range failRecord { + buf.WriteString(failRecord[i]) + buf.WriteString(", ") + } + buf.Truncate(buf.Len() - 2) + return errors.Error(buf.String()) +} + +// +func (m *SSnapshotPolicyDiskManager) SyncAttachDiskExt(ctx context.Context, userCred mcclient.TokenCredential, + extSnapshotpolicies []string, syncOwnerID mcclient.IIdentityProvider, disk *SDisk, storage *SStorage) error { + + //fetch snapshotPolicy Cache to find the snapshotpolicyID corresponding to extSnapshotpolicyID + spCaches, err := SnapshotPolicyCacheManager.FetchAllByExtIds(extSnapshotpolicies, storage.GetRegion().GetId(), + storage.ManagerId) + if err != nil { + return errors.Wrapf(err, "fetachsnapshotpolicy caches failed") + } + + snapshotPolicie := make([]string, 0, 1) + for i := range spCaches { + snapshotPolicie = append(snapshotPolicie, spCaches[i].SnapshotpolicyId) + } + + return m.SyncAttachDisk(ctx, userCred, snapshotPolicie, syncOwnerID, disk) +} + +// ==================================================== detach ========================================================= + +func (sd *SSnapshotPolicyDisk) RealDetach(ctx context.Context, userCred mcclient.TokenCredential) error { + return db.DeleteModel(ctx, userCred, sd) +} + +func (sd *SSnapshotPolicyDisk) Delete(ctx context.Context, userCred mcclient.TokenCredential) error { + return nil +} + +func (sd *SSnapshotPolicyDisk) Detach(ctx context.Context, userCred mcclient.TokenCredential) error { + return nil +} + +// syncDetach should lock before +func (sd *SSnapshotPolicyDisk) DetachByDisk(ctx context.Context, userCred mcclient.TokenCredential, disk *SDisk) error { + snapshotPolicy := SSnapshotPolicy{} + snapshotPolicy.Id = sd.SnapshotpolicyId + snapshotPolicy.SetModelManager(SnapshotPolicyManager, &snapshotPolicy) + lockman.LockJointObject(ctx, disk, &snapshotPolicy) + defer lockman.ReleaseJointObject(ctx, disk, &snapshotPolicy) + // todo call real Detach + return sd.RealDetach(ctx, userCred) +} + +func (sd *SSnapshotPolicyDisk) DetachBySnapshotpolicy(ctx context.Context, userCred mcclient.TokenCredential, + snapshotPolicy *SSnapshotPolicy) error { + + disk := SDisk{} + disk.Id = sd.DiskId + disk.SetModelManager(DiskManager, &disk) + lockman.LockJointObject(ctx, &disk, snapshotPolicy) + defer lockman.ReleaseJointObject(ctx, &disk, snapshotPolicy) + return sd.RealDetach(ctx, userCred) +} + +// ==================================================== create ========================================================= + +var ErrExistSD = fmt.Errorf("snapshotpolicy disk has been exist") + +func (self *SSnapshotPolicyDiskManager) newSnapshotpolicyDisk(ctx context.Context, userCred mcclient.TokenCredential, + sp *SSnapshotPolicy, disk *SDisk) (*SSnapshotPolicyDisk, error) { + + q := self.Query().Equals("snapshotpolicy_id", sp.GetId()).Equals("disk_id", disk.GetId()) + count, err := q.CountWithError() + if err != nil { + return nil, nil + } + if count > 0 { + spd := SSnapshotPolicyDisk{} + q.First(&spd) + spd.SetModelManager(self, &spd) + return &spd, ErrExistSD + } + + spd := SSnapshotPolicyDisk{SnapshotpolicyId: sp.GetId(), DiskId: disk.GetId()} + spd.SetModelManager(self, &spd) + + lockman.LockJointObject(ctx, disk, sp) + defer lockman.ReleaseJointObject(ctx, disk, sp) + return &spd, self.TableSpec().Insert(&spd) + +} +func (self *SSnapshotPolicyDiskManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) { + diskId, _ := data.GetString(self.GetMasterFieldName()) + snapshotPolicyId, _ := data.GetString(self.GetSlaveFieldName()) + disk := DiskManager.FetchDiskById(diskId) + snapshotPolicy := SnapshotPolicyManager.FetchSnapshotPolicyById(snapshotPolicyId) + err := disk.GetStorage().GetRegion().GetDriver().ValidateCreateSnapshopolicyDiskData(ctx, userCred, disk, snapshotPolicy) + if err != nil { + return nil, err + } + //to control that one disk should only bind one snapshot policy + spds, err := SnapshotPolicyDiskManager.FetchAllByDiskID(ctx, userCred, diskId) + if err != nil { + return nil, err + } + if len(spds) > 1 { + return nil, httperrors.NewInputParameterError("disk %s has too many snapshot policy attached", diskId) + } + if len(spds) == 1 { + data.Add(jsonutils.NewString(spds[0].SnapshotpolicyId), "need_detach") + } + // I don't want to request to database again behind + data.Add(jsonutils.Marshal(snapshotPolicy), "snapshotPolicy") + data.Add(jsonutils.Marshal(disk), "disk") + return data, nil +} + +func (sd *SSnapshotPolicyDisk) PostCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient. + IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) { + + taskdata := data.(*jsonutils.JSONDict) + taskdata.Add(jsonutils.Marshal(sd), "snapshotPolicyDisk") + disk := &SDisk{} + data.Unmarshal(disk, "disk") + disk.SetModelManager(DiskManager, disk) + task, err := taskman.TaskManager.NewTask(ctx, "SnapshotPolicyApplyTask", disk, userCred, nil, "", "", nil) if err != nil { log.Errorf("SnapshotPolicyApplyTask newTask error %s", err) } else { - task.ScheduleRun(nil) + task.ScheduleRun(taskdata) } } -func (self *SSnapshotPolicyDisk) CustomizeDelete(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) error { - diskID := self.DiskId +// ==================================================== delete ========================================================= + +func (sd *SSnapshotPolicyDisk) CustomizeDelete(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, + data jsonutils.JSONObject) error { + diskID := sd.DiskId model, err := DiskManager.FetchById(diskID) if err != nil { return errors.Wrapf(err, "Fetch disk by ID %s failed", diskID) } disk := model.(*SDisk) - snapshotPolicyID := self.SnapshotpolicyId + snapshotPolicyID := sd.SnapshotpolicyId taskData := jsonutils.NewDict() taskData.Add(jsonutils.NewString(snapshotPolicyID), "snapshot_policy_id") - task, err := taskman.TaskManager.NewTask(ctx, "SnapshotPolicyCancelTask", disk, userCred, taskData, "", "", nil) + taskData.Add(jsonutils.Marshal(sd), "snapshotPolicyDisk") + sd.SetStatus(userCred, compute.SNAPSHOT_POLICY_DISK_DELETING, "") + task, err := taskman.TaskManager.NewTask(ctx, "SnapshotPolicyCancelTask", disk, userCred, nil, "", "", nil) if err != nil { return errors.Wrapf(err, "SnapshotPolicyCancelTask newTask error %s", err) } else { - task.ScheduleRun(nil) + task.ScheduleRun(taskData) } return nil } diff --git a/pkg/compute/options/options.go b/pkg/compute/options/options.go index 4b7b09012d..b30c60abd3 100644 --- a/pkg/compute/options/options.go +++ b/pkg/compute/options/options.go @@ -77,6 +77,11 @@ type ComputeOptions struct { DefaultMaxSnapshotCount int `default:"9" help:"Per Disk max snapshot count, default 9"` DefaultMaxManualSnapshotCount int `default:"2" help:"Per Disk max manual snapshot count, default 2"` + //snapshot policy options + RetentionDaysLimit int `default:"49" help:"Days of snapshot retention, default 49 days"` + TimePointsLimit int `default:"1" help:"time point of every days, default 1 point"` + RepeatWeekdaysLimit int `default:"7" help:"day point of every weekday, default 7 points"` + // sku sync SyncSkusDay int `default:"1" help:"Days auto sync skus data, default 1 day"` SyncSkusHour int `default:"3" help:"What hour start sync skus, default 03:00"` diff --git a/pkg/compute/regiondrivers/aliyun.go b/pkg/compute/regiondrivers/aliyun.go index 0ab6e2c323..c53237fc63 100644 --- a/pkg/compute/regiondrivers/aliyun.go +++ b/pkg/compute/regiondrivers/aliyun.go @@ -18,7 +18,6 @@ import ( "context" "fmt" "regexp" - "sort" "strings" "time" @@ -805,34 +804,21 @@ 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) ValidateCreateSnapshopolicyDiskData(ctx context.Context, userCred mcclient.TokenCredential, diskID string) error { - ret, err := models.SnapshotPolicyDiskManager.FetchAllSnapshotPolicyOfDisk(ctx, userCred, diskID) - if err != nil { - return err - } - if len(ret) != 0 { - return httperrors.NewBadRequestError("One disk could't attach two snapshot policy in aliyun; please detach last one first.") - } +func (self *SAliyunRegionDriver) ValidateCreateSnapshopolicyDiskData(ctx context.Context, + userCred mcclient.TokenCredential, disk *models.SDisk, snapshotPolicy *models.SSnapshotPolicy) error { + //err := self.SManagedVirtualizationRegionDriver.ValidateCreateSnapshopolicyDiskData(ctx, userCred, disk, snapshotPolicy) + //if err != nil { + // return nil + //} + //// In Aliyun, One disk only apply one snapshot policy + //ret, err := models.SnapshotPolicyDiskManager.FetchAllByDiskID(ctx, userCred, disk.GetId()) + //if err != nil { + // return err + //} + //if len(ret) != 0 { + // return httperrors.NewBadRequestError("One disk could't attach two snapshot policy in aliyun; please detach last one first.") + //} + //return nil return nil } diff --git a/pkg/compute/regiondrivers/base.go b/pkg/compute/regiondrivers/base.go index 02deebadaf..f795acacce 100644 --- a/pkg/compute/regiondrivers/base.go +++ b/pkg/compute/regiondrivers/base.go @@ -17,14 +17,11 @@ package regiondrivers import ( "context" "fmt" - "yunion.io/x/jsonutils" - api "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" - "yunion.io/x/onecloud/pkg/httperrors" "yunion.io/x/onecloud/pkg/mcclient" ) @@ -127,41 +124,16 @@ func (self *SBaseRegionDriver) RequestDeleteLoadbalancerListenerRule(ctx context return fmt.Errorf("Not Implement RequestDeleteLoadbalancerListenerRule") } -func (self *SBaseRegionDriver) ValidateCreateSnapshotPolicyData(ctx context.Context, userCred mcclient.TokenCredential, input *api.SSnapshotPolicyCreateInput, ownerId mcclient.IIdentityProvider, data *jsonutils.JSONDict) error { - var err error - - if len(input.RepeatWeekdays) == 0 { - return httperrors.NewMissingParameterError("repeat_weekdays") - } - input.RepeatWeekdays, err = daysValidate(input.RepeatWeekdays, 1, 7) - if err != nil { - return httperrors.NewInputParameterError(err.Error()) - } - - if len(input.TimePoints) == 0 { - return httperrors.NewInputParameterError("time_points") - } - input.TimePoints, err = daysValidate(input.TimePoints, 0, 23) - if err != nil { - return httperrors.NewInputParameterError(err.Error()) - } - return nil +func (self *SBaseRegionDriver) RequestUpdateSnapshotPolicy(ctx context.Context, userCred mcclient.TokenCredential, sp *models.SSnapshotPolicy, input cloudprovider.SnapshotPolicyInput, task taskman.ITask) error { + return fmt.Errorf("Not Implement RequestUpdateSnapshotPolicy") } -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, diskId string) error { +func (self *SBaseRegionDriver) RequestApplySnapshotPolicy(ctx context.Context, userCred mcclient.TokenCredential, task taskman.ITask, disk *models.SDisk, sp *models.SSnapshotPolicy, data jsonutils.JSONObject) error { return fmt.Errorf("Not Implement RequestApplySnapshotPolicy") } -func (self *SBaseRegionDriver) RequestCancelSnapshotPolicy(ctx context.Context, userCred mcclient.TokenCredential, sp *models.SSnapshotPolicy, task taskman.ITask, diskId string) error { - return fmt.Errorf("Not Implement RequestApplySnapshotPolicy") +func (self *SBaseRegionDriver) RequestCancelSnapshotPolicy(ctx context.Context, userCred mcclient.TokenCredential, task taskman.ITask, disk *models.SDisk, sp *models.SSnapshotPolicy, data jsonutils.JSONObject) error { + return fmt.Errorf("Not Implement RequestCancelSnapshotPolicy") } func (self *SBaseRegionDriver) ValidateSnapshotDelete(ctx context.Context, snapshot *models.SSnapshot) error { @@ -192,8 +164,9 @@ func (self *SBaseRegionDriver) OnDiskReset(ctx context.Context, userCred mcclien return fmt.Errorf("Not Implement OnDiskReset") } -func (self *SBaseRegionDriver) ValidateCreateSnapshopolicyDiskData(ctx context.Context, userCred mcclient.TokenCredential, diskID string) error { - return fmt.Errorf("Not Implement ValidateCreateSnapshotpolicyDiskData") +func (self *SBaseRegionDriver) ValidateCreateSnapshopolicyDiskData(ctx context.Context, + userCred mcclient.TokenCredential, disk *models.SDisk, snapshotPolicy *models.SSnapshotPolicy) error { + return nil } func (self *SBaseRegionDriver) OnSnapshotDelete(ctx context.Context, snapshot *models.SSnapshot, task taskman.ITask, data jsonutils.JSONObject) error { diff --git a/pkg/compute/regiondrivers/kvm.go b/pkg/compute/regiondrivers/kvm.go index e5f5e0ffb6..43a289a195 100644 --- a/pkg/compute/regiondrivers/kvm.go +++ b/pkg/compute/regiondrivers/kvm.go @@ -21,7 +21,6 @@ import ( "yunion.io/x/jsonutils" "yunion.io/x/log" - "yunion.io/x/onecloud/pkg/util/rand" "yunion.io/x/pkg/utils" api "yunion.io/x/onecloud/pkg/apis/compute" @@ -30,8 +29,10 @@ import ( "yunion.io/x/onecloud/pkg/cloudcommon/validators" "yunion.io/x/onecloud/pkg/cloudprovider" "yunion.io/x/onecloud/pkg/compute/models" + "yunion.io/x/onecloud/pkg/compute/options" "yunion.io/x/onecloud/pkg/httperrors" "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/util/rand" ) type SKVMRegionDriver struct { @@ -774,37 +775,48 @@ func (self *SKVMRegionDriver) OnDiskReset(ctx context.Context, userCred mcclient return models.GetStorageDriver(storage.StorageType).OnDiskReset(ctx, userCred, disk, snapshot, data) } -func (self *SKVMRegionDriver) ValidateCreateSnapshotPolicyData(ctx context.Context, userCred mcclient.TokenCredential, input *api.SSnapshotPolicyCreateInput, ownerId mcclient.IIdentityProvider, data *jsonutils.JSONDict) error { - err := self.SBaseRegionDriver.ValidateCreateSnapshotPolicyData(ctx, userCred, input, ownerId, data) - if err != nil { - return err +func (self *SKVMRegionDriver) RequestUpdateSnapshotPolicy(ctx context.Context, + userCred mcclient.TokenCredential, sp *models.SSnapshotPolicy, input cloudprovider.SnapshotPolicyInput, + task taskman.ITask) error { + + return nil +} + +func (self *SKVMRegionDriver) ValidateCreateSnapshopolicyDiskData(ctx context.Context, + userCred mcclient.TokenCredential, disk *models.SDisk, snapshotPolicy *models.SSnapshotPolicy) error { + + if snapshotPolicy.RetentionDays < -1 || snapshotPolicy.RetentionDays == 0 || snapshotPolicy.RetentionDays > options.Options.RetentionDaysLimit { + return httperrors.NewInputParameterError("Retention days must in 1~%d or -1", options.Options.RetentionDaysLimit) } - // TODO: To be determined - if input.RetentionDays < -1 || input.RetentionDays == 0 || input.RetentionDays > 10 { - return httperrors.NewInputParameterError("Retention days must in 1~10 or -1") + + repeatWeekdays := models.SnapshotPolicyManager.RepeatWeekdaysToIntArray(snapshotPolicy.RepeatWeekdays) + timePoints := models.SnapshotPolicyManager.TimePointsToIntArray(snapshotPolicy.TimePoints) + + if len(repeatWeekdays) > options.Options.RepeatWeekdaysLimit { + return httperrors.NewInputParameterError("repeat_weekdays only contains %d days at most", + options.Options.RepeatWeekdaysLimit) + } + + if len(timePoints) > options.Options.TimePointsLimit { + return httperrors.NewInputParameterError("time_points only contains %d points at most", options.Options.TimePointsLimit) } return nil } -func (self *SKVMRegionDriver) RequestCreateSnapshotPolicy(ctx context.Context, userCred mcclient.TokenCredential, sp *models.SSnapshotPolicy, task taskman.ITask) error { +func (self *SKVMRegionDriver) RequestApplySnapshotPolicy(ctx context.Context, userCred mcclient.TokenCredential, task taskman.ITask, disk *models.SDisk, sp *models.SSnapshotPolicy, data jsonutils.JSONObject) error { taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { - return nil, nil + data := jsonutils.NewDict() + data.Add(jsonutils.NewString(sp.GetId()), "snapshotpolicy_id") + return data, nil }) return nil } -func (self *SKVMRegionDriver) ValidateCreateSnapshopolicyDiskData(ctx context.Context, userCred mcclient.TokenCredential, diskID string) error { - return nil -} - -func (self *SKVMRegionDriver) RequestApplySnapshotPolicy(ctx context.Context, userCred mcclient.TokenCredential, sp *models.SSnapshotPolicy, task taskman.ITask, diskId string) error { - task.ScheduleRun(nil) - return nil -} - -func (self *SKVMRegionDriver) RequestCancelSnapshotPolicy(ctx context.Context, userCred mcclient.TokenCredential, sp *models.SSnapshotPolicy, task taskman.ITask, diskId string) error { +func (self *SKVMRegionDriver) RequestCancelSnapshotPolicy(ctx context.Context, userCred mcclient.TokenCredential, task taskman.ITask, disk *models.SDisk, sp *models.SSnapshotPolicy, data jsonutils.JSONObject) error { taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { - return nil, nil + data := jsonutils.NewDict() + data.Add(jsonutils.NewString(sp.GetId()), "snapshotpolicy_id") + return data, nil }) return nil } @@ -823,3 +835,13 @@ func (self *SKVMRegionDriver) RequestBindIPToNatgateway(ctx context.Context, tas return nil } + +func (self *SKVMRegionDriver) RequestPreSnapshotPolicyApply(ctx context.Context, userCred mcclient. + TokenCredential, task taskman.ITask, disk *models.SDisk, sp *models.SSnapshotPolicy, data jsonutils.JSONObject) error { + + taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { + + return data, nil + }) + return nil +} diff --git a/pkg/compute/regiondrivers/managedvirtual.go b/pkg/compute/regiondrivers/managedvirtual.go index 58a682ee34..01e16d5719 100644 --- a/pkg/compute/regiondrivers/managedvirtual.go +++ b/pkg/compute/regiondrivers/managedvirtual.go @@ -29,7 +29,6 @@ import ( "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/compute/models" "yunion.io/x/onecloud/pkg/httperrors" @@ -1041,79 +1040,73 @@ func (self *SManagedVirtualizationRegionDriver) ValidateCreateEipData(ctx contex return data, nil } -func (self *SManagedVirtualizationRegionDriver) RequestCreateSnapshotPolicy(ctx context.Context, userCred mcclient.TokenCredential, sp *models.SSnapshotPolicy, task taskman.ITask) error { +func (self *SManagedVirtualizationRegionDriver) RequestUpdateSnapshotPolicy(ctx context.Context, userCred mcclient. + TokenCredential, sp *models.SSnapshotPolicy, input cloudprovider.SnapshotPolicyInput, task taskman.ITask) error { + // it's too cumbersome to pass parameters in taskman, so change a simple way for the moment + + //spcache, err := models.SnapshotPolicyCacheManager.FetchSnapshotPolicyCache(sp.GetId(), sp.CloudregionId, sp.ManagerId) + //if err != nil { + // return errors.Wrapf(err, "Fetch cache ofsnapshotpolicy %s", sp.GetId()) + //} + //return spcache.UpdateCloudSnapshotPolicy(&input) + + return nil +} + +// RequestApplySnapshotPolicy apply snapshotpolicy for public cloud. +// In our system, one disk only can hava one snapshot policy attached. +// Default, some public cloud such as Aliyun is same with us and this function shoule be used for these public cloud. +// But in Some public cloud such as Qcloud different with us, +// we should wirte a new function in corressponding regiondriver which detach all snapshotpolicy of disk after +// attache new one. +// You can refer to the implementations of function SQcloudRegionDriver.RequestApplySnapshotPolicy(). +func (self *SManagedVirtualizationRegionDriver) RequestApplySnapshotPolicy(ctx context.Context, + userCred mcclient.TokenCredential, task taskman.ITask, disk *models.SDisk, sp *models.SSnapshotPolicy, + data jsonutils.JSONObject) error { + taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { - iRegion, err := sp.GetIRegion() + + spcache, err := models.SnapshotPolicyCacheManager.Register(ctx, userCred, sp.GetId(), + disk.GetStorage().GetRegion().GetId(), + disk.GetStorage().ManagerId) if err != nil { - return nil, err + return nil, errors.Wrap(err, "registersnapshotpolicy cache failed") } - input, err := sp.GenerateCreateSpParams() - if err != nil { - return nil, err - } - policyId, err := iRegion.CreateSnapshotPolicy(input) - if err != nil { - return nil, err - } - err = db.SetExternalId(sp, userCred, policyId) + + iRegion, err := disk.GetIRegion() if err != nil { return nil, err } - iPolicy, err := iRegion.GetISnapshotPolicyById(policyId) + err = iRegion.ApplySnapshotPolicyToDisks(spcache.GetExternalId(), disk.GetExternalId()) 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 + data := jsonutils.NewDict() + data.Add(jsonutils.NewString(sp.GetId()), "snapshotpolicy_id") + return data, 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) RequestCancelSnapshotPolicy(ctx context.Context, userCred mcclient. + TokenCredential, task taskman.ITask, disk *models.SDisk, sp *models.SSnapshotPolicy, data jsonutils.JSONObject) error { -func (self *SManagedVirtualizationRegionDriver) RequestApplySnapshotPolicy(ctx context.Context, userCred mcclient.TokenCredential, sp *models.SSnapshotPolicy, task taskman.ITask, diskId string) error { taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { - iRegion, err := sp.GetIRegion() - if err != nil { - return nil, err - } - err = iRegion.ApplySnapshotPolicyToDisks(sp.GetExternalId(), diskId) - if err != nil { - return nil, err - } - return nil, nil - }) - return nil -} + spcache, err := models.SnapshotPolicyCacheManager.FetchSnapshotPolicyCache(sp.GetId(), + disk.GetStorage().GetRegion().GetId(), disk.GetStorage().ManagerId) -func (self *SManagedVirtualizationRegionDriver) RequestCancelSnapshotPolicy(ctx context.Context, userCred mcclient.TokenCredential, sp *models.SSnapshotPolicy, task taskman.ITask, diskId string) error { - taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { - iRegion, err := sp.GetIRegion() + iRegion, err := spcache.GetIRegion() if err != nil { return nil, err } - err = iRegion.CancelSnapshotPolicyToDisks(sp.GetExternalId(), diskId) + err = iRegion.CancelSnapshotPolicyToDisks(spcache.GetExternalId(), disk.GetExternalId()) if err != nil { return nil, err } - return nil, nil + data := jsonutils.NewDict() + data.Add(jsonutils.NewString(sp.GetId()), "snapshotpolicy_id") + return data, nil }) return nil } @@ -1180,7 +1173,9 @@ func (self *SManagedVirtualizationRegionDriver) GetDiskResetParams(snapshot *mod return params } -func (self *SManagedVirtualizationRegionDriver) OnDiskReset(ctx context.Context, userCred mcclient.TokenCredential, disk *models.SDisk, snapshot *models.SSnapshot, data jsonutils.JSONObject) error { +func (self *SManagedVirtualizationRegionDriver) OnDiskReset(ctx context.Context, userCred mcclient.TokenCredential, + disk *models.SDisk, snapshot *models.SSnapshot, data jsonutils.JSONObject) error { + externalId, _ := data.GetString("exteranl_disk_id") if len(externalId) > 0 { _, err := db.Update(disk, func() error { @@ -1211,19 +1206,11 @@ func (self *SManagedVirtualizationRegionDriver) OnDiskReset(ctx context.Context, return nil } -func (self *SManagedVirtualizationRegionDriver) ValidateCreateSnapshopolicyDiskData(ctx context.Context, userCred mcclient.TokenCredential, diskID string) error { - return nil -} - -func (self *SManagedVirtualizationRegionDriver) ValidateCreateSnapshotPolicyData(ctx context.Context, userCred mcclient.TokenCredential, input *api.SSnapshotPolicyCreateInput, ownerId mcclient.IIdentityProvider, data *jsonutils.JSONDict) error { - - cloudregionV := validators.NewModelIdOrNameValidator("cloudregion", "cloudregion", ownerId) - err := cloudregionV.Validate(data) - if err != nil { - return err +func (self *SManagedVirtualizationRegionDriver) ValidateCreateSnapshopolicyDiskData(ctx context.Context, + userCred mcclient.TokenCredential, disk *models.SDisk, snapshotPolicy *models.SSnapshotPolicy) error { + if snapshotPolicy.RetentionDays < -1 || snapshotPolicy.RetentionDays == 0 || snapshotPolicy.RetentionDays > 65535 { + return httperrors.NewInputParameterError("Retention days must in 1~65535 or -1") } - cloudregion := cloudregionV.Model.(*models.SCloudregion) - input.CloudregionId = cloudregion.GetId() return nil } @@ -1243,3 +1230,13 @@ func (self *SManagedVirtualizationRegionDriver) RequestBindIPToNatgateway(ctx co task.ScheduleRun(nil) return nil } + +func (self *SManagedVirtualizationRegionDriver) RequestPreSnapshotPolicyApply(ctx context.Context, userCred mcclient. + TokenCredential, task taskman.ITask, disk *models.SDisk, sp *models.SSnapshotPolicy, data jsonutils.JSONObject) error { + + taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { + + return data, nil + }) + return nil +} diff --git a/pkg/compute/regiondrivers/qcloud.go b/pkg/compute/regiondrivers/qcloud.go index 1e1b98ec10..2dad9a3a9f 100644 --- a/pkg/compute/regiondrivers/qcloud.go +++ b/pkg/compute/regiondrivers/qcloud.go @@ -18,7 +18,6 @@ import ( "context" "fmt" "regexp" - "yunion.io/x/jsonutils" api "yunion.io/x/onecloud/pkg/apis/compute" @@ -743,3 +742,27 @@ func (self *SQcloudRegionDriver) ValidateCreateLoadbalancerBackendData(ctx conte data.Set("cloudregion_id", jsonutils.NewString(lb.CloudregionId)) return data, nil } + +func (self *SQcloudRegionDriver) RequestPreSnapshotPolicyApply(ctx context.Context, userCred mcclient. + TokenCredential, task taskman.ITask, disk *models.SDisk, sp *models.SSnapshotPolicy, data jsonutils.JSONObject) error { + + taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { + + if sp == nil { + return data, nil + } + spcache, err := models.SnapshotPolicyCacheManager.FetchSnapshotPolicyCache(sp.GetId(), + disk.GetStorage().GetRegion().GetId(), disk.GetStorage().ManagerId) + + iRegion, err := spcache.GetIRegion() + if err != nil { + return nil, err + } + err = iRegion.CancelSnapshotPolicyToDisks(spcache.GetExternalId(), disk.GetExternalId()) + if err != nil { + return nil, err + } + return data, nil + }) + return nil +} diff --git a/pkg/compute/tasks/disk_delete_task.go b/pkg/compute/tasks/disk_delete_task.go index 45cf7c9ef2..0249e66960 100644 --- a/pkg/compute/tasks/disk_delete_task.go +++ b/pkg/compute/tasks/disk_delete_task.go @@ -118,7 +118,17 @@ func (self *DiskDeleteTask) OnMasterStorageDeleteDiskCompleteFailed(ctx context. } func (self *DiskDeleteTask) startPendingDeleteDisk(ctx context.Context, disk *models.SDisk) { - disk.DoPendingDelete(ctx, self.UserCred) + err := disk.DoPendingDelete(ctx, self.UserCred) + if err != nil { + self.OnGuestDiskDeleteCompleteFailed(ctx, disk, jsonutils.NewString("pending delete disk failed")) + return + } + err = models.SnapshotPolicyDiskManager.SyncDetachByDisk(ctx, self.UserCred, nil, disk) + if err != nil { + self.OnGuestDiskDeleteCompleteFailed(ctx, disk, + jsonutils.NewString("detach all snapshotpolicies of disk failed")) + return + } self.SetStageComplete(ctx, nil) } diff --git a/pkg/compute/tasks/snapshot_policy_delete_task.go b/pkg/compute/tasks/snapshot_policy_delete_task.go index 9f0a7663c9..152216590b 100644 --- a/pkg/compute/tasks/snapshot_policy_delete_task.go +++ b/pkg/compute/tasks/snapshot_policy_delete_task.go @@ -16,9 +16,9 @@ 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" @@ -45,15 +45,12 @@ func (self *SnapshotPolicyDeleteTask) taskFail(ctx context.Context, sp *models.S 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)) + err := models.SnapshotPolicyCacheManager.DeleteCloudSnapshotPolices(ctx, self.UserCred, sp.GetId()) + if err != nil { + self.taskFail(ctx, sp, err.Error()) return } - self.SetStage("OnSnapshotPolicyDeleteComplete", nil) - if err := region.GetDriver().RequestDeleteSnapshotPolicy(ctx, self.GetUserCred(), sp, self); err != nil { - self.taskFail(ctx, sp, err.Error()) - } + self.OnSnapshotPolicyDeleteComplete(ctx, sp, data) } func (self *SnapshotPolicyDeleteTask) OnSnapshotPolicyDeleteComplete(ctx context.Context, sp *models.SSnapshotPolicy, data jsonutils.JSONObject) { diff --git a/pkg/compute/tasks/snapshotpolicy_create_task.go b/pkg/compute/tasks/snapshotpolicy_create_task.go deleted file mode 100644 index 39ca18b76c..0000000000 --- a/pkg/compute/tasks/snapshotpolicy_create_task.go +++ /dev/null @@ -1,173 +0,0 @@ -// 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 tasks - -import ( - "context" - "fmt" - - "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/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, disk *models.SDisk, snapshotPolicyId, reason string) { - jointModel, err := db.FetchJointByIds(models.SnapshotPolicyDiskManager, disk.Id, snapshotPolicyId, jsonutils.JSONNull) - if err != nil { - log.Errorf("Fetch SnapshotPolicy %s Disk %s joint model failed %s", disk.Id, snapshotPolicyId, reason) - return - } - snapshotPolicyDisk := jointModel.(*models.SSnapshotPolicyDisk) - err = snapshotPolicyDisk.Detach(ctx, self.UserCred) - if err != nil { - log.Errorf("Delete SnapshotPolicy %s Disk %s joint model failed, need to delete", self.Id, snapshotPolicyId) - } - - disk.SetStatus(self.UserCred, compute.DISK_APPLY_SNAPSHOT_FAIL, reason) - db.OpsLog.LogEvent(disk, db.ACT_APPLY_SNAPSHOT_POLICY_FAILED, reason, self.UserCred) - logclient.AddActionLogWithStartable(self, disk, logclient.ACT_APPLY_SNAPSHOT_POLICY, reason, self.UserCred, false) - self.SetStageFailed(ctx, reason) -} - -func (self *SnapshotPolicyApplyTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { - disk := obj.(*models.SDisk) - snapshotPolicyID, _ := self.Params.GetString("snapshot_policy_id") - - // fetch disk model by diksID - model, err := models.SnapshotPolicyManager.FetchById(snapshotPolicyID) - if err != nil { - self.taskFail(ctx, disk, snapshotPolicyID, fmt.Sprintf("failed to fetch disk by id %s: %s", snapshotPolicyID, err.Error())) - return - } - snapshotPolicy := model.(*models.SSnapshotPolicy) - - self.SetStage("OnSnapshotPolicyApply", nil) - if err := disk.GetStorage().GetRegion().GetDriver(). - RequestApplySnapshotPolicy(ctx, self.UserCred, snapshotPolicy, self, disk.ExternalId); err != nil { - self.taskFail(ctx, disk, snapshotPolicyID, fmt.Sprintf("faile to attach snapshot policy %s and disk %s: %s", snapshotPolicy.Id, disk.Id, err.Error())) - } - -} - -func (self *SnapshotPolicyApplyTask) OnSnapshotPolicyApply(ctx context.Context, disk *models.SDisk, data jsonutils.JSONObject) { - db.OpsLog.LogEvent(disk, db.ACT_APPLY_SNAPSHOT_POLICY, "", self.UserCred) - logclient.AddActionLogWithStartable(self, disk, logclient.ACT_APPLY_SNAPSHOT_POLICY, "", self.UserCred, true) - self.SetStageComplete(ctx, nil) -} - -// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - -type SnapshotPolicyCancelTask struct { - taskman.STask -} - -func (self *SnapshotPolicyCancelTask) taskFail(ctx context.Context, disk *models.SDisk, snapshotPolicyId, reason string) { - jointModel, err := db.FetchJointByIds(models.SnapshotPolicyDiskManager, disk.Id, snapshotPolicyId, jsonutils.JSONNull) - if err != nil { - log.Errorf("Fetch SnapshotPolicy %s Disk %s joint model failed %s", disk.Id, snapshotPolicyId, reason) - return - } - snapshotPolicyDisk := jointModel.(*models.SSnapshotPolicyDisk) - err = snapshotPolicyDisk.MarkUnDelete() - if err != nil { - log.Errorf("Mark undelete joint model snapshotPolicy %s and disk %s failes", snapshotPolicyId, disk.Id) - } - disk.SetStatus(self.UserCred, compute.DISK_CALCEL_SNAPSHOT_FAIL, reason) - 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) - snapshotPolicyID, _ := self.GetParams().GetString("snapshot_policy_id") - - model, err := models.SnapshotPolicyManager.FetchById(snapshotPolicyID) - if err != nil { - self.taskFail(ctx, disk, snapshotPolicyID, fmt.Sprintf("failed to fetch disk by id %s: %s", snapshotPolicyID, err.Error())) - return - } - snapshotPolicy := model.(*models.SSnapshotPolicy) - self.SetStage("OnSnapshotPolicyCancel", nil) - if err := disk.GetStorage().GetRegion().GetDriver(). - RequestCancelSnapshotPolicy(ctx, self.UserCred, snapshotPolicy, self, disk.ExternalId); err != nil { - self.taskFail(ctx, disk, snapshotPolicyID, fmt.Sprintf("faile to detach snapshot policy %s and disk %s: %s", snapshotPolicy.Id, disk.Id, err.Error())) - } -} - -func (self *SnapshotPolicyCancelTask) OnSnapshotPolicyCancel(ctx context.Context, disk *models.SDisk, data jsonutils.JSONObject) { - 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) -} diff --git a/pkg/compute/tasks/snapshotpolicy_disk_task.go b/pkg/compute/tasks/snapshotpolicy_disk_task.go new file mode 100644 index 0000000000..3248492309 --- /dev/null +++ b/pkg/compute/tasks/snapshotpolicy_disk_task.go @@ -0,0 +1,210 @@ +// 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 tasks + +import ( + "context" + "fmt" + + "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/cloudcommon/notifyclient" + "yunion.io/x/onecloud/pkg/compute/models" + "yunion.io/x/onecloud/pkg/util/logclient" +) + +func init() { + taskman.RegisterTask(SnapshotPolicyApplyTask{}) + taskman.RegisterTask(SnapshotPolicyCancelTask{}) +} + +type SnapshotPolicyApplyTask struct { + taskman.STask +} + +func (self *SnapshotPolicyApplyTask) taskFail(ctx context.Context, disk *models.SDisk, + spd *models.SSnapshotPolicyDisk, reason string) { + + if spd != nil { + err := spd.RealDetach(ctx, self.UserCred) + if err != nil { + log.Errorf("Delete snapshotpolicydisk %s failed, need to delete", spd.GetId()) + } + } + + disk.SetStatus(self.UserCred, compute.DISK_APPLY_SNAPSHOT_FAIL, reason) + db.OpsLog.LogEvent(disk, db.ACT_APPLY_SNAPSHOT_POLICY_FAILED, reason, self.UserCred) + logclient.AddActionLogWithStartable(self, disk, logclient.ACT_APPLY_SNAPSHOT_POLICY, reason, self.UserCred, false) + notifyclient.NotifySystemError(disk.GetId(), disk.Name, compute.DISK_APPLY_SNAPSHOT_FAIL, reason) + self.SetStageFailed(ctx, reason) +} + +func (self *SnapshotPolicyApplyTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + disk := obj.(*models.SDisk) + spd := models.SSnapshotPolicyDisk{} + data.Unmarshal(&spd, "snapshotPolicy") + + var snapshotPolicy *models.SSnapshotPolicy + if data.Contains("need_detach") { + snapshotPolicyID, _ := data.GetString("need_detach") + model, err := models.SnapshotPolicyManager.FetchById(snapshotPolicyID) + if err != nil { + self.taskFail(ctx, disk, &spd, err.Error()) + return + } + snapshotPolicy = model.(*models.SSnapshotPolicy) + } + self.Params.Add(jsonutils.NewString(spd.GetId()), "snapshotpolicy_id") + + self.SetStage("OnPreSnapshotPolicyApplyComplete", nil) + // pass data to next Stage without inserting database through this way + if err := disk.GetStorage().GetRegion().GetDriver().RequestPreSnapshotPolicyApply(ctx, self.UserCred, self, disk, snapshotPolicy, + data); err != nil { + + self.taskFail(ctx, disk, &spd, err.Error()) + return + } +} + +func (self *SnapshotPolicyApplyTask) OnPreSnapshotPolicyApplyCompleteFailed(ctx context.Context, disk *models.SDisk, + reason jsonutils.JSONObject) { + + spId, _ := self.Params.GetString("snapshotpolicy_id") + spd, err := models.SnapshotPolicyDiskManager.FetchBySnapshotPolicyDisk(spId, disk.GetId()) + if err != nil { + self.taskFail(ctx, disk, nil, reason.String()) + return + } + self.taskFail(ctx, disk, spd, reason.String()) +} + +func (self *SnapshotPolicyApplyTask) OnPreSnapshotPolicyApplyComplete(ctx context.Context, disk *models.SDisk, + data jsonutils.JSONObject) { + + if data.Contains("need_detach") { + snapshotPolicyID, _ := data.GetString("need_detach") + spd1, err := models.SnapshotPolicyDiskManager.FetchBySnapshotPolicyDisk(snapshotPolicyID, disk.GetId()) + if err != nil { + self.taskFail(ctx, disk, spd1, err.Error()) + return + } + if spd1 != nil { + spd1.RealDetach(ctx, self.UserCred) + } + + } + snapshotPolicy, spd := models.SSnapshotPolicy{}, models.SSnapshotPolicyDisk{} + data.Unmarshal(&snapshotPolicy, "snapshotPolicy") + data.Unmarshal(&spd, "snapshotPolicyDisk") + self.SetStage("OnSnapshotPolicyApply", nil) + + // pass data to next Stage without inserting database through this way + if err := disk.GetStorage().GetRegion().GetDriver(). + RequestApplySnapshotPolicy(ctx, self.UserCred, self, disk, &snapshotPolicy, data); err != nil { + + self.taskFail(ctx, disk, &spd, err.Error()) + } +} + +func (self *SnapshotPolicyApplyTask) OnSnapshotPolicyApplyFailed(ctx context.Context, disk *models.SDisk, + reason jsonutils.JSONObject) { + + spId, _ := self.Params.GetString("snapshotpolicy_id") + spd, err := models.SnapshotPolicyDiskManager.FetchBySnapshotPolicyDisk(spId, disk.GetId()) + if err != nil { + self.taskFail(ctx, disk, nil, reason.String()) + return + } + self.taskFail(ctx, disk, spd, reason.String()) +} + +func (self *SnapshotPolicyApplyTask) OnSnapshotPolicyApply(ctx context.Context, disk *models.SDisk, data jsonutils.JSONObject) { + sp_id, _ := data.GetString("snapshotpolicy_id") + spd, err := models.SnapshotPolicyDiskManager.FetchBySnapshotPolicyDisk(sp_id, disk.GetId()) + if err != nil { + log.Errorf("Fechsnapshotpolicy disk failed") + } + spd.SetStatus(self.UserCred, compute.SNAPSHOT_POLICY_DISK_READY, "") + db.OpsLog.LogEvent(disk, db.ACT_APPLY_SNAPSHOT_POLICY, "", self.UserCred) + logclient.AddActionLogWithStartable(self, disk, logclient.ACT_APPLY_SNAPSHOT_POLICY, "", self.UserCred, true) + self.SetStageComplete(ctx, nil) +} + +// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +type SnapshotPolicyCancelTask struct { + taskman.STask +} + +func (self *SnapshotPolicyCancelTask) taskFail(ctx context.Context, disk *models.SDisk, spd *models.SSnapshotPolicyDisk, reason string) { + if spd != nil { + spd.SetStatus(self.UserCred, compute.SNAPSHOT_POLICY_DISK_DELETE_FAILED, "") + } + disk.SetStatus(self.UserCred, compute.DISK_CALCEL_SNAPSHOT_FAIL, reason) + 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) + snapshotPolicyID, _ := data.GetString("snapshot_policy_id") + spd := models.SSnapshotPolicyDisk{} + data.Unmarshal(&spd, "snapshotPolicyDisk") + self.Params.Add(jsonutils.NewString(snapshotPolicyID), "snapshotpolicy_id") + + model, err := models.SnapshotPolicyManager.FetchById(snapshotPolicyID) + if err != nil { + self.taskFail(ctx, disk, &spd, fmt.Sprintf("failed to fetch disk by id %s: %s", snapshotPolicyID, err.Error())) + return + } + snapshotPolicy := model.(*models.SSnapshotPolicy) + self.SetStage("OnSnapshotPolicyCancel", nil) + if err := disk.GetStorage().GetRegion().GetDriver().RequestCancelSnapshotPolicy(ctx, self.UserCred, self, disk, snapshotPolicy, data); err != nil { + + self.taskFail(ctx, disk, &spd, fmt.Sprintf("faile to detach snapshot policy %s and disk %s: %s", + snapshotPolicy.Id, disk.Id, err.Error())) + } +} + +func (self *SnapshotPolicyCancelTask) OnSnapshotPolicyCancelFailed(ctx context.Context, disk *models.SDisk, + reason jsonutils.JSONObject) { + + spId, _ := self.Params.GetString("snapshotpolicy_id") + spd, err := models.SnapshotPolicyDiskManager.FetchBySnapshotPolicyDisk(spId, disk.GetId()) + if err != nil { + self.taskFail(ctx, disk, nil, reason.String()) + return + } + self.taskFail(ctx, disk, spd, reason.String()) +} + +func (self *SnapshotPolicyCancelTask) OnSnapshotPolicyCancel(ctx context.Context, disk *models.SDisk, data jsonutils.JSONObject) { + sp_id, _ := data.GetString("snapshotpolicy_id") + spd, err := models.SnapshotPolicyDiskManager.FetchBySnapshotPolicyDisk(sp_id, disk.GetId()) + if err != nil { + log.Errorf("Fechsnapshotpolicy disk failed") + } + //real detach + spd.RealDetach(ctx, self.UserCred) + + 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) +} diff --git a/pkg/mcclient/modules/mod_snapshotpolicydisk.go b/pkg/mcclient/modules/mod_snapshotpolicydisk.go index 27eabd6d55..3794ff06e9 100644 --- a/pkg/mcclient/modules/mod_snapshotpolicydisk.go +++ b/pkg/mcclient/modules/mod_snapshotpolicydisk.go @@ -15,7 +15,8 @@ package modules var ( - SnapshotPolicyDisk JointResourceManager + SnapshotPolicyDisk JointResourceManager + SnapshotPolicyDisk1 JointResourceManager ) func init() { @@ -27,4 +28,13 @@ func init() { &Disks, &SnapshotPoliciy) registerCompute(&SnapshotPolicyDisk) + + SnapshotPolicyDisk1 = NewJointComputeManager( + "snapshotpolicydisk", + "snapshotpolicydisks", + []string{"Disk_ID", "Snapshotpolicy_ID"}, + []string{}, + &SnapshotPoliciy, + &Disks) + registerCompute(&SnapshotPolicyDisk1) } diff --git a/pkg/multicloud/aliyun/disk.go b/pkg/multicloud/aliyun/disk.go index e6940a1446..e633225247 100644 --- a/pkg/multicloud/aliyun/disk.go +++ b/pkg/multicloud/aliyun/disk.go @@ -405,8 +405,11 @@ func (self *SDisk) GetCreatedAt() time.Time { return self.CreationTime } -func (self *SDisk) GetExtSnapshotPolicyIds() []string { - return []string{self.AutoSnapshotPolicyId} +func (self *SDisk) GetExtSnapshotPolicyIds() ([]string, error) { + if len(self.AutoSnapshotPolicyId) == 0 { + return []string{}, nil + } + return []string{self.AutoSnapshotPolicyId}, nil } func (self *SDisk) GetExpiredAt() time.Time { diff --git a/pkg/multicloud/aliyun/snapshot_policy.go b/pkg/multicloud/aliyun/snapshot_policy.go index 74274c5358..5c369aecac 100644 --- a/pkg/multicloud/aliyun/snapshot_policy.go +++ b/pkg/multicloud/aliyun/snapshot_policy.go @@ -234,23 +234,17 @@ func (self *SRegion) CreateSnapshotPolicy(input *cloudprovider.SnapshotPolicyInp } } -func (self *SRegion) UpdateSnapshotPolicy( - snapshotPolicyId string, retentionDays *int, - repeatWeekdays, timePoints *jsonutils.JSONArray, policyName string, -) error { +func (self *SRegion) UpdateSnapshotPolicy(input *cloudprovider.SnapshotPolicyInput, snapshotPolicyId string) error { params := make(map[string]string) params["RegionId"] = self.RegionId - if len(policyName) > 0 { - params["autoSnapshotPolicyName"] = policyName + if input.RetentionDays != 0 { + params["retentionDays"] = strconv.Itoa(input.RetentionDays) } - if retentionDays != nil { - params["retentionDays"] = strconv.Itoa(*retentionDays) + if input.RepeatWeekdays != nil && len(input.RepeatWeekdays) != 0 { + params["repeatWeekdays"] = jsonutils.Marshal(input.GetStringArrayRepeatWeekdays()).String() } - if repeatWeekdays != nil { - params["repeatWeekdays"] = repeatWeekdays.String() - } - if timePoints != nil { - params["timePoints"] = timePoints.String() + if input.TimePoints != nil && len(input.TimePoints) != 0 { + params["timePoints"] = jsonutils.Marshal(input.GetStringArrayTimePoints()).String() } _, err := self.ecsRequest("ModifyAutoSnapshotPolicyEx", params) if err != nil { @@ -259,6 +253,31 @@ func (self *SRegion) UpdateSnapshotPolicy( return nil } +//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, diskId string) error { params := make(map[string]string) params["RegionId"] = self.RegionId diff --git a/pkg/multicloud/disk_base.go b/pkg/multicloud/disk_base.go index e7290691ef..873e92273b 100644 --- a/pkg/multicloud/disk_base.go +++ b/pkg/multicloud/disk_base.go @@ -16,8 +16,8 @@ package multicloud type SDisk struct{} -func (self *SDisk) GetExtSnapshotPolicyIds() []string { - return []string{""} +func (self *SDisk) GetExtSnapshotPolicyIds() ([]string, error) { + return []string{""}, nil } func (self *SDisk) GetIStorageId() string { diff --git a/pkg/multicloud/qcloud/disk.go b/pkg/multicloud/qcloud/disk.go index b92e4a14d5..2942917e27 100644 --- a/pkg/multicloud/qcloud/disk.go +++ b/pkg/multicloud/qcloud/disk.go @@ -348,6 +348,10 @@ func (self *SDisk) GetISnapshots() ([]cloudprovider.ICloudSnapshot, error) { return isnapshots, nil } +func (self *SDisk) GetExtSnapshotPolicyIds() ([]string, error) { + return self.storage.zone.region.GetSnapshotIdByDiskId(self.GetId()) +} + func (self *SDisk) GetTemplateId() string { //return self.ImageId return "" diff --git a/pkg/multicloud/qcloud/snapshot_policy.go b/pkg/multicloud/qcloud/snapshot_policy.go index d941e933c5..86dc5f6782 100644 --- a/pkg/multicloud/qcloud/snapshot_policy.go +++ b/pkg/multicloud/qcloud/snapshot_policy.go @@ -43,7 +43,7 @@ type SSnapshotPolicy struct { AutoSnapshotPolicyState string RetentionDays int Policy []SPolicy - Activated bool `json:"is_activated"` + Activated bool `json:"IsActivated"` IsPermanent bool } @@ -90,6 +90,9 @@ func (self *SSnapshotPolicy) GetProjectId() string { } func (self *SSnapshotPolicy) GetRetentionDays() int { + if self.IsPermanent { + return -1 + } return self.RetentionDays } @@ -148,6 +151,9 @@ func (self *SRegion) GetSnapshotPolicies(policyId string, offset int, limit int) if err := body.Unmarshal(&snapshotPolicies, "AutoSnapshotPolicySet"); err != nil { return nil, 0, errors.Wrap(err, "Unmarshal snapshot policies detail failed") } + for i := range snapshotPolicies { + snapshotPolicies[i].region = self + } return snapshotPolicies, len(snapshotPolicies), nil } @@ -187,7 +193,14 @@ func (self *SRegion) CreateSnapshotPolicy(input *cloudprovider.SnapshotPolicyInp return "", fmt.Errorf("Can't create snapshot policy with nil timePoints") } params := make(map[string]string) - params["RetentionDays"] = strconv.Itoa(input.RetentionDays) + + // In Qcloud, that IsPermanent is true means that keep snapshot forever, + // In OneCloud, that RetentionDays is -1 means that keep snapshot forever. + if input.RetentionDays == -1 { + params["IsPermanent"] = strconv.FormatBool(true) + } else { + params["RetentionDays"] = strconv.Itoa(input.RetentionDays) + } dayOfWeekPrefix, hourPrefix := "Policy.0.DayOfWeek.", "Policy.0.Hour." for index, day := range input.RepeatWeekdays { if day == 0 { @@ -209,10 +222,7 @@ func (self *SRegion) CreateSnapshotPolicy(input *cloudprovider.SnapshotPolicyInp return id, nil } -func (self *SRegion) UpdateSnapshotPolicy( - snapshotPolicyId string, retentionDays *int, - repeatWeekdays, timePoints *jsonutils.JSONArray, policyName string, -) error { +func (self *SRegion) UpdateSnapshotPolicy(input *cloudprovider.SnapshotPolicyInput, snapshotPolicyId string) error { // not implement return nil } @@ -238,3 +248,23 @@ func (self *SRegion) CancelSnapshotPolicyToDisks(snapshotPolicyId string, diskId } return nil } + +func (self *SRegion) GetSnapshotIdByDiskId(diskID string) ([]string, error) { + params := make(map[string]string) + params["DiskId"] = diskID + + rps, err := self.cbsRequest("DescribeDiskAssociatedAutoSnapshotPolicy", params) + if err != nil { + return nil, errors.Wrapf(err, "Get All SnapshotpolicyIDs of Disk %s failed", diskID) + } + + snapshotpolicies := make([]SSnapshotPolicy, 0) + if err := rps.Unmarshal(&snapshotpolicies, "AutoSnapshotPolicySet"); err != nil { + return nil, errors.Wrapf(err, "Unmarshal snapshot policies details failed") + } + ret := make([]string, len(snapshotpolicies)) + for i := range snapshotpolicies { + ret[i] = snapshotpolicies[i].GetId() + } + return ret, nil +} diff --git a/pkg/multicloud/region_base.go b/pkg/multicloud/region_base.go index 13855e4ad7..3d4cbac4da 100644 --- a/pkg/multicloud/region_base.go +++ b/pkg/multicloud/region_base.go @@ -26,6 +26,10 @@ func (r *SRegion) CreateSnapshotPolicy(input *cloudprovider.SnapshotPolicyInput) return "", fmt.Errorf("CreateSnapshotPolicy not implement") } +func (r *SRegion) UpdateSnapshotPolicy(input *cloudprovider.SnapshotPolicyInput, snapshotPolicyId string) error { + return fmt.Errorf("UpdateSnapshotPolicy not implement") +} + func (r *SRegion) GetISnapshotPolicyById(snapshotPolicyId string) (cloudprovider.ICloudSnapshotPolicy, error) { return nil, fmt.Errorf("GetISnapshotPolicyById not implement") } diff --git a/pkg/util/bitmap/bitmap.go b/pkg/util/bitmap/bitmap.go index 5a80cf9c86..fdf4dedc60 100644 --- a/pkg/util/bitmap/bitmap.go +++ b/pkg/util/bitmap/bitmap.go @@ -14,6 +14,7 @@ package bitmap +// Uint2IntArray transfer bitmap displayed as n to int array func Uint2IntArray(n uint32) []int { ret := make([]int, 0, 2) var i uint = 0 @@ -27,6 +28,7 @@ func Uint2IntArray(n uint32) []int { return ret } +// IntArray2Uint transfer int array nums to bitmap number func IntArray2Uint(nums []int) uint32 { var ret uint32 = 0 for _, i := range nums { @@ -34,3 +36,16 @@ func IntArray2Uint(nums []int) uint32 { } return ret } + +// Determine if int slice a euqals b +func IntSliceEqual(a, b []int) bool { + if len(a) != len(b) { + return false + } + for i := 0; i < len(a); i++ { + if a[i] != b[i] { + return false + } + } + return true +} diff --git a/pkg/util/bitmap/bitmap_test.go b/pkg/util/bitmap/bitmap_test.go index 0ca70f462f..78cf8b2bca 100644 --- a/pkg/util/bitmap/bitmap_test.go +++ b/pkg/util/bitmap/bitmap_test.go @@ -35,24 +35,12 @@ func TestUint2IntArray(t *testing.T) { for _, tc := range testCase { real := Uint2IntArray(tc.input) - if !sliceEqual(real, tc.want) { + if !IntSliceEqual(real, tc.want) { t.Fatalf("want %v, but %v\n", tc.want, real) } } } -func sliceEqual(a, b []int) bool { - if len(a) != len(b) { - return false - } - for i := 0; i < len(a); i++ { - if a[i] != b[i] { - return false - } - } - return true -} - func TestIntArray2Uint(t *testing.T) { oneCase := make([]int, 32) for i := range oneCase { diff --git a/pkg/util/validate/days.go b/pkg/util/validate/days.go new file mode 100644 index 0000000000..20eb10691c --- /dev/null +++ b/pkg/util/validate/days.go @@ -0,0 +1,39 @@ +// 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 validate + +import ( + "fmt" + "sort" +) + +// DaysValidate sort days and check if days is out of range [min, max] or has repeated member +func DaysCheck(days []int, min, max int) ([]int, error) { + if len(days) == 0 { + return days, nil + } + sort.Ints(days) + + if days[0] < min || days[len(days)-1] > max { + return days, fmt.Errorf("Out of range") + } + + for i := 1; i < len(days); i++ { + if days[i] == days[i-1] { + return days, fmt.Errorf("Has repeat day %v", days) + } + } + return days, nil +} diff --git a/pkg/util/validate/days_test.go b/pkg/util/validate/days_test.go new file mode 100644 index 0000000000..587a2ba50d --- /dev/null +++ b/pkg/util/validate/days_test.go @@ -0,0 +1,63 @@ +// 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 validate + +import ( + "fmt" + "reflect" + "testing" +) + +func TestDaysCheck(t *testing.T) { + tcs := []struct { + input_days []int + input_max int + input_min int + want []int + want_error error + }{ + { + []int{3, 5, 1, 4, 8, 3, 4}, + 10, + 1, + []int{1, 3, 3, 4, 4, 5, 8}, + fmt.Errorf("Has repeat day %v", []int{1, 3, 3, 4, 4, 5, 8}), + }, + { + []int{10, 1}, + 10, + 1, + []int{1, 10}, + nil, + }, + { + []int{10, 3, 5}, + 3, + 5, + []int{3, 5, 10}, + fmt.Errorf("Out of range"), + }, + } + for _, tc := range tcs { + days, err := DaysCheck(tc.input_days, tc.input_min, tc.input_max) + if !reflect.DeepEqual(days, tc.want) { + t.Fatalf("want: %v, actual: %v", tc.want, days) + } + + if !reflect.DeepEqual(err, tc.want_error) { + t.Fatal("err not correct") + } + } +}