From 23e3234d8dc82cc7ea91deed5626bae0f60248b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B1=88=E8=BD=A9?= Date: Mon, 12 Jan 2026 12:37:38 +0800 Subject: [PATCH] fix(region): add billing resource check (#24059) --- .../shell/compute/billing_resource_checks.go | 27 ++ pkg/apis/compute/billing_resource_checks.go | 25 ++ pkg/apis/compute/snapshotpolicy.go | 2 + pkg/compute/models/billingresource.go | 256 +-------------- pkg/compute/models/billingresourcecheck.go | 308 ++++++++++++++++++ pkg/compute/models/snapshotpolicy.go | 33 ++ pkg/compute/service/handlers.go | 2 +- .../compute/mod_billing_resource_checks.go | 36 ++ .../compute/billing_resource_checks.go | 41 +++ 9 files changed, 481 insertions(+), 249 deletions(-) create mode 100644 cmd/climc/shell/compute/billing_resource_checks.go create mode 100644 pkg/apis/compute/billing_resource_checks.go create mode 100644 pkg/compute/models/billingresourcecheck.go create mode 100644 pkg/mcclient/modules/compute/mod_billing_resource_checks.go create mode 100644 pkg/mcclient/options/compute/billing_resource_checks.go diff --git a/cmd/climc/shell/compute/billing_resource_checks.go b/cmd/climc/shell/compute/billing_resource_checks.go new file mode 100644 index 0000000000..e9c2155649 --- /dev/null +++ b/cmd/climc/shell/compute/billing_resource_checks.go @@ -0,0 +1,27 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package compute + +import ( + "yunion.io/x/onecloud/cmd/climc/shell" + modules "yunion.io/x/onecloud/pkg/mcclient/modules/compute" + computeoptions "yunion.io/x/onecloud/pkg/mcclient/options/compute" +) + +func init() { + cmd := shell.NewResourceCmd(&modules.BillingResourceChecks).WithKeyword("billing-resource-check") + cmd.List(new(computeoptions.BillingResourceCheckListOptions)) + cmd.PerformClass("check", new(computeoptions.BillingResourceCheckCheckOptions)) +} diff --git a/pkg/apis/compute/billing_resource_checks.go b/pkg/apis/compute/billing_resource_checks.go new file mode 100644 index 0000000000..5545686715 --- /dev/null +++ b/pkg/apis/compute/billing_resource_checks.go @@ -0,0 +1,25 @@ +// 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 compute + +import ( + "yunion.io/x/onecloud/pkg/apis" +) + +type BillingResourceCheckListInput struct { + apis.VirtualResourceListInput + + ResourceType []string `json:"resource_type"` +} diff --git a/pkg/apis/compute/snapshotpolicy.go b/pkg/apis/compute/snapshotpolicy.go index e859a2373f..75f133ca20 100644 --- a/pkg/apis/compute/snapshotpolicy.go +++ b/pkg/apis/compute/snapshotpolicy.go @@ -46,6 +46,8 @@ type SnapshotPolicyDetails struct { BindingDiskCount int `json:"binding_disk_count"` BindingResourceCount int `json:"binding_resource_count"` + // 快照数量 + SnapshotCount int `json:"snapshot_count"` } type SSnapshotPolicyCreateInput struct { diff --git a/pkg/compute/models/billingresource.go b/pkg/compute/models/billingresource.go index 88ed7233c0..6b3bc7e166 100644 --- a/pkg/compute/models/billingresource.go +++ b/pkg/compute/models/billingresource.go @@ -16,26 +16,14 @@ package models import ( "context" - "fmt" - "reflect" "time" "yunion.io/x/jsonutils" - "yunion.io/x/log" - "yunion.io/x/pkg/errors" - "yunion.io/x/pkg/util/billing" "yunion.io/x/sqlchemy" api "yunion.io/x/onecloud/pkg/apis/billing" - billing_api "yunion.io/x/onecloud/pkg/apis/billing" - notifyapi "yunion.io/x/onecloud/pkg/apis/notify" - "yunion.io/x/onecloud/pkg/cloudcommon/db" - "yunion.io/x/onecloud/pkg/cloudcommon/notifyclient" "yunion.io/x/onecloud/pkg/compute/options" "yunion.io/x/onecloud/pkg/mcclient" - "yunion.io/x/onecloud/pkg/mcclient/auth" - "yunion.io/x/onecloud/pkg/mcclient/modules/notify" - "yunion.io/x/onecloud/pkg/util/logclient" "yunion.io/x/onecloud/pkg/util/stringutils2" ) @@ -71,6 +59,14 @@ func (self *SBillingResourceBase) GetExpiredAt() time.Time { return self.ExpiredAt } +func (self *SBillingResourceBase) GetReleaseAt() time.Time { + return self.ReleaseAt +} + +func (self *SBillingResourceBase) GetAutoRenew() bool { + return self.AutoRenew +} + func (self *SBillingResourceBase) SetExpiredAt(expireAt time.Time) { self.ExpiredAt = expireAt } @@ -204,239 +200,3 @@ func ListExpiredPostpaidResources( } return q } - -type SBillingResourceCheckManager struct { - db.SResourceBaseManager -} - -type SBillingResourceCheck struct { - db.SResourceBase - ResourceId string `width:"128" charset:"ascii" primary:"true"` - ResourceType string `width:"36" charset:"ascii" primary:"true"` - AdvanceDays int `primary:"true"` - LastCheck time.Time - NotifyNumber int -} - -var BillingResourceCheckManager *SBillingResourceCheckManager - -func init() { - BillingResourceCheckManager = &SBillingResourceCheckManager{ - SResourceBaseManager: db.NewResourceBaseManager( - SBillingResourceCheck{}, - "billingresourcecheck2_tbl", - "billingresourcecheck", - "billingresourcechecks", - ), - } - BillingResourceCheckManager.SetVirtualObject(BillingResourceCheckManager) -} - -type IBillingModelManager interface { - db.IModelManager - GetExpiredModels(advanceDay int) ([]IBillingModel, error) -} - -type IBillingModel interface { - db.IModel - GetExpiredAt() time.Time - SetReleaseAt(releaseAt time.Time) - SetExpiredAt(expireAt time.Time) - SetBillingCycle(billingCycle string) - SetBillingType(billingType string) - GetBillingType() string -} - -func SaveReleaseAt(ctx context.Context, model IBillingModel, userCred mcclient.TokenCredential, releaseAt time.Time) error { - diff, err := db.Update(model, func() error { - model.SetReleaseAt(releaseAt) - return nil - }) - if err != nil { - return errors.Wrap(err, "Update") - } - if len(diff) > 0 { - db.OpsLog.LogEvent(model, db.ACT_SET_RELEASE_TIME, fmt.Sprintf("release at: %s", releaseAt), userCred) - } - if len(diff) > 0 && userCred != nil { - logclient.AddActionLogWithContext(ctx, model, logclient.ACT_SET_RELEASE_TIME, diff, userCred, true) - } - return nil -} - -func SaveRenewInfo( - ctx context.Context, userCred mcclient.TokenCredential, - model IBillingModel, bc *billing.SBillingCycle, expireAt *time.Time, billingType string, -) error { - _, err := db.Update(model, func() error { - if billingType == "" { - billingType = billing_api.BILLING_TYPE_PREPAID - } - if model.GetBillingType() == "" { - model.SetBillingType(billingType) - } - if expireAt != nil && !expireAt.IsZero() { - model.SetExpiredAt(*expireAt) - } else if bc != nil { - model.SetBillingCycle(bc.String()) - model.SetExpiredAt(bc.EndAt(model.GetExpiredAt())) - } - return nil - }) - if err != nil { - log.Errorf("UpdateItem error %s", err) - return err - } - db.OpsLog.LogEvent(model, db.ACT_RENEW, model.GetShortDesc(ctx), userCred) - return nil -} - -func fetchExpiredModels(manager db.IModelManager, advanceDay int) ([]IBillingModel, error) { - upLimit := time.Now().AddDate(0, 0, advanceDay+1) - downLimit := time.Now().AddDate(0, 0, advanceDay) - v := reflect.MakeSlice(reflect.SliceOf(manager.TableSpec().DataType()), 0, 0) - q := manager.Query() - q = q.Filter( - sqlchemy.OR( - sqlchemy.AND( - sqlchemy.Equals(q.Field("billing_type"), billing_api.BILLING_TYPE_POSTPAID), - sqlchemy.LE(q.Field("expired_at"), upLimit), - sqlchemy.GE(q.Field("expired_at"), downLimit), - ), - // 跳过自动续费实例 - sqlchemy.AND( - sqlchemy.Equals(q.Field("billing_type"), billing_api.BILLING_TYPE_PREPAID), - sqlchemy.LE(q.Field("expired_at"), upLimit), - sqlchemy.GE(q.Field("expired_at"), downLimit), - sqlchemy.IsFalse(q.Field("auto_renew")), - ))) - - vp := reflect.New(v.Type()) - vp.Elem().Set(v) - err := db.FetchModelObjects(manager, q, vp.Interface()) - if err != nil { - return nil, errors.Wrapf(err, "unable to list %s", manager.KeywordPlural()) - } - - v = vp.Elem() - log.Debugf("%s length of v: %d", manager.Alias(), v.Len()) - - ms := make([]IBillingModel, v.Len()) - for i := range ms { - ms[i] = v.Index(i).Addr().Interface().(IBillingModel) - } - return ms, nil -} - -func (bm *SBillingResourceCheckManager) Create(ctx context.Context, resourceId, resourceType string, advanceDays int) error { - bc := &SBillingResourceCheck{ - ResourceId: resourceId, - ResourceType: resourceType, - AdvanceDays: advanceDays, - LastCheck: time.Now(), - NotifyNumber: 1, - } - bc.SetModelManager(bm, bc) - return bm.TableSpec().InsertOrUpdate(ctx, bc) -} - -func (bm *SBillingResourceCheckManager) Fetch(resourceIds []string, advanceDays int, length int) (map[string]*SBillingResourceCheck, error) { - billingResourceChecks := make([]SBillingResourceCheck, 0, length) - bq := bm.Query().Equals("advance_days", advanceDays).In("resource_id", resourceIds) - err := db.FetchModelObjects(bm, bq, &billingResourceChecks) - if err != nil { - return nil, err - } - ret := make(map[string]*SBillingResourceCheck, len(billingResourceChecks)) - for i := range billingResourceChecks { - ret[billingResourceChecks[i].ResourceId] = &billingResourceChecks[i] - } - return ret, nil -} - -func CheckBillingResourceExpireAt(ctx context.Context, userCred mcclient.TokenCredential, isStart bool) { - billingResourceManagers := []IBillingModelManager{ - GuestManager, - DBInstanceManager, - ElasticcacheManager, - } - s := auth.GetAdminSession(ctx, options.Options.Region) - resp, err := notify.NotifyTopic.List(s, jsonutils.Marshal(map[string]interface{}{ - "filter": fmt.Sprintf("name.equals('%s')", notifyapi.DefaultResourceRelease), - "scope": "system", - })) - if err != nil { - log.Errorln(errors.Wrap(err, "list topics")) - return - } - topics := []notifyapi.TopicDetails{} - err = jsonutils.Update(&topics, resp.Data) - if err != nil { - log.Errorln(errors.Wrap(err, "update topic")) - return - } - if len(topics) != 1 { - log.Errorln(errors.Wrapf(errors.ErrNotSupported, "len topics :%d", len(topics))) - return - } - - for _, advanceDay := range topics[0].AdvanceDays { - for _, manager := range billingResourceManagers { - expiredModels, err := manager.GetExpiredModels(advanceDay) - if err != nil { - log.Errorf("unable to fetchExpiredModels: %s", err.Error()) - continue - } - mIds := make([]string, len(expiredModels)) - for i := range expiredModels { - mIds[i] = expiredModels[i].GetId() - } - checks, err := BillingResourceCheckManager.Fetch(mIds, advanceDay, len(expiredModels)) - if err != nil { - log.Errorf("unbale to fetch billingResourceChecks: %s", err.Error()) - continue - } - - for i := range expiredModels { - em := expiredModels[i] - check, ok := checks[em.GetId()] - if !ok { - detailsDecro := func(ctx context.Context, details *jsonutils.JSONDict) { - details.Set("advance_days", jsonutils.NewInt(int64(advanceDay))) - } - notifyclient.EventNotify(ctx, userCred, notifyclient.SEventNotifyParam{ - Obj: em, - ObjDetailsDecorator: detailsDecro, - Action: notifyclient.ActionExpiredRelease, - AdvanceDays: advanceDay, - }) - err := BillingResourceCheckManager.Create(ctx, em.GetId(), manager.Keyword(), advanceDay) - if err != nil { - log.Errorf("unable to create billingresourcecheck for resource %s %s", manager.Keyword(), em.GetId()) - } - continue - } - if check.LastCheck.AddDate(0, 0, advanceDay).After(em.GetExpiredAt()) { - continue - } - detailsDecro := func(ctx context.Context, details *jsonutils.JSONDict) { - details.Set("advance_days", jsonutils.NewInt(int64(advanceDay))) - } - notifyclient.EventNotify(ctx, userCred, notifyclient.SEventNotifyParam{ - ObjDetailsDecorator: detailsDecro, - Obj: em, - Action: notifyclient.ActionExpiredRelease, - AdvanceDays: advanceDay, - }) - _, err := db.Update(check, func() error { - check.LastCheck = time.Now() - check.NotifyNumber += 1 - return nil - }) - if err != nil { - log.Errorf("unable to update billingresourcecheck for resource %s %s", manager.Keyword(), em.GetId()) - } - } - } - } -} diff --git a/pkg/compute/models/billingresourcecheck.go b/pkg/compute/models/billingresourcecheck.go new file mode 100644 index 0000000000..f7afe7fd5a --- /dev/null +++ b/pkg/compute/models/billingresourcecheck.go @@ -0,0 +1,308 @@ +package models + +import ( + "context" + "fmt" + "reflect" + "time" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/util/billing" + "yunion.io/x/sqlchemy" + + billing_api "yunion.io/x/onecloud/pkg/apis/billing" + api "yunion.io/x/onecloud/pkg/apis/compute" + notifyapi "yunion.io/x/onecloud/pkg/apis/notify" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/notifyclient" + "yunion.io/x/onecloud/pkg/compute/options" + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/mcclient/auth" + "yunion.io/x/onecloud/pkg/mcclient/modules/notify" + "yunion.io/x/onecloud/pkg/util/logclient" +) + +type SBillingResourceCheckManager struct { + db.SVirtualResourceBaseManager +} + +var BillingResourceCheckManager *SBillingResourceCheckManager + +func init() { + BillingResourceCheckManager = &SBillingResourceCheckManager{ + SVirtualResourceBaseManager: db.NewVirtualResourceBaseManager( + SBillingResourceCheck{}, + "billing_resource_checks_tbl", + "billing_resource_check", + "billing_resource_checks", + ), + } + BillingResourceCheckManager.SetVirtualObject(BillingResourceCheckManager) +} + +type SBillingResourceCheck struct { + db.SVirtualResourceBase + SBillingResourceBase + + ResourceType string `width:"36" charset:"ascii" list:"user"` +} + +type IBillingModelManager interface { + db.IModelManager + GetExpiredModels(advanceDay int) ([]IBillingModel, error) +} + +type IBillingModel interface { + db.IModel + GetOwnerId() mcclient.IIdentityProvider + GetStatus() string + GetExpiredAt() time.Time + GetReleaseAt() time.Time + GetAutoRenew() bool + SetReleaseAt(releaseAt time.Time) + SetExpiredAt(expireAt time.Time) + SetBillingCycle(billingCycle string) + SetBillingType(billingType string) + GetBillingType() string +} + +// 即将到期释放资源列表 +func (manager *SBillingResourceCheckManager) ListItemFilter( + ctx context.Context, + q *sqlchemy.SQuery, + userCred mcclient.TokenCredential, + query api.BillingResourceCheckListInput, +) (*sqlchemy.SQuery, error) { + var err error + q, err = manager.SVirtualResourceBaseManager.ListItemFilter(ctx, q, userCred, query.VirtualResourceListInput) + if err != nil { + return nil, errors.Wrap(err, "SVirtualResourceBaseManager.ListItemFilter") + } + if len(query.ResourceType) > 0 { + q = q.In("resource_type", query.ResourceType) + } + return q, nil +} + +func SaveReleaseAt(ctx context.Context, model IBillingModel, userCred mcclient.TokenCredential, releaseAt time.Time) error { + diff, err := db.Update(model, func() error { + model.SetReleaseAt(releaseAt) + return nil + }) + if err != nil { + return errors.Wrap(err, "Update") + } + if len(diff) > 0 { + db.OpsLog.LogEvent(model, db.ACT_SET_RELEASE_TIME, fmt.Sprintf("release at: %s", releaseAt), userCred) + } + if len(diff) > 0 && userCred != nil { + logclient.AddActionLogWithContext(ctx, model, logclient.ACT_SET_RELEASE_TIME, diff, userCred, true) + } + return nil +} + +func SaveRenewInfo( + ctx context.Context, userCred mcclient.TokenCredential, + model IBillingModel, bc *billing.SBillingCycle, expireAt *time.Time, billingType string, +) error { + _, err := db.Update(model, func() error { + if billingType == "" { + billingType = billing_api.BILLING_TYPE_PREPAID + } + if model.GetBillingType() == "" { + model.SetBillingType(billingType) + } + if expireAt != nil && !expireAt.IsZero() { + model.SetExpiredAt(*expireAt) + } else if bc != nil { + model.SetBillingCycle(bc.String()) + model.SetExpiredAt(bc.EndAt(model.GetExpiredAt())) + } + return nil + }) + if err != nil { + log.Errorf("UpdateItem error %s", err) + return err + } + db.OpsLog.LogEvent(model, db.ACT_RENEW, model.GetShortDesc(ctx), userCred) + return nil +} + +func (manager *SBillingResourceCheckManager) GetExpiredModels(advanceDay int) ([]SBillingResourceCheck, error) { + upLimit := time.Now().AddDate(0, 0, advanceDay+1) + downLimit := time.Now().AddDate(0, 0, advanceDay) + q := manager.Query() + q = q.Filter( + sqlchemy.OR( + sqlchemy.AND( + sqlchemy.Equals(q.Field("billing_type"), billing_api.BILLING_TYPE_POSTPAID), + sqlchemy.LE(q.Field("release_at"), upLimit), + sqlchemy.GE(q.Field("release_at"), downLimit), + ), + // 跳过自动续费实例 + sqlchemy.AND( + sqlchemy.Equals(q.Field("billing_type"), billing_api.BILLING_TYPE_PREPAID), + sqlchemy.LE(q.Field("expired_at"), upLimit), + sqlchemy.GE(q.Field("expired_at"), downLimit), + sqlchemy.IsFalse(q.Field("auto_renew")), + ))) + + ret := []SBillingResourceCheck{} + err := db.FetchModelObjects(manager, q, &ret) + if err != nil { + return nil, errors.Wrap(err, "FetchModelObjects") + } + return ret, nil +} + +func fetchExpiredModels(manager db.IModelManager, advanceDay int) ([]IBillingModel, error) { + upLimit := time.Now().AddDate(0, 0, advanceDay+1) + downLimit := time.Now() + v := reflect.MakeSlice(reflect.SliceOf(manager.TableSpec().DataType()), 0, 0) + q := manager.Query() + q = q.Filter( + sqlchemy.OR( + sqlchemy.AND( + sqlchemy.Equals(q.Field("billing_type"), billing_api.BILLING_TYPE_POSTPAID), + sqlchemy.LE(q.Field("release_at"), upLimit), + sqlchemy.GE(q.Field("release_at"), downLimit), + ), + sqlchemy.AND( + sqlchemy.Equals(q.Field("billing_type"), billing_api.BILLING_TYPE_PREPAID), + sqlchemy.LE(q.Field("expired_at"), upLimit), + sqlchemy.GE(q.Field("expired_at"), downLimit), + ))) + + vp := reflect.New(v.Type()) + vp.Elem().Set(v) + err := db.FetchModelObjects(manager, q, vp.Interface()) + if err != nil { + return nil, errors.Wrapf(err, "unable to list %s", manager.KeywordPlural()) + } + + v = vp.Elem() + if v.Len() > 0 { + log.Debugf("%s length of v: %d will be notified", manager.Alias(), v.Len()) + } + + ms := make([]IBillingModel, v.Len()) + for i := range ms { + ms[i] = v.Index(i).Addr().Interface().(IBillingModel) + } + return ms, nil +} + +func (bm *SBillingResourceCheckManager) Create(ctx context.Context, res IBillingModel, resourceType string) error { + bc := &SBillingResourceCheck{ + ResourceType: resourceType, + } + bc.Id = res.GetId() + bc.Name = res.GetName() + bc.ExpiredAt = res.GetExpiredAt() + bc.ReleaseAt = res.GetReleaseAt() + bc.AutoRenew = res.GetAutoRenew() + bc.BillingType = res.GetBillingType() + if owner := res.GetOwnerId(); owner != nil { + bc.ProjectId = owner.GetProjectId() + bc.DomainId = owner.GetDomainId() + } + bc.Status = res.GetStatus() + bc.SetModelManager(bm, bc) + return bm.TableSpec().InsertOrUpdate(ctx, bc) +} + +func (man *SBillingResourceCheckManager) PerformCheck(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input jsonutils.JSONObject) (jsonutils.JSONObject, error) { + scanExpiredBillingResources(ctx) + return jsonutils.NewDict(), nil +} + +func (man *SBillingResourceCheckManager) clean(resType string, resourceIds []string) error { + ids, err := db.FetchField(man, "id", func(q *sqlchemy.SQuery) *sqlchemy.SQuery { + return q.Equals("resource_type", resType).NotIn("id", resourceIds) + }) + if err != nil { + return errors.Wrap(err, "fetchField") + } + if len(ids) == 0 { + return nil + } + return db.Purge(man, "id", ids, true) +} + +func scanExpiredBillingResources(ctx context.Context) { + billingResourceManagers := []IBillingModelManager{ + GuestManager, + DBInstanceManager, + ElasticcacheManager, + } + + for _, manager := range billingResourceManagers { + expiredModels, err := manager.GetExpiredModels(30) + if err != nil { + log.Errorf("unable to fetchExpiredModels: %v", err) + continue + } + + resourceIds := []string{} + for _, model := range expiredModels { + err = BillingResourceCheckManager.Create(ctx, model, manager.Keyword()) + if err != nil { + log.Errorf("unable to create billing_resource_check for resource %s %s", manager.Keyword(), model.GetId()) + continue + } + resourceIds = append(resourceIds, model.GetId()) + } + + err = BillingResourceCheckManager.clean(manager.Keyword(), resourceIds) + if err != nil { + log.Errorf("unable to clean billing_resource_check for resource %s", manager.Keyword()) + continue + } + } +} + +func CheckBillingResourceExpireAt(ctx context.Context, userCred mcclient.TokenCredential, isStart bool) { + scanExpiredBillingResources(ctx) + + s := auth.GetAdminSession(ctx, options.Options.Region) + resp, err := notify.NotifyTopic.List(s, jsonutils.Marshal(map[string]interface{}{ + "filter": fmt.Sprintf("name.equals('%s')", notifyapi.DefaultResourceRelease), + "scope": "system", + })) + if err != nil { + log.Errorln(errors.Wrap(err, "list topics")) + return + } + topics := []notifyapi.TopicDetails{} + err = jsonutils.Update(&topics, resp.Data) + if err != nil { + log.Errorln(errors.Wrap(err, "update topic")) + return + } + if len(topics) != 1 { + log.Errorln(errors.Wrapf(errors.ErrNotSupported, "len topics :%d", len(topics))) + return + } + + for _, advanceDay := range topics[0].AdvanceDays { + resources, err := BillingResourceCheckManager.GetExpiredModels(advanceDay) + if err != nil { + log.Errorf("unable to fetchExpiredModels: %s", err.Error()) + continue + } + + for i := range resources { + detailsDecro := func(ctx context.Context, details *jsonutils.JSONDict) { + details.Set("advance_days", jsonutils.NewInt(int64(advanceDay))) + } + notifyclient.EventNotify(ctx, userCred, notifyclient.SEventNotifyParam{ + Obj: &resources[i], + ObjDetailsDecorator: detailsDecro, + Action: notifyclient.ActionExpiredRelease, + AdvanceDays: advanceDay, + }) + } + } +} diff --git a/pkg/compute/models/snapshotpolicy.go b/pkg/compute/models/snapshotpolicy.go index c46d1fd05e..bae7253374 100644 --- a/pkg/compute/models/snapshotpolicy.go +++ b/pkg/compute/models/snapshotpolicy.go @@ -207,6 +207,7 @@ func (manager *SSnapshotPolicyManager) FetchCustomizeColumns( policyIds[i] = policy.Id } + diskIds := []string{} q := SnapshotPolicyResourceManager.Query().In("snapshotpolicy_id", policyIds) sprs := []SSnapshotPolicyResource{} err := q.All(&sprs) @@ -220,11 +221,43 @@ func (manager *SSnapshotPolicyManager) FetchCustomizeColumns( if !ok { sprmap[sp.SnapshotpolicyId] = []SSnapshotPolicyResource{} } + if sp.ResourceType == api.SNAPSHOT_POLICY_TYPE_DISK { + diskIds = append(diskIds, sp.ResourceId) + } sprmap[sp.SnapshotpolicyId] = append(sprmap[sp.SnapshotpolicyId], sp) } + + sq := SnapshotManager.Query().In("disk_id", diskIds).SubQuery() + q = sq.Query( + sq.Field("disk_id"), + sqlchemy.COUNT("count", sq.Field("id")), + ).GroupBy(sq.Field("disk_id")) + + snapshotCounts := []struct { + DiskId string + Count int + }{} + err = q.All(&snapshotCounts) + if err != nil { + log.Errorf("query snapshot counts error: %v", err) + return rows + } + snapshotCountMap := map[string]int{} + for _, snapshotCount := range snapshotCounts { + snapshotCountMap[snapshotCount.DiskId] = snapshotCount.Count + } + for i := range rows { resources := sprmap[policyIds[i]] rows[i].BindingResourceCount = len(resources) + for _, resource := range resources { + if resource.ResourceType == api.SNAPSHOT_POLICY_TYPE_DISK { + cnt, ok := snapshotCountMap[resource.ResourceId] + if ok { + rows[i].SnapshotCount += cnt + } + } + } sp := objs[i].(*SSnapshotPolicy) if sp.Type == api.SNAPSHOT_POLICY_TYPE_DISK { rows[i].BindingDiskCount = len(resources) diff --git a/pkg/compute/service/handlers.go b/pkg/compute/service/handlers.go index 0566d44a8c..67810c248f 100644 --- a/pkg/compute/service/handlers.go +++ b/pkg/compute/service/handlers.go @@ -99,7 +99,6 @@ func InitHandlers(app *appsrv.Application) { models.CloudimageManager, models.WafRuleStatementManager, - models.BillingResourceCheckManager, models.LoadbalancerSecurityGroupManager, } { @@ -160,6 +159,7 @@ func InitHandlers(app *appsrv.Application) { models.RouteTableRouteSetManager, models.InterVpcNetworkRouteSetManager, models.GuestScreenDumpManager, + models.BillingResourceCheckManager, models.SchedpolicyManager, models.DynamicschedtagManager, diff --git a/pkg/mcclient/modules/compute/mod_billing_resource_checks.go b/pkg/mcclient/modules/compute/mod_billing_resource_checks.go new file mode 100644 index 0000000000..392eabaf35 --- /dev/null +++ b/pkg/mcclient/modules/compute/mod_billing_resource_checks.go @@ -0,0 +1,36 @@ +// 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 compute + +import ( + "yunion.io/x/onecloud/pkg/mcclient/modulebase" + "yunion.io/x/onecloud/pkg/mcclient/modules" +) + +type BillingResourceChecksManager struct { + modulebase.ResourceManager +} + +var ( + BillingResourceChecks BillingResourceChecksManager +) + +func init() { + BillingResourceChecks = BillingResourceChecksManager{modules.NewComputeManager("billing_resource_check", "billing_resource_checks", + []string{}, + []string{})} + + modules.RegisterCompute(&BillingResourceChecks) +} diff --git a/pkg/mcclient/options/compute/billing_resource_checks.go b/pkg/mcclient/options/compute/billing_resource_checks.go new file mode 100644 index 0000000000..b4b4488dab --- /dev/null +++ b/pkg/mcclient/options/compute/billing_resource_checks.go @@ -0,0 +1,41 @@ +// 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 compute + +import ( + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/mcclient/options" +) + +type BillingResourceCheckListOptions struct { + options.BaseListOptions + ResourceType []string `json:"resource_type"` +} + +func (opts *BillingResourceCheckListOptions) Params() (jsonutils.JSONObject, error) { + params, err := options.ListStructToParams(opts) + if err != nil { + return nil, err + } + return params, nil +} + +type BillingResourceCheckCheckOptions struct { +} + +func (opts *BillingResourceCheckCheckOptions) Params() (jsonutils.JSONObject, error) { + return nil, nil +}