Merge pull request #886 in YUNIONIO/onecloud from ~QIUJIAN/onecloud:feature/qj-vipkid-prepaid-features to release/2.5.0

* commit 'ffe6d3910d8dccbab848fb1c08f81a8c0521ee65':
  增加climc zone-update-status命令
  server-create创建prepaid主机,增加auto_prepaid_recycle参数,创建后自动加入包年包月资源池
  minor fixes
  minor changes: 防止float类型的column设置default后每次都要更新
  增加:1. 包年包月主机加入资源池后,如果auto_delete=true,则自动删除该主机 2. 包年包月资源池的宿主机支持续费操作Perform(host.id, "renew-prepaid-recycle", {"duration": "1m"})
This commit is contained in:
邱剑
2019-01-05 20:14:17 +08:00
20 changed files with 231 additions and 25 deletions
+4
View File
@@ -15,6 +15,7 @@ type BaseEventListOptions struct {
Offset int64 `help:"Offset"`
Ascending bool `help:"Ascending order"`
Descending bool `help:"Descending order"`
OrderBy string `help:"order by specific field"`
Action []string `help:"Log action"`
}
@@ -62,6 +63,9 @@ func doEventList(man modules.ResourceManager, s *mcclient.ClientSession, args *E
} else if !args.Ascending && args.Descending {
params.Add(jsonutils.NewString("desc"), "order")
}
if len(args.OrderBy) > 0 {
params.Add(jsonutils.NewString(args.OrderBy), "order_by")
}
if len(args.Action) > 0 {
params.Add(jsonutils.NewStringArray(args.Action), "action")
}
+15
View File
@@ -497,4 +497,19 @@ func init() {
printObject(result)
return nil
})
type PrepaidRecycleHostRenewOptions struct {
ID string `help:"ID or name of server to renew"`
DURATION string `help:"Duration of renew, ADMIN only command"`
}
R(&PrepaidRecycleHostRenewOptions{}, "host-renew-prepaid-recycle", "Renew a prepaid recycle host", func(s *mcclient.ClientSession, args *PrepaidRecycleHostRenewOptions) error {
params := jsonutils.NewDict()
params.Add(jsonutils.NewString(args.DURATION), "duration")
result, err := modules.Hosts.PerformAction(s, args.ID, "renew-prepaid-recycle", params)
if err != nil {
return err
}
printObject(result)
return nil
})
}
+5 -1
View File
@@ -634,10 +634,14 @@ func init() {
})
type ServerPrepaidRecycleOptions struct {
ID string `help:"ID or name of server to recycle"`
ID string `help:"ID or name of server to recycle"`
AutoDelete bool `help:"after joining the pool, remove the server automatically"`
}
R(&ServerPrepaidRecycleOptions{}, "server-enable-recycle", "Put a prepaid server into recycle pool, so that it can be shared", func(s *mcclient.ClientSession, args *ServerPrepaidRecycleOptions) error {
params := jsonutils.NewDict()
if args.AutoDelete {
params.Add(jsonutils.JSONTrue, "auto_delete")
}
result, err := modules.Servers.PerformAction(s, args.ID, "prepaid-recycle", params)
if err != nil {
return err
+17
View File
@@ -126,4 +126,21 @@ func init() {
return nil
})
type ZoneStatusOptions struct {
ID string `help:"ID or name of zone"`
STATUS string `help:"zone status" choices:"enable|disable|soldout"`
REASON string `help:"why update status"`
}
R(&ZoneStatusOptions{}, "zone-update-status", "Update zone status", func(s *mcclient.ClientSession, args *ZoneStatusOptions) error {
params := jsonutils.NewDict()
params.Set("status", jsonutils.NewString(args.STATUS))
params.Set("reason", jsonutils.NewString(args.REASON))
result, err := modules.Zones.PerformAction(s, args.ID, "status", params)
if err != nil {
return err
}
printObject(result)
return nil
})
}
+5 -6
View File
@@ -447,7 +447,6 @@ func ListItems(manager IModelManager, ctx context.Context, userCred mcclient.Tok
limit = maxLimit
}
orderBy := jsonutils.GetQueryStringArray(queryDict, "order_by")
order := sqlchemy.SQL_ORDER_DESC
if len(orderBy) == 0 {
colSpec := manager.TableSpec().ColumnSpec("id")
if err == nil && colSpec != nil && colSpec.IsNumeric() {
@@ -455,11 +454,11 @@ func ListItems(manager IModelManager, ctx context.Context, userCred mcclient.Tok
} else {
orderBy = []string{"created_at"}
}
} else {
orderStr, _ := queryDict.GetString("order")
if orderStr == "asc" {
order = sqlchemy.SQL_ORDER_ASC
}
}
order := sqlchemy.SQL_ORDER_DESC
orderStr, _ := queryDict.GetString("order")
if orderStr == "asc" {
order = sqlchemy.SQL_ORDER_ASC
}
if order == sqlchemy.SQL_ORDER_ASC {
for _, orderByField := range orderBy {
+6
View File
@@ -9,6 +9,12 @@ var (
Action: PolicyActionPerform,
Result: rbacutils.UserAllow,
},
{
Service: "compute",
Resource: "hosts",
Action: PolicyActionList,
Result: rbacutils.UserAllow,
},
{
Service: "compute",
Resource: "zones",
+8
View File
@@ -63,6 +63,14 @@ func (self *SBaseGuestDriver) OnGuestCreateTaskComplete(ctx context.Context, gue
if err == nil && guest.ExpiredAt.IsZero() {
guest.SaveRenewInfo(ctx, task.GetUserCred(), &bc, nil)
}
if jsonutils.QueryBoolean(task.GetParams(), "auto_prepaid_recycle", false) {
err := guest.CanPerformPrepaidRecycle()
if err == nil {
task.SetStageComplete(ctx, nil)
guest.DoPerformPrepaidRecycle(ctx, task.GetUserCred(), true)
return nil
}
}
}
if jsonutils.QueryBoolean(task.GetParams(), "auto_start", false) {
task.SetStage("on_auto_start_guest", nil)
+1 -1
View File
@@ -52,7 +52,7 @@ func getHypervisors(zone *SZone) []string {
q = q.Equals("zone_id", zone.Id)
}
q = q.IsNotEmpty("host_type").IsNotNull("host_type")
q = q.Equals("host_status", HOST_ONLINE)
// q = q.Equals("host_status", HOST_ONLINE)
q = q.IsTrue("enabled")
q = q.Distinct()
rows, err := q.Rows()
+2 -1
View File
@@ -3429,7 +3429,8 @@ func (manager *SGuestManager) DeleteExpiredPrepaidServers(ctx context.Context, u
return
}
for i := 0; i < len(guests); i += 1 {
guests[i].StartDeleteGuestTask(ctx, userCred, "", false, true)
// fake delete expired prepaid servers
guests[i].StartDeleteGuestTask(ctx, userCred, "", false, false)
}
}
+71 -1
View File
@@ -15,10 +15,12 @@ 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/cloudprovider"
"yunion.io/x/onecloud/pkg/compute/baremetal"
"yunion.io/x/onecloud/pkg/httperrors"
"yunion.io/x/onecloud/pkg/mcclient"
"yunion.io/x/onecloud/pkg/util/billing"
"yunion.io/x/onecloud/pkg/util/logclient"
)
@@ -56,7 +58,11 @@ func (self *SGuest) PerformPrepaidRecycle(ctx context.Context, userCred mcclient
return nil, httperrors.NewInvalidStatusError(err.Error())
}
err = self.doPrepaidRecycle(ctx, userCred)
return self.DoPerformPrepaidRecycle(ctx, userCred, jsonutils.QueryBoolean(data, "auto_delete", false))
}
func (self *SGuest) DoPerformPrepaidRecycle(ctx context.Context, userCred mcclient.TokenCredential, autoDelete bool) (jsonutils.JSONObject, error) {
err := self.doPrepaidRecycle(ctx, userCred)
if err != nil {
logclient.AddActionLog(self, logclient.ACT_RECYCLE_PREPAID, self.GetShortDesc(ctx), userCred, false)
return nil, httperrors.NewGeneralError(err)
@@ -65,6 +71,10 @@ func (self *SGuest) PerformPrepaidRecycle(ctx context.Context, userCred mcclient
db.OpsLog.LogEvent(self, db.ACT_RECYCLE_PREPAID, self.GetShortDesc(ctx), userCred)
logclient.AddActionLog(self, logclient.ACT_RECYCLE_PREPAID, self.GetShortDesc(ctx), userCred, true)
if autoDelete {
self.StartDeleteGuestTask(ctx, userCred, "", false, true)
}
return nil, nil
}
@@ -656,3 +666,63 @@ func (manager *SHostManager) GetHostByRealExternalId(eid string) *SHost {
return &host
}
func (self *SHost) AllowPerformRenewPrepaidRecycle(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool {
return db.IsAdminAllowPerform(userCred, self, "renew-prepaid-recycle")
}
func (self *SHost) PerformRenewPrepaidRecycle(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
durationStr := jsonutils.GetAnyString(data, []string{"duration"})
if len(durationStr) == 0 {
return nil, httperrors.NewInputParameterError("missing duration")
}
bc, err := billing.ParseBillingCycle(durationStr)
if err != nil {
return nil, httperrors.NewInputParameterError("invalid duration %s: %s", durationStr, err)
}
if !GetDriver(HOSTTYPE_HYPERVISOR[self.HostType]).IsSupportedBillingCycle(bc) {
return nil, httperrors.NewInputParameterError("unsupported duration %s", durationStr)
}
err = self.startPrepaidRecycleHostRenewTask(ctx, userCred, durationStr, "")
if err != nil {
return nil, err
}
return nil, nil
}
func (self *SHost) startPrepaidRecycleHostRenewTask(ctx context.Context, userCred mcclient.TokenCredential, duration string, parentTaskId string) error {
data := jsonutils.NewDict()
data.Add(jsonutils.NewString(duration), "duration")
task, err := taskman.TaskManager.NewTask(ctx, "PrepaidRecycleHostRenewTask", self, userCred, data, parentTaskId, "", nil)
if err != nil {
log.Errorf("fail to crate GuestRenewTask %s", err)
return err
}
task.ScheduleRun(nil)
return nil
}
func (self *SHost) DoSaveRenewInfo(ctx context.Context, userCred mcclient.TokenCredential, bc *billing.SBillingCycle, expireAt *time.Time) error {
_, err := self.GetModelManager().TableSpec().Update(self, func() error {
if self.BillingType != BILLING_TYPE_PREPAID {
self.BillingType = BILLING_TYPE_PREPAID
}
if expireAt != nil && !expireAt.IsZero() {
self.ExpiredAt = *expireAt
} else {
self.BillingCycle = bc.String()
self.ExpiredAt = bc.EndAt(self.ExpiredAt)
}
return nil
})
if err != nil {
log.Errorf("Update error %s", err)
return err
}
db.OpsLog.LogEvent(self, db.ACT_RENEW, self.GetShortDesc(ctx), userCred)
return nil
}
+4 -4
View File
@@ -138,11 +138,11 @@ type SHost struct {
CpuMhz int `nullable:"true" get:"admin" update:"admin" create:"admin_optional"` // Column(Integer, nullable=True) # cpu MHz
CpuCache int `nullable:"true" get:"admin" update:"admin" create:"admin_optional"` // Column(Integer, nullable=True) # cpu Cache in KB
CpuReserved int8 `nullable:"true" default:"0" list:"admin" update:"admin" create:"admin_optional"` // Column(TINYINT, nullable=True, default=0)
CpuCmtbound float32 `nullable:"true" default:"8.0" list:"admin" update:"admin" create:"admin_optional"` // = Column(Float, nullable=True)
CpuCmtbound float32 `nullable:"true" default:"8" list:"admin" update:"admin" create:"admin_optional"` // = Column(Float, nullable=True)
MemSize int `nullable:"true" list:"admin" update:"admin" create:"admin_optional"` // Column(Integer, nullable=True) # memory size in MB
MemReserved int `nullable:"true" default:"0" list:"admin" update:"admin" create:"admin_optional"` // Column(Integer, nullable=True, default=0) # memory reserved in MB
MemCmtbound float32 `nullable:"true" default:"1.0" list:"admin" update:"admin" create:"admin_optional"` // = Column(Float, nullable=True)
MemSize int `nullable:"true" list:"admin" update:"admin" create:"admin_optional"` // Column(Integer, nullable=True) # memory size in MB
MemReserved int `nullable:"true" default:"0" list:"admin" update:"admin" create:"admin_optional"` // Column(Integer, nullable=True, default=0) # memory reserved in MB
MemCmtbound float32 `nullable:"true" default:"1" list:"admin" update:"admin" create:"admin_optional"` // = Column(Float, nullable=True)
StorageSize int `nullable:"true" list:"admin" update:"admin" create:"admin_optional"` // Column(Integer, nullable=True) # storage size in MB
StorageType string `width:"20" charset:"ascii" nullable:"true" list:"admin" update:"admin" create:"admin_optional"` // Column(VARCHAR(20, charset='ascii'), nullable=True)
+1 -1
View File
@@ -120,7 +120,7 @@ type SStorage struct {
Reserved int `nullable:"true" default:"0" list:"admin" update:"admin"` // Column(Integer, nullable=True, default=0)
StorageType string `width:"32" charset:"ascii" nullable:"false" list:"user" update:"admin" create:"admin_required"` // Column(VARCHAR(32, charset='ascii'), nullable=False)
MediumType string `width:"32" charset:"ascii" nullable:"false" list:"user" update:"admin" create:"admin_required"` // Column(VARCHAR(32, charset='ascii'), nullable=False)
Cmtbound float32 `nullable:"true" default:"1.0" list:"admin" update:"admin"` // Column(Float, nullable=True)
Cmtbound float32 `nullable:"true" default:"1" list:"admin" update:"admin"` // Column(Float, nullable=True)
StorageConf jsonutils.JSONObject `nullable:"true" get:"admin" update:"admin"` // = Column(JSONEncodedDict, nullable=True)
ZoneId string `width:"36" charset:"ascii" nullable:"false" list:"user" create:"admin_required"`
+23 -1
View File
@@ -20,7 +20,7 @@ const (
ZONE_ENABLE = "enable"
ZONE_DISABLE = "disable"
ZONE_SOLDOUT = "soldout"
ZONE_LACK = "lack"
// ZONE_LACK = "lack"
)
type SZoneManager struct {
@@ -469,6 +469,12 @@ func (manager *SZoneManager) InitializeData() error {
return nil
})
}
if z.Status == "init" {
manager.TableSpec().Update(&z, func() error {
z.Status = ZONE_ENABLE
return nil
})
}
}
return nil
}
@@ -529,6 +535,21 @@ func (manager *SZoneManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQu
sqlchemy.In(q.Field("id"), sq.SubQuery()),
sqlchemy.In(q.Field("id"), sq2.SubQuery()),
))
q = q.Equals("status", ZONE_ENABLE)
}
managerStr, _ := query.GetString("manager")
if len(managerStr) > 0 {
provider := CloudproviderManager.FetchCloudproviderByIdOrName(managerStr)
if provider == nil {
return nil, httperrors.NewResourceNotFoundError("Cloud provider/manager %s not found", managerStr)
}
subq := CloudregionManager.Query("id").Equals("provider", provider.Provider).SubQuery()
q = q.In("cloudregion_id", subq)
}
providerStr, _ := query.GetString("provider")
if len(providerStr) > 0 {
subq := CloudregionManager.Query("id").Equals("provider", providerStr).SubQuery()
q = q.In("cloudregion_id", subq)
}
return q, nil
}
@@ -595,5 +616,6 @@ func (manager *SZoneManager) ValidateCreateData(ctx context.Context, userCred mc
regionId = "default"
}
data.Add(jsonutils.NewString(regionId), "cloudregion_id")
data.Set("status", jsonutils.NewString(ZONE_ENABLE))
return manager.SStatusStandaloneResourceBaseManager.ValidateCreateData(ctx, userCred, ownerProjId, query, data)
}
+1
View File
@@ -7,6 +7,7 @@ import (
"yunion.io/x/jsonutils"
"yunion.io/x/log"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
"yunion.io/x/onecloud/pkg/cloudcommon/db/taskman"
"yunion.io/x/onecloud/pkg/cloudcommon/notifyclient"
+3 -2
View File
@@ -153,8 +153,9 @@ func (self *GuestDeleteTask) OnSyncConfigComplete(ctx context.Context, obj db.IS
}
func (self *GuestDeleteTask) OnSyncConfigCompleteFailed(ctx context.Context, obj db.IStandaloneModel, err jsonutils.JSONObject) {
guest := obj.(*models.SGuest)
self.OnFailed(ctx, guest, err)
// guest := obj.(*models.SGuest)
// self.OnFailed(ctx, guest, err)
self.OnSyncConfigComplete(ctx, obj, err) // ignore sync config failed error
}
func (self *GuestDeleteTask) OnGuestDeleteFailed(ctx context.Context, obj db.IStandaloneModel, err jsonutils.JSONObject) {
+48
View File
@@ -19,6 +19,7 @@ type GuestRenewTask struct {
func init() {
taskman.RegisterTask(GuestRenewTask{})
taskman.RegisterTask(PrepaidRecycleHostRenewTask{})
}
func (self *GuestRenewTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) {
@@ -45,3 +46,50 @@ func (self *GuestRenewTask) OnInit(ctx context.Context, obj db.IStandaloneModel,
self.SetStageComplete(ctx, nil)
}
type PrepaidRecycleHostRenewTask struct {
taskman.STask
}
func (self *PrepaidRecycleHostRenewTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) {
host := obj.(*models.SHost)
durationStr, _ := self.GetParams().GetString("duration")
bc, _ := billing.ParseBillingCycle(durationStr)
ihost, err := host.GetIHost()
if err != nil {
msg := fmt.Sprintf("host.GetIHost fail %s", err)
log.Errorf(msg)
self.SetStageFailed(ctx, msg)
return
}
iVM, err := ihost.GetIVMById(host.RealExternalId)
if err != nil {
msg := fmt.Sprintf("ihost.GetIVMById fail %s", err)
log.Errorf(msg)
self.SetStageFailed(ctx, msg)
return
}
err = iVM.Renew(bc)
if err != nil {
msg := fmt.Sprintf("iVM.Renew fail %s", err)
log.Errorf(msg)
self.SetStageFailed(ctx, msg)
return
}
exp := iVM.GetExpiredAt()
err = host.DoSaveRenewInfo(ctx, self.UserCred, &bc, &exp)
if err != nil {
msg := fmt.Sprintf("SaveRenewInfo fail %s", err)
log.Errorf(msg)
self.SetStageFailed(ctx, msg)
return
}
self.SetStageComplete(ctx, nil)
}
+5 -2
View File
@@ -94,8 +94,9 @@ func ParseServerDeployInfoList(list []string) (*jsonutils.JSONDict, error) {
type ServerCreateOptions struct {
ScheduleOptions
NAME string `help:"Name of server" json:"-"`
MEMSPEC string `help:"Memory size Or Instance Type" metavar:"MEMSPEC" json:"-"`
NAME string `help:"Name of server" json:"-"`
MEMSPEC string `help:"Memory size Or Instance Type" metavar:"MEMSPEC" json:"-"`
Disk []string `help:"Disk descriptions" nargs:"+"`
Net []string `help:"Network descriptions" metavar:"NETWORK"`
IsolatedDevice []string `help:"Isolated device model or ID" metavar:"ISOLATED_DEVICE"`
@@ -127,6 +128,8 @@ type ServerCreateOptions struct {
Duration string `help:"valid duration of the server, e.g. 1H, 1D, 1W, 1M, 1Y, ADMIN ONLY option"`
AutoPrepaidRecycle bool `help:"automatically enable prepaid recycling after server is created successfully" json:",omitfalse"`
GenerateName bool `help:"name is generated by pattern" json:"-"`
}
+1 -1
View File
@@ -83,7 +83,7 @@ func _jsonRequest(client *sdk.Client, domain string, version string, apiName str
resp, err := processCommonRequest(client, req)
if err != nil {
log.Errorf("request error %s", err)
log.Errorf("request error %s parameters %s", err, params)
return nil, err
}
body, err := jsonutils.Parse(resp.GetHttpContentBytes())
+9 -3
View File
@@ -905,7 +905,13 @@ func (self *SInstance) GetBillingType() string {
}
func (self *SInstance) GetExpiredAt() time.Time {
return self.ExpiredTime
if !self.ExpiredTime.IsZero() {
now := time.Now()
if self.ExpiredTime.Sub(now) < time.Hour*24*365*6 {
return self.ExpiredTime
}
}
return time.Time{}
}
func (self *SInstance) UpdateUserData(userData string) error {
@@ -925,10 +931,10 @@ func (region *SRegion) RenewInstance(instanceId string, bc billing.SBillingCycle
params["InstanceId"] = instanceId
if bc.GetWeeks() <= 4 {
params["PeriodUnit"] = "Week"
params["Period"] = fmt.Sprintf("%s", bc.GetWeeks())
params["Period"] = fmt.Sprintf("%d", bc.GetWeeks())
} else {
params["PeriodUnit"] = "Month"
params["Period"] = fmt.Sprintf("%s", bc.GetMonths())
params["Period"] = fmt.Sprintf("%d", bc.GetMonths())
}
params["ClientToken"] = utils.GenRequestId(20)
_, err := region.ecsRequest("RenewInstance", params)
+2 -1
View File
@@ -5,6 +5,7 @@ import (
"yunion.io/x/jsonutils"
"yunion.io/x/log"
"yunion.io/x/pkg/utils"
"yunion.io/x/onecloud/pkg/cloudprovider"
"yunion.io/x/onecloud/pkg/compute/models"
@@ -134,7 +135,7 @@ func (self *SZone) IsEmulated() bool {
}
func (self *SZone) GetStatus() string {
if len(self.AvailableResourceCreation.ResourceTypes) == 0 {
if len(self.AvailableResourceCreation.ResourceTypes) == 0 || !utils.IsInStringArray("Instance", self.AvailableResourceCreation.ResourceTypes) {
return models.ZONE_SOLDOUT
} else {
return models.ZONE_ENABLE