mirror of
https://github.com/yunionio/cloudpods.git
synced 2026-09-19 10:46:58 +08:00
fix(region): add billing resource check (#24008)
This commit is contained in:
@@ -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))
|
||||
}
|
||||
@@ -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"`
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -209,6 +209,7 @@ func (manager *SSnapshotPolicyManager) FetchCustomizeColumns(
|
||||
policyIds[i] = policy.Id
|
||||
}
|
||||
|
||||
diskIds := []string{}
|
||||
q := SnapshotPolicyResourceManager.Query().In("snapshotpolicy_id", policyIds)
|
||||
sprs := []SSnapshotPolicyResource{}
|
||||
err := q.All(&sprs)
|
||||
@@ -222,11 +223,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)
|
||||
|
||||
@@ -102,7 +102,6 @@ func InitHandlers(app *appsrv.Application) {
|
||||
models.CloudimageManager,
|
||||
|
||||
models.WafRuleStatementManager,
|
||||
models.BillingResourceCheckManager,
|
||||
|
||||
models.LoadbalancerSecurityGroupManager,
|
||||
|
||||
@@ -167,6 +166,7 @@ func InitHandlers(app *appsrv.Application) {
|
||||
models.RouteTableRouteSetManager,
|
||||
models.InterVpcNetworkRouteSetManager,
|
||||
models.GuestScreenDumpManager,
|
||||
models.BillingResourceCheckManager,
|
||||
|
||||
models.SchedpolicyManager,
|
||||
models.DynamicschedtagManager,
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user