mirror of
https://github.com/yunionio/cloudpods.git
synced 2026-05-31 13:00:41 +08:00
feature:
- postpaid server support expire - extend distinct tenant filed - gpu batch perform action
This commit is contained in:
@@ -20,6 +20,7 @@ import (
|
||||
"yunion.io/x/jsonutils"
|
||||
"yunion.io/x/sqlchemy"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/httperrors"
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
"yunion.io/x/onecloud/pkg/util/rbacutils"
|
||||
)
|
||||
@@ -62,3 +63,16 @@ func (manager *SProjectizedResourceBaseManager) ResourceScope() rbacutils.TRbacS
|
||||
func (manager *SProjectizedResourceBaseManager) FetchOwnerId(ctx context.Context, data jsonutils.JSONObject) (mcclient.IIdentityProvider, error) {
|
||||
return FetchProjectInfo(ctx, data)
|
||||
}
|
||||
|
||||
func (manager *SProjectizedResourceBaseManager) QueryDistinctExtraField(q *sqlchemy.SQuery, field string) (*sqlchemy.SQuery, error) {
|
||||
switch field {
|
||||
case "tenant":
|
||||
tenantCacheQuery := TenantCacheManager.Query("name", "id").Distinct().SubQuery()
|
||||
q.AppendField(tenantCacheQuery.Field("name", "tenant"))
|
||||
q = q.Join(tenantCacheQuery, sqlchemy.Equals(q.Field("tenant_id"), tenantCacheQuery.Field("id")))
|
||||
q.GroupBy(tenantCacheQuery.Field("name"))
|
||||
default:
|
||||
return nil, httperrors.NewBadRequestError("unsupport field %s", field)
|
||||
}
|
||||
return q, nil
|
||||
}
|
||||
|
||||
@@ -313,3 +313,9 @@ func (self *SBaseGuestDriver) GetGuestSecgroupVpcid(guest *models.SGuest) (strin
|
||||
}
|
||||
return vpcId, nil
|
||||
}
|
||||
|
||||
func (self *SBaseGuestDriver) CancelExpireTime(
|
||||
ctx context.Context, userCred mcclient.TokenCredential, guest *models.SGuest) error {
|
||||
|
||||
return httperrors.NewBadRequestError("unsupport cancel expire time")
|
||||
}
|
||||
|
||||
@@ -225,3 +225,8 @@ func (self *SESXiGuestDriver) RequestRenewInstance(guest *models.SGuest, bc bill
|
||||
func (self *SESXiGuestDriver) IsSupportEip() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (self *SESXiGuestDriver) CancelExpireTime(
|
||||
ctx context.Context, userCred mcclient.TokenCredential, guest *models.SGuest) error {
|
||||
return guest.CancelExpireTime(ctx, userCred)
|
||||
}
|
||||
|
||||
@@ -459,3 +459,8 @@ func (self *SKVMGuestDriver) OnGuestChangeCpuMemFailed(ctx context.Context, gues
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *SKVMGuestDriver) CancelExpireTime(
|
||||
ctx context.Context, userCred mcclient.TokenCredential, guest *models.SGuest) error {
|
||||
return guest.CancelExpireTime(ctx, userCred)
|
||||
}
|
||||
|
||||
@@ -157,3 +157,8 @@ func (self *SOpenStackGuestDriver) AllowReconfigGuest() bool {
|
||||
func (self *SOpenStackGuestDriver) IsSupportedBillingCycle(bc billing.SBillingCycle) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (self *SOpenStackGuestDriver) CancelExpireTime(
|
||||
ctx context.Context, userCred mcclient.TokenCredential, guest *models.SGuest) error {
|
||||
return guest.CancelExpireTime(ctx, userCred)
|
||||
}
|
||||
|
||||
@@ -164,3 +164,8 @@ func (self *SZStackGuestDriver) AllowReconfigGuest() bool {
|
||||
func (self *SZStackGuestDriver) IsSupportedBillingCycle(bc billing.SBillingCycle) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (self *SZStackGuestDriver) CancelExpireTime(
|
||||
ctx context.Context, userCred mcclient.TokenCredential, guest *models.SGuest) error {
|
||||
return guest.CancelExpireTime(ctx, userCred)
|
||||
}
|
||||
|
||||
@@ -54,6 +54,16 @@ func (self *SBillingResourceBase) IsValidPrePaid() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (self *SBillingResourceBase) IsValidPostPaid() bool {
|
||||
if self.BillingType == api.BILLING_TYPE_POSTPAID {
|
||||
now := time.Now().UTC()
|
||||
if self.ExpiredAt.After(now) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type SBillingBaseInfo struct {
|
||||
ChargeType string `json:",omitempty"`
|
||||
ExpiredAt time.Time `json:",omitempty"`
|
||||
|
||||
@@ -556,11 +556,6 @@ func (manager *SBucketManager) ListItemFilter(ctx context.Context, q *sqlchemy.S
|
||||
|
||||
func (manager *SBucketManager) QueryDistinctExtraField(q *sqlchemy.SQuery, field string) (*sqlchemy.SQuery, error) {
|
||||
switch field {
|
||||
case "tenant":
|
||||
tenantCacheQuery := db.TenantCacheManager.Query("name", "id").Distinct().SubQuery()
|
||||
q.AppendField(tenantCacheQuery.Field("name", "tenant"))
|
||||
q = q.Join(tenantCacheQuery, sqlchemy.Equals(q.Field("tenant_id"), tenantCacheQuery.Field("id")))
|
||||
q.GroupBy(tenantCacheQuery.Field("name"))
|
||||
case "account":
|
||||
cloudproviders := CloudproviderManager.Query().SubQuery()
|
||||
cloudaccounts := CloudaccountManager.Query("name", "id").Distinct().SubQuery()
|
||||
|
||||
@@ -1501,18 +1501,23 @@ func (self *SGuest) PerformDetachIsolatedDevice(ctx context.Context, userCred mc
|
||||
logclient.AddActionLogWithContext(ctx, self, logclient.ACT_GUEST_DETACH_ISOLATED_DEVICE, msg, userCred, false)
|
||||
return nil, httperrors.NewBadRequestError(msg)
|
||||
}
|
||||
err = self.startDetachIsolateDevice(ctx, userCred, device)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func (self *SGuest) startDetachIsolateDevice(ctx context.Context, userCred mcclient.TokenCredential, device string) error {
|
||||
iDev, err := IsolatedDeviceManager.FetchByIdOrName(userCred, device)
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("Isolated device %s not found", device)
|
||||
logclient.AddActionLogWithContext(ctx, self, logclient.ACT_GUEST_DETACH_ISOLATED_DEVICE, msg, userCred, false)
|
||||
return nil, httperrors.NewBadRequestError(msg)
|
||||
return httperrors.NewBadRequestError(msg)
|
||||
}
|
||||
dev := iDev.(*SIsolatedDevice)
|
||||
host := self.GetHost()
|
||||
lockman.LockObject(ctx, host)
|
||||
defer lockman.ReleaseObject(ctx, host)
|
||||
err = self.detachIsolateDevice(ctx, userCred, dev)
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
|
||||
func (self *SGuest) detachIsolateDevice(ctx context.Context, userCred mcclient.TokenCredential, dev *SIsolatedDevice) error {
|
||||
@@ -1551,11 +1556,16 @@ func (self *SGuest) PerformAttachIsolatedDevice(ctx context.Context, userCred mc
|
||||
logclient.AddActionLogWithContext(ctx, self, logclient.ACT_GUEST_ATTACH_ISOLATED_DEVICE, msg, userCred, false)
|
||||
return nil, httperrors.NewBadRequestError(msg)
|
||||
}
|
||||
err = self.startAttachIsolatedDevice(ctx, userCred, device)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func (self *SGuest) startAttachIsolatedDevice(ctx context.Context, userCred mcclient.TokenCredential, device string) error {
|
||||
iDev, err := IsolatedDeviceManager.FetchByIdOrName(userCred, device)
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("Isolated device %s not found", device)
|
||||
logclient.AddActionLogWithContext(ctx, self, logclient.ACT_GUEST_ATTACH_ISOLATED_DEVICE, msg, userCred, false)
|
||||
return nil, httperrors.NewBadRequestError(msg)
|
||||
return httperrors.NewBadRequestError(msg)
|
||||
}
|
||||
dev := iDev.(*SIsolatedDevice)
|
||||
host := self.GetHost()
|
||||
@@ -1567,7 +1577,78 @@ func (self *SGuest) PerformAttachIsolatedDevice(ctx context.Context, userCred mc
|
||||
msg = err.Error()
|
||||
}
|
||||
logclient.AddActionLogWithContext(ctx, self, logclient.ACT_GUEST_ATTACH_ISOLATED_DEVICE, msg, userCred, err == nil)
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
|
||||
func (self *SGuest) attachIsolatedDevice(ctx context.Context, userCred mcclient.TokenCredential, dev *SIsolatedDevice) error {
|
||||
if len(dev.GuestId) > 0 {
|
||||
return fmt.Errorf("Isolated device already attached to another guest: %s", dev.GuestId)
|
||||
}
|
||||
if dev.HostId != self.HostId {
|
||||
return fmt.Errorf("Isolated device and guest are not located in the same host")
|
||||
}
|
||||
_, err := db.Update(dev, func() error {
|
||||
dev.GuestId = self.Id
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
db.OpsLog.LogEvent(self, db.ACT_GUEST_ATTACH_ISOLATED_DEVICE, dev.GetShortDesc(ctx), userCred)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *SGuest) AllowPerformSetIsolatedDevice(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool {
|
||||
return self.IsOwner(userCred) || db.IsAdminAllowPerform(userCred, self, "set-isolated-device")
|
||||
}
|
||||
|
||||
func (self *SGuest) PerformSetIsolatedDevice(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
|
||||
if self.Hypervisor != api.HYPERVISOR_KVM {
|
||||
return nil, httperrors.NewNotAcceptableError("Not allow for hypervisor %s", self.Hypervisor)
|
||||
}
|
||||
if self.Status != api.VM_READY {
|
||||
return nil, httperrors.NewInvalidStatusError("Only allowed to attach isolated device when guest is ready")
|
||||
}
|
||||
var addDevs []string
|
||||
{
|
||||
addDevices, err := data.Get("add_devices")
|
||||
if err == nil {
|
||||
arrAddDev, ok := addDevices.(*jsonutils.JSONArray)
|
||||
if ok {
|
||||
addDevs = arrAddDev.GetStringArray()
|
||||
} else {
|
||||
return nil, httperrors.NewInputParameterError("attach devices is not string array")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var delDevs []string
|
||||
{
|
||||
delDevices, err := data.Get("del_devices")
|
||||
if err == nil {
|
||||
arrDelDev, ok := delDevices.(*jsonutils.JSONArray)
|
||||
if ok {
|
||||
delDevs = arrDelDev.GetStringArray()
|
||||
} else {
|
||||
return nil, httperrors.NewInputParameterError("detach devices is not string array")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// detach first
|
||||
for i := 0; i < len(delDevs); i++ {
|
||||
err := self.startDetachIsolateDevice(ctx, userCred, delDevs[i])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
for i := 0; i < len(addDevs); i++ {
|
||||
err := self.startAttachIsolatedDevice(ctx, userCred, addDevs[i])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (self *SGuest) AllowPerformChangeIpaddr(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool {
|
||||
@@ -2973,6 +3054,18 @@ func (self *SGuest) PerformDelExtraOption(ctx context.Context, userCred mcclient
|
||||
return nil, self.SetExtraOptions(ctx, userCred, extraOptions)
|
||||
}
|
||||
|
||||
func (self *SGuest) AllowPerformCancelExpire(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool {
|
||||
return self.IsOwner(userCred) || db.IsAdminAllowPerform(userCred, self, "cancel-expire")
|
||||
}
|
||||
|
||||
func (self *SGuest) PerformCancelExpire(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
|
||||
if self.BillingType != billing_api.BILLING_TYPE_POSTPAID {
|
||||
return nil, httperrors.NewBadRequestError("guest billing type %s not support cancel expire", self.BillingType)
|
||||
}
|
||||
err := self.GetDriver().CancelExpireTime(ctx, userCred, self)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func (self *SGuest) AllowPerformRenew(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool {
|
||||
return db.IsAdminAllowPerform(userCred, self, "renew")
|
||||
}
|
||||
@@ -3021,11 +3114,9 @@ func (self *SGuest) SaveRenewInfo(ctx context.Context, userCred mcclient.TokenCr
|
||||
guestdisks := self.GetDisks()
|
||||
for i := 0; i < len(guestdisks); i += 1 {
|
||||
disk := guestdisks[i].GetDisk()
|
||||
if disk.BillingType == billing_api.BILLING_TYPE_PREPAID {
|
||||
err = disk.SaveRenewInfo(ctx, userCred, bc, expireAt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = disk.SaveRenewInfo(ctx, userCred, bc, expireAt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@@ -3033,7 +3124,7 @@ func (self *SGuest) SaveRenewInfo(ctx context.Context, userCred mcclient.TokenCr
|
||||
|
||||
func (self *SGuest) doSaveRenewInfo(ctx context.Context, userCred mcclient.TokenCredential, bc *billing.SBillingCycle, expireAt *time.Time) error {
|
||||
_, err := db.Update(self, func() error {
|
||||
if self.BillingType != billing_api.BILLING_TYPE_PREPAID {
|
||||
if len(self.BillingType) == 0 {
|
||||
self.BillingType = billing_api.BILLING_TYPE_PREPAID
|
||||
}
|
||||
if expireAt != nil && !expireAt.IsZero() {
|
||||
@@ -3052,6 +3143,23 @@ func (self *SGuest) doSaveRenewInfo(ctx context.Context, userCred mcclient.Token
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *SGuest) CancelExpireTime(ctx context.Context, userCred mcclient.TokenCredential) error {
|
||||
if self.BillingType != billing_api.BILLING_TYPE_POSTPAID {
|
||||
return fmt.Errorf("billing type %s not support cancel expire", self.BillingType)
|
||||
}
|
||||
_, err := sqlchemy.GetDB().Exec(
|
||||
fmt.Sprintf(
|
||||
"update %s set expired_at = NULL and billing_cycle = NULL where id = ?",
|
||||
GuestManager.TableSpec().Name(),
|
||||
), self.Id,
|
||||
)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "guest cancel expire time")
|
||||
}
|
||||
db.OpsLog.LogEvent(self, db.ACT_RENEW, "guest cancel expire time", userCred)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *SGuest) AllowPerformStreamDisksComplete(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool {
|
||||
return db.IsAdminAllowPerform(userCred, self, "stream-disks-complete")
|
||||
}
|
||||
|
||||
@@ -170,6 +170,7 @@ type IGuestDriver interface {
|
||||
|
||||
IsNeedInjectPasswordByCloudInit(desc *cloudprovider.SManagedVMCreateConfig) bool
|
||||
GetUserDataType() string
|
||||
CancelExpireTime(ctx context.Context, userCred mcclient.TokenCredential, guest *SGuest) error
|
||||
}
|
||||
|
||||
var guestDrivers map[string]IGuestDriver
|
||||
|
||||
@@ -1073,7 +1073,9 @@ func (manager *SGuestManager) validateCreateData(
|
||||
return nil, httperrors.NewInputParameterError("unsupported duration %s", input.Duration)
|
||||
}
|
||||
|
||||
input.BillingType = billing_api.BILLING_TYPE_PREPAID
|
||||
if len(input.BillingType) == 0 {
|
||||
input.BillingType = billing_api.BILLING_TYPE_PREPAID
|
||||
}
|
||||
input.BillingCycle = billingCycle.String()
|
||||
// expired_at will be set later by callback
|
||||
// data.Add(jsonutils.NewTimeString(billingCycle.EndAt(time.Time{})), "expired_at")
|
||||
@@ -3039,24 +3041,6 @@ func (self *SGuest) createIsolatedDeviceOnHost(ctx context.Context, userCred mcc
|
||||
return err
|
||||
}
|
||||
|
||||
func (self *SGuest) attachIsolatedDevice(ctx context.Context, userCred mcclient.TokenCredential, dev *SIsolatedDevice) error {
|
||||
if len(dev.GuestId) > 0 {
|
||||
return fmt.Errorf("Isolated device already attached to another guest: %s", dev.GuestId)
|
||||
}
|
||||
if dev.HostId != self.HostId {
|
||||
return fmt.Errorf("Isolated device and guest are not located in the same host")
|
||||
}
|
||||
_, err := db.Update(dev, func() error {
|
||||
dev.GuestId = self.Id
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
db.OpsLog.LogEvent(self, db.ACT_GUEST_ATTACH_ISOLATED_DEVICE, dev.GetShortDesc(ctx), userCred)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *SGuest) JoinGroups(ctx context.Context, userCred mcclient.TokenCredential, groupIds []string) error {
|
||||
for _, id := range groupIds {
|
||||
_, err := GroupguestManager.Attach(ctx, id, self.Id)
|
||||
@@ -4025,6 +4009,20 @@ func (manager *SGuestManager) getExpiredPrepaidGuests() []SGuest {
|
||||
return guests
|
||||
}
|
||||
|
||||
func (manager *SGuestManager) getExpiredPostpaidGuests() []SGuest {
|
||||
deadline := time.Now()
|
||||
q := manager.Query().Equals("billing_type", billing_api.BILLING_TYPE_POSTPAID).
|
||||
LT("expired_at", deadline).Limit(options.Options.ExpiredPrepaidMaxCleanBatchSize)
|
||||
guests := make([]SGuest, 0)
|
||||
err := db.FetchModelObjects(GuestManager, q, &guests)
|
||||
if err != nil {
|
||||
log.Errorf("fetch guests error %s", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
return guests
|
||||
}
|
||||
|
||||
func (self *SGuest) doExternalSync(ctx context.Context, userCred mcclient.TokenCredential) error {
|
||||
host := self.GetHost()
|
||||
if host == nil {
|
||||
@@ -4059,6 +4057,24 @@ func (manager *SGuestManager) DeleteExpiredPrepaidServers(ctx context.Context, u
|
||||
}
|
||||
}
|
||||
|
||||
func (manager *SGuestManager) DeleteExpiredPostpaidServers(ctx context.Context, userCred mcclient.TokenCredential, isStart bool) {
|
||||
guests := manager.getExpiredPostpaidGuests()
|
||||
if len(guests) == 0 {
|
||||
log.Infof("No expired postpaid guest")
|
||||
return
|
||||
}
|
||||
for i := 0; i < len(guests); i++ {
|
||||
if len(guests[i].ExternalId) > 0 {
|
||||
err := guests[i].doExternalSync(ctx, userCred)
|
||||
if err == nil && guests[i].IsValidPostPaid() {
|
||||
continue
|
||||
}
|
||||
}
|
||||
guests[i].SetDisableDelete(userCred, false)
|
||||
guests[i].StartDeleteGuestTask(ctx, userCred, "", false, false)
|
||||
}
|
||||
}
|
||||
|
||||
func (self *SGuest) GetEip() (*SElasticip, error) {
|
||||
return ElasticipManager.getEipForInstance("server", self.Id)
|
||||
}
|
||||
|
||||
@@ -95,19 +95,6 @@ func (manager *SSecurityGroupManager) ListItemFilter(ctx context.Context, q *sql
|
||||
return q, nil
|
||||
}
|
||||
|
||||
func (manager *SSecurityGroupManager) QueryDistinctExtraField(q *sqlchemy.SQuery, field string) (*sqlchemy.SQuery, error) {
|
||||
switch field {
|
||||
case "tenant":
|
||||
tenantCacheQuery := db.TenantCacheManager.Query("name", "id").Distinct().SubQuery()
|
||||
q.AppendField(tenantCacheQuery.Field("name", "tenant"))
|
||||
q = q.Join(tenantCacheQuery, sqlchemy.Equals(q.Field("tenant_id"), tenantCacheQuery.Field("id")))
|
||||
q.GroupBy(tenantCacheQuery.Field("name"))
|
||||
default:
|
||||
return nil, httperrors.NewBadRequestError("unsupport field %s", field)
|
||||
}
|
||||
return q, nil
|
||||
}
|
||||
|
||||
func (manager *SSecurityGroupManager) OrderByExtraFields(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*sqlchemy.SQuery, error) {
|
||||
q, err := manager.SVirtualResourceBaseManager.OrderByExtraFields(ctx, q, userCred, query)
|
||||
if err != nil {
|
||||
|
||||
@@ -78,6 +78,7 @@ func StartService() {
|
||||
if opts.PrepaidExpireCheck {
|
||||
cron.AddJobAtIntervals("CleanExpiredPrepaidServers", time.Duration(opts.PrepaidExpireCheckSeconds)*time.Second, models.GuestManager.DeleteExpiredPrepaidServers)
|
||||
}
|
||||
cron.AddJobAtIntervals("CleanExpiredPostpaidServers", time.Duration(opts.PrepaidExpireCheckSeconds)*time.Second, models.GuestManager.DeleteExpiredPostpaidServers)
|
||||
cron.AddJobAtIntervals("StartHostPingDetectionTask", time.Duration(opts.HostOfflineDetectionInterval)*time.Second, models.HostManager.PingDetectionTask)
|
||||
|
||||
cron.AddJobAtIntervalsWithStartRun("CalculateQuotaUsages", time.Duration(opts.CalculateQuotaUsageIntervalSeconds)*time.Second, models.QuotaManager.CalculateQuotaUsages, true)
|
||||
|
||||
Reference in New Issue
Block a user