mirror of
https://github.com/yunionio/cloudpods.git
synced 2026-06-22 21:33:17 +08:00
1. Add List dispaly Params 'brand' and List filter Params 'brand' for GuestTemplate and ScalingGroup. 2. ScalingActivity's list elem should be sorted in order by StartTime and EndTime 3. HealthCheckCycle and HealthCheckGov in ScalingGroup work now 4. Detach all ScalingGroup when pending delete Guest 5. If the scaling behavior of the scaling group fails n(n is 3 for now) times consecutively, the controller will automatically disablet this group 6. Timer will trigger ScalingPolicy throught request but func call
715 lines
27 KiB
Go
715 lines
27 KiB
Go
// Copyright 2019 Yunion
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package models
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"fmt"
|
|
"time"
|
|
|
|
"yunion.io/x/jsonutils"
|
|
"yunion.io/x/log"
|
|
"yunion.io/x/pkg/errors"
|
|
"yunion.io/x/pkg/utils"
|
|
"yunion.io/x/sqlchemy"
|
|
|
|
"yunion.io/x/onecloud/pkg/apis"
|
|
api "yunion.io/x/onecloud/pkg/apis/compute"
|
|
"yunion.io/x/onecloud/pkg/cloudcommon/db"
|
|
"yunion.io/x/onecloud/pkg/cloudcommon/db/lockman"
|
|
"yunion.io/x/onecloud/pkg/cloudcommon/db/taskman"
|
|
"yunion.io/x/onecloud/pkg/httperrors"
|
|
"yunion.io/x/onecloud/pkg/mcclient"
|
|
"yunion.io/x/onecloud/pkg/util/logclient"
|
|
"yunion.io/x/onecloud/pkg/util/stringutils2"
|
|
)
|
|
|
|
type SScalingGroupManager struct {
|
|
db.SVirtualResourceBaseManager
|
|
SCloudregionResourceBaseManager
|
|
SVpcResourceBaseManager
|
|
SLoadbalancerBackendgroupResourceBaseManager
|
|
SGroupResourceBaseManager
|
|
SGuestTemplateResourceBaseManager
|
|
SGuestResourceBaseManager
|
|
db.SEnabledResourceBaseManager
|
|
}
|
|
|
|
type SScalingGroup struct {
|
|
db.SVirtualResourceBase
|
|
SCloudregionResourceBase
|
|
SVpcResourceBase
|
|
SLoadbalancerBackendgroupResourceBase
|
|
// GuestGroupId represent the guest gropu related to this scaling group.
|
|
// Every scaling group will have only one guest group related to itself.
|
|
SGroupResourceBase
|
|
SGuestTemplateResourceBase
|
|
db.SEnabledResourceBase
|
|
|
|
Hypervisor string `width:"16" charset:"ascii" default:"kvm" create:"required" list:"user" get:"user" update:"user"`
|
|
MinInstanceNumber int `nullable:"false" default:"0" create:"required" list:"user" get:"user" update:"user"`
|
|
MaxInstanceNumber int `nullable:"false" default:"10" create:"required" list:"user" get:"user" update:"user"`
|
|
|
|
// DesireInstanceNumber represent the number of instances that should exist in the scaling group.
|
|
// Scaling controller will monitor and ensure this in real time.
|
|
// Scaling activities triggered by various policies will also modify this value.
|
|
// This value should between MinInstanceNumber and MaxInstanceNumber
|
|
DesireInstanceNumber int `nullable:"false" default:"0" create:"required" list:"user" get:"user" update:"user"`
|
|
|
|
// ExpansionPrinciple represent the principle when creating new instance to join in.
|
|
ExpansionPrinciple string `width:"32" charset:"ascii" default:"balanced" create:"optional" list:"user" update:"user" get:"user"`
|
|
|
|
// ShrinkPrinciple represent the principle when removing instance from scaling group.
|
|
ShrinkPrinciple string `width:"32" charset:"ascii" default:"earliest" create:"optional" list:"user" update:"user" get:"user"`
|
|
|
|
HealthCheckMode string `width:"32" charset:"ascii" default:"normal" create:"optional" list:"user" update:"user" get:"user"`
|
|
HealthCheckCycle int `nullable:"false" default:"300" create:"optional" list:"user" update:"user" get:"user"'`
|
|
HealthCheckGov int `nullable:"false" default:"180" create:"optional" list:"user" update:"user" get:"user"`
|
|
|
|
LoadbalancerBackendPort int `nullable:"false" default:"80" create:"optional" list:"user" get:"user"`
|
|
LoadbalancerBackendWeight int `nillable:"false" default:"1" create:"optional" list:"user" get:"user"`
|
|
|
|
// Time to allow scale
|
|
AllowScaleTime time.Time
|
|
// NextCheckTime descripe the next time to check instance's health
|
|
NextCheckTime time.Time
|
|
}
|
|
|
|
var ScalingGroupManager *SScalingGroupManager
|
|
|
|
func init() {
|
|
ScalingGroupManager = &SScalingGroupManager{
|
|
SVirtualResourceBaseManager: db.NewVirtualResourceBaseManager(
|
|
SScalingGroup{},
|
|
"scalinggroups_tbl",
|
|
"scalinggroup",
|
|
"scalinggroups",
|
|
),
|
|
}
|
|
ScalingGroupManager.SetVirtualObject(ScalingGroupManager)
|
|
}
|
|
|
|
func (sgm *SScalingGroupManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential,
|
|
ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, input api.ScalingGroupCreateInput) (api.ScalingGroupCreateInput,
|
|
error) {
|
|
var err error
|
|
input.VirtualResourceCreateInput, err = sgm.SVirtualResourceBaseManager.ValidateCreateData(ctx, userCred,
|
|
ownerId, query, input.VirtualResourceCreateInput)
|
|
// check InstanceNumber
|
|
if input.MinInstanceNumber < 0 {
|
|
return input, httperrors.NewInputParameterError("min_instance_number should not be smaller than 0")
|
|
}
|
|
if input.MinInstanceNumber > input.MaxInstanceNumber {
|
|
return input, httperrors.NewInputParameterError(
|
|
"min_instance_number should not be bigger than max_instance_number")
|
|
}
|
|
if input.DesireInstanceNumber < input.MinInstanceNumber || input.DesireInstanceNumber > input.MaxInstanceNumber {
|
|
return input, httperrors.NewInputParameterError(
|
|
"desire_instance_number should between min_instance_number and max_instance_number")
|
|
}
|
|
|
|
// check cloudregion
|
|
idOrName := input.Cloudregion
|
|
if len(input.CloudregionId) != 0 {
|
|
idOrName = input.CloudregionId
|
|
}
|
|
cloudregion, err := CloudregionManager.FetchByIdOrName(userCred, idOrName)
|
|
if errors.Cause(err) == sql.ErrNoRows {
|
|
return input, httperrors.NewInputParameterError("no such cloud region %s", idOrName)
|
|
}
|
|
if err != nil {
|
|
return input, errors.Wrap(err, "CloudregionManager.FetchByIdOrName")
|
|
}
|
|
input.CloudregionId = cloudregion.GetId()
|
|
|
|
// check vpc
|
|
_, input.VpcResourceInput, err = ValidateVpcResourceInput(userCred, input.VpcResourceInput)
|
|
if err != nil {
|
|
return input, err
|
|
}
|
|
|
|
// check networks
|
|
if len(input.Networks) == 0 {
|
|
return input, httperrors.NewInputParameterError("ScalingGroup should have some networks")
|
|
}
|
|
networks := make([]SNetwork, 0, len(input.Networks))
|
|
q := NetworkManager.Query()
|
|
if len(input.Networks) == 1 {
|
|
q = q.Filter(sqlchemy.OR(sqlchemy.Equals(q.Field("id"), input.Networks[0]), sqlchemy.Equals(q.Field("name"), input.Networks[0])))
|
|
} else {
|
|
q = q.Filter(sqlchemy.OR(sqlchemy.In(q.Field("id"), input.Networks), sqlchemy.In(q.Field("name"), input.Networks)))
|
|
}
|
|
err = db.FetchModelObjects(NetworkManager, q, &networks)
|
|
if err != nil {
|
|
return input, errors.Wrap(err, "db.FetchModelObjects")
|
|
}
|
|
if len(networks) != len(input.Networks) {
|
|
return input, httperrors.NewInputParameterError("some networks not exist")
|
|
}
|
|
|
|
// check networks in vpc
|
|
for i := range networks {
|
|
vpc := networks[i].GetVpc()
|
|
if vpc == nil {
|
|
return input, fmt.Errorf("Get vpc of network '%s' failed", networks[i].Id)
|
|
}
|
|
if vpc.Id != input.Vpc {
|
|
return input, httperrors.NewInputParameterError("network '%s' not in vpc '%s'", networks[i].Id, input.Vpc)
|
|
}
|
|
input.Networks[i] = networks[i].Id
|
|
}
|
|
|
|
// check Guest Template
|
|
idOrName = input.GuestTemplate
|
|
if len(input.GuestTemplateId) != 0 {
|
|
idOrName = input.GuestTemplateId
|
|
}
|
|
guestTemplate, err := GuestTemplateManager.FetchByIdOrName(userCred, idOrName)
|
|
if errors.Cause(err) == sql.ErrNoRows {
|
|
return input, httperrors.NewInputParameterError("no such guest template %s", idOrName)
|
|
}
|
|
if err != nil {
|
|
return input, errors.Wrap(err, "GuestTempalteManager.FetchByIdOrName")
|
|
}
|
|
if ok, reason := guestTemplate.(*SGuestTemplate).Validate(ctx, userCred, ownerId,
|
|
SGuestTemplateValidate{input.Hypervisor, input.CloudregionId, input.Vpc, input.Networks}); !ok {
|
|
return input, httperrors.NewInputParameterError("the guest template %s is not valid in cloudregion %s, "+
|
|
"reason: %s", idOrName, input.CloudregionId, reason)
|
|
}
|
|
input.GuestTemplateId = guestTemplate.GetId()
|
|
|
|
// check Expansion Principle
|
|
if !utils.IsInStringArray(input.ExpansionPrinciple, []string{api.EXPANSION_BALANCED, ""}) {
|
|
return input, httperrors.NewInputParameterError("unkown expansion principle %s", input.ExpansionPrinciple)
|
|
}
|
|
|
|
// check Shrink Principle
|
|
if !utils.IsInStringArray(input.ShrinkPrinciple, []string{api.SHRINK_EARLIEST_CREATION_FIRST, api.SHRINK_LATEST_CREATION_FIRST,
|
|
api.SHRINK_CONFIG_EARLIEST_CREATION_FIRST, api.SHRINK_CONFIG_LATEST_CREATION_FIRST, ""}) {
|
|
return input, httperrors.NewInputParameterError("unkown shrink principle %s", input.ShrinkPrinciple)
|
|
}
|
|
|
|
// check health check mod
|
|
if !utils.IsInStringArray(input.HealthCheckMode, []string{api.HEALTH_CHECK_MODE_LOADBALANCER, api.HEALTH_CHECK_MODE_NORMAL, ""}) {
|
|
return input, httperrors.NewInputParameterError("unkown health check mode %s", input.HealthCheckMode)
|
|
}
|
|
|
|
// check lb
|
|
if len(input.LbBackendGroup) != 0 {
|
|
idOrName = input.LbBackendGroup
|
|
lb, err := LoadbalancerBackendGroupManager.FetchByIdOrName(userCred, idOrName)
|
|
if errors.Cause(err) == sql.ErrNoRows {
|
|
return input, httperrors.NewInputParameterError("no such loadbalancer backend group '%s'", idOrName)
|
|
}
|
|
if err != nil {
|
|
return input, errors.Wrap(err, "LoadbalancerBackendGroupManager.FetchByIdOrName")
|
|
}
|
|
input.BackendGroupId = lb.GetId()
|
|
|
|
// check lbeg port
|
|
if input.LoadbalancerBackendPort < 1 || input.LoadbalancerBackendPort > 65535 {
|
|
return input, httperrors.NewInputParameterError("invalid loadbalancer backend port '%d'", input.LoadbalancerBackendPort)
|
|
}
|
|
|
|
// check lbeg weight
|
|
if input.LoadbalancerBackendWeight < 1 {
|
|
return input, httperrors.NewInputParameterError("invalid loadbalancer backend weight '%d'", input.LoadbalancerBackendWeight)
|
|
}
|
|
}
|
|
|
|
return input, nil
|
|
}
|
|
|
|
func (sg *SScalingGroup) ValidateDeleteCondition(ctx context.Context) error {
|
|
// check enabled
|
|
if sg.Enabled.IsTrue() {
|
|
return httperrors.NewForbiddenError("Please disable this ScalingGroup firstly")
|
|
}
|
|
count, err := sg.GuestNumber()
|
|
if err != nil {
|
|
return errors.Wrap(err, "ScalingGroup.GuestNumber error")
|
|
}
|
|
if count != 0 {
|
|
return httperrors.NewForbiddenError("There are some guests in this ScalingGroup, please delete them firstly")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (sg *SScalingGroup) Delete(ctx context.Context, userCred mcclient.TokenCredential) error {
|
|
log.Infof("SScaling Group delete do nothing")
|
|
return nil
|
|
}
|
|
|
|
func (sg *SScalingGroup) RealDelete(ctx context.Context, userCred mcclient.TokenCredential) error {
|
|
err := db.DeleteModel(ctx, userCred, sg)
|
|
if err != nil {
|
|
return errors.Wrap(err, "db.DeleteModel")
|
|
}
|
|
sg.SetStatus(userCred, api.SG_STATUS_DELETED, "")
|
|
return nil
|
|
}
|
|
|
|
func (sg *SScalingGroup) CustomizeDelete(ctx context.Context, userCred mcclient.TokenCredential,
|
|
query jsonutils.JSONObject, data jsonutils.JSONObject) error {
|
|
return sg.StartScalingGroupDeleteTask(ctx, userCred)
|
|
}
|
|
|
|
func (sg *SScalingGroup) StartScalingGroupDeleteTask(ctx context.Context, userCred mcclient.TokenCredential) error {
|
|
task, err := taskman.TaskManager.NewTask(ctx, "ScalingGroupDeleteTask", sg, userCred, nil, "", "", nil)
|
|
if err != nil {
|
|
return errors.Wrap(err, "Start ScalingGroupDeleteTask failed")
|
|
}
|
|
task.ScheduleRun(nil)
|
|
return nil
|
|
}
|
|
|
|
func (sg *SScalingGroup) StartScalingGroupCreateTask(ctx context.Context, userCred mcclient.TokenCredential) error {
|
|
task, err := taskman.TaskManager.NewTask(ctx, "ScalingGroupCreateTask", sg, userCred, nil, "", "", nil)
|
|
if err != nil {
|
|
return errors.Wrap(err, "Start ScalingGroupCreateTask failed")
|
|
}
|
|
task.ScheduleRun(nil)
|
|
return nil
|
|
}
|
|
|
|
func (sg *SScalingGroup) ScalingPolicies() ([]SScalingPolicy, error) {
|
|
ret := make([]SScalingPolicy, 0)
|
|
q := ScalingPolicyManager.Query().Equals("scaling_group_id", sg.Id)
|
|
err := db.FetchModelObjects(ScalingPolicyManager, q, &ret)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "db.FetchModelObjects")
|
|
}
|
|
return ret, nil
|
|
}
|
|
|
|
func (sgm *SScalingGroupManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery,
|
|
userCred mcclient.TokenCredential, input api.ScalingGroupListInput) (*sqlchemy.SQuery, error) {
|
|
|
|
q, err := sgm.SCloudregionResourceBaseManager.ListItemFilter(ctx, q, userCred, input.RegionalFilterListInput)
|
|
if err != nil {
|
|
return q, err
|
|
}
|
|
q, err = sgm.SVpcResourceBaseManager.ListItemFilter(ctx, q, userCred, input.VpcFilterListInput)
|
|
if err != nil {
|
|
return q, err
|
|
}
|
|
q, err = sgm.SLoadbalancerBackendgroupResourceBaseManager.ListItemFilter(ctx, q, userCred, input.LoadbalancerBackendGroupFilterListInput)
|
|
if err != nil {
|
|
return q, err
|
|
}
|
|
q, err = sgm.SGroupResourceBaseManager.ListItemFilter(ctx, q, userCred, input.GroupFilterListInput)
|
|
if err != nil {
|
|
return q, err
|
|
}
|
|
q, err = sgm.SGuestTemplateResourceBaseManager.ListItemFilter(ctx, q, userCred, input.GuestTemplateFilterListInput)
|
|
if err != nil {
|
|
return q, err
|
|
}
|
|
q, err = sgm.SEnabledResourceBaseManager.ListItemFilter(ctx, q, userCred, input.EnabledResourceBaseListInput)
|
|
if err != nil {
|
|
return q, err
|
|
}
|
|
if len(input.Hypervisor) > 0 {
|
|
q = q.Equals("hypervisor", input.Hypervisor)
|
|
}
|
|
if len(input.Brand) > 0 {
|
|
q = q.Equals("hypervisor", Brand2Hypervisor(input.Brand))
|
|
}
|
|
return q, nil
|
|
}
|
|
|
|
func (sgm *SScalingGroup) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential,
|
|
query jsonutils.JSONObject, isList bool) (api.ScalingGroupDetails, error) {
|
|
return api.ScalingGroupDetails{}, nil
|
|
}
|
|
|
|
func (sgm *SScalingGroupManager) FetchCustomizeColumns(
|
|
ctx context.Context,
|
|
userCred mcclient.TokenCredential,
|
|
query jsonutils.JSONObject,
|
|
objs []interface{},
|
|
fields stringutils2.SSortedStrings,
|
|
isList bool,
|
|
) []api.ScalingGroupDetails {
|
|
virtRows := sgm.SVirtualResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList)
|
|
crRows := sgm.SCloudregionResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList)
|
|
vpcRows := sgm.SVpcResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList)
|
|
lbbgRows := sgm.SLoadbalancerBackendgroupResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList)
|
|
groupRows := sgm.SGroupResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList)
|
|
gtRows := sgm.SGuestTemplateResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList)
|
|
|
|
rows := make([]api.ScalingGroupDetails, len(objs))
|
|
for i := range rows {
|
|
rows[i] = api.ScalingGroupDetails{
|
|
VirtualResourceDetails: virtRows[i],
|
|
CloudregionResourceInfo: crRows[i],
|
|
LoadbalancerBackendGroupResourceInfo: lbbgRows[i],
|
|
VpcResourceInfo: vpcRows[i],
|
|
GroupResourceInfo: groupRows[i],
|
|
GuestTemplateResourceInfo: gtRows[i],
|
|
}
|
|
sg := objs[i].(*SScalingGroup)
|
|
n, _ := sg.GuestNumber()
|
|
rows[i].InstanceNumber = n
|
|
n, _ = sg.ScalingPolicyNumber()
|
|
rows[i].ScalingPolicyNumber = n
|
|
rows[i].Brand = Hypervisor2Brand(sg.Hypervisor)
|
|
}
|
|
return rows
|
|
}
|
|
|
|
func (sg *SScalingGroup) GuestNumber() (int, error) {
|
|
q := GuestManager.Query().In("id", ScalingGroupGuestManager.Query("guest_id").Equals("scaling_group_id",
|
|
sg.Id).SubQuery()).IsFalse("pending_deleted")
|
|
return q.CountWithError()
|
|
}
|
|
|
|
func (sg *SScalingGroup) ScalingPolicyNumber() (int, error) {
|
|
q := ScalingPolicyManager.Query().Equals("scaling_group_id", sg.Id)
|
|
return q.CountWithError()
|
|
}
|
|
|
|
/*func (sg *SScalingGroup) RemoveAllGuests(ctx context.Context, userCred mcclient.TokenCredential) error {
|
|
q := ScalingGroupGuestManager.Query().Equals("scaling_group_id", sg.Id)
|
|
scalingGroupGuests := make([]SScalingGroupGuest, 0)
|
|
err := db.FetchModelObjects(ScalingGroupGuestManager, q, &scalingGroupGuests)
|
|
if err != nil {
|
|
return errors.Wrap(err, "db.FetchModelObjects")
|
|
}
|
|
for i := range scalingGroupGuests {
|
|
err := scalingGroupGuests[i].Delete(ctx, userCred)
|
|
return errors.Wrapf(err, "delete ScalingGroupGuests(ScalingGroupId: '%s', GuestId: '%s') failed",
|
|
scalingGroupGuests[i].ScalingGroupId, scalingGroupGuests[i].GuestId)
|
|
}
|
|
return nil
|
|
}*/
|
|
|
|
func (sg *SScalingGroup) ScalingGroupGuests(guestIds []string) ([]SScalingGroupGuest, error) {
|
|
q := ScalingGroupGuestManager.Query().Equals("scaling_group_id", sg.GetId())
|
|
if len(guestIds) == 0 {
|
|
return nil, nil
|
|
}
|
|
if len(guestIds) == 1 {
|
|
q = q.Equals("guest_id", guestIds[0])
|
|
}
|
|
if len(guestIds) > 1 {
|
|
q = q.In("guest_id", guestIds)
|
|
}
|
|
sggs := make([]SScalingGroupGuest, 0, len(guestIds))
|
|
err := db.FetchModelObjects(ScalingGroupGuestManager, q, &sggs)
|
|
return sggs, err
|
|
}
|
|
|
|
func (sg *SScalingGroup) Guests() ([]SGuest, error) {
|
|
q := GuestManager.Query().In("id", ScalingGroupGuestManager.Query("guest_id").Equals("scaling_group_id", sg.Id).SubQuery())
|
|
guests := make([]SGuest, 0, 1)
|
|
err := db.FetchModelObjects(GuestManager, q, &guests)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "db.FetchModelObjects")
|
|
}
|
|
return guests, nil
|
|
}
|
|
|
|
type sExecResult struct {
|
|
// 0: success; 1: part success; 2: reject; 3: fail
|
|
code uint8
|
|
actionStr string
|
|
reason string
|
|
intanceNum int
|
|
}
|
|
|
|
func (sg *SScalingGroup) exec(ctx context.Context, action IScalingAction) (ret sExecResult) {
|
|
ret.code = 3
|
|
ret.intanceNum = -1
|
|
lockman.LockObject(ctx, sg)
|
|
defer lockman.ReleaseObject(ctx, sg)
|
|
// query again to fetch the latest desire instance number of sg
|
|
model, err := ScalingGroupManager.FetchById(sg.Id)
|
|
if err != nil {
|
|
ret.reason = fmt.Sprintf("fail to get ScalingGroup: %s", err.Error())
|
|
return
|
|
}
|
|
sg = model.(*SScalingGroup)
|
|
targetNum := action.Exec(sg.DesireInstanceNumber)
|
|
// targetNum must between sg.MinInstanceNumber and sg.MaxInstanceNumber
|
|
ret.code = 0
|
|
if targetNum > sg.MaxInstanceNumber {
|
|
if sg.DesireInstanceNumber == sg.MaxInstanceNumber {
|
|
ret.code = 2
|
|
ret.reason = fmt.Sprintf(
|
|
`Want to change the Desired Instance Number from "%d" to "%d", but the Desired Instance Number has reached the Max Instance Number`,
|
|
sg.DesireInstanceNumber, targetNum,
|
|
)
|
|
return
|
|
}
|
|
ret.code = 1
|
|
ret.reason = fmt.Sprintf(
|
|
`Want to change the Desired Instance Number from "%d" to "%d", but "%d" is greater than the Max Instance Number "%d"`,
|
|
sg.DesireInstanceNumber, targetNum, targetNum, sg.MaxInstanceNumber,
|
|
)
|
|
targetNum = sg.MaxInstanceNumber
|
|
} else if targetNum < sg.MinInstanceNumber {
|
|
if sg.DesireInstanceNumber == sg.MinInstanceNumber {
|
|
ret.code = 2
|
|
ret.reason = fmt.Sprintf(
|
|
`Want to change the Desired Instance Number from "%d" to "%d", but the Desired Instance Number has reached the Min Instance Number`,
|
|
sg.DesireInstanceNumber, targetNum,
|
|
)
|
|
return
|
|
}
|
|
ret.code = 1
|
|
ret.reason = fmt.Sprintf(
|
|
`Want to change the Desired Instance Number from "%d" to "%d", but "%d" is less than the Min Instance Number "%d"`,
|
|
sg.DesireInstanceNumber, targetNum, targetNum, sg.MinInstanceNumber,
|
|
)
|
|
targetNum = sg.MinInstanceNumber
|
|
}
|
|
ret.actionStr = fmt.Sprintf(`Change the Desired Instance Number from "%d" to "%d"`, sg.DesireInstanceNumber, targetNum)
|
|
_, err = db.Update(sg, func() error {
|
|
sg.DesireInstanceNumber = targetNum
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
ret.code = 3
|
|
ret.reason = fmt.Sprintf("Update ScalingGroup's DesireInstanceNumber failed: %s", err.Error())
|
|
}
|
|
return
|
|
}
|
|
|
|
// Scale will modify SScalingGroup.DesireInstanceNumber and generate SScalingActivity based on the trigger and its
|
|
// corresponding SScalingPolicy.
|
|
func (sg *SScalingGroup) Scale(ctx context.Context, triggerDesc IScalingTriggerDesc, action IScalingAction) error {
|
|
if sg.Enabled.IsFalse() {
|
|
return nil
|
|
}
|
|
scalingActivity, err := ScalingActivityManager.CreateScalingActivity(sg.Id, triggerDesc.TriggerDescription(), api.SA_STATUS_EXEC)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "create ScalingActivity whose ScalingGroup is %s error", sg.Id)
|
|
}
|
|
if action.CheckCoolTime() && !sg.AllowScale() {
|
|
err = scalingActivity.SetReject("",
|
|
fmt.Sprintf("The Cooling Time limit the execution time of the policy to at least: %s",
|
|
sg.AllowScaleTime))
|
|
return nil
|
|
}
|
|
|
|
ret := sg.exec(ctx, action)
|
|
switch ret.code {
|
|
case 0:
|
|
err = scalingActivity.SetResult(ret.actionStr, api.SA_STATUS_SUCCEED, "", ret.intanceNum)
|
|
case 1:
|
|
err = scalingActivity.SetResult(ret.actionStr, api.SA_STATUS_PART_SUCCEED, ret.reason, ret.intanceNum)
|
|
case 2:
|
|
err = scalingActivity.SetReject("", ret.reason)
|
|
case 3:
|
|
err = scalingActivity.SetFailed(ret.actionStr, ret.reason)
|
|
}
|
|
|
|
if err != nil {
|
|
log.Errorf("ScalingActivity set result failed: %s", err.Error())
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (sgm *SScalingGroupManager) QueryDistinctExtraField(q *sqlchemy.SQuery, field string) (*sqlchemy.SQuery, error) {
|
|
q, err := sgm.SVirtualResourceBaseManager.QueryDistinctExtraField(q, field)
|
|
if err == nil {
|
|
return q, nil
|
|
}
|
|
q, err = sgm.SCloudregionResourceBaseManager.QueryDistinctExtraField(q, field)
|
|
if err == nil {
|
|
return q, nil
|
|
}
|
|
q, err = sgm.SVpcResourceBaseManager.QueryDistinctExtraField(q, field)
|
|
if err == nil {
|
|
return q, nil
|
|
}
|
|
q, err = sgm.SLoadbalancerBackendgroupResourceBaseManager.QueryDistinctExtraField(q, field)
|
|
if err == nil {
|
|
return q, nil
|
|
}
|
|
q, err = sgm.SGroupResourceBaseManager.QueryDistinctExtraField(q, field)
|
|
if err == nil {
|
|
return q, nil
|
|
}
|
|
q, err = sgm.SGuestTemplateResourceBaseManager.QueryDistinctExtraField(q, field)
|
|
if err == nil {
|
|
return q, nil
|
|
}
|
|
scalinggroupGuests := ScalingGroupGuestManager.Query("scaling_group_id", "guest_id").SubQuery()
|
|
q = q.LeftJoin(scalinggroupGuests, sqlchemy.Equals(q.Field("id"), scalinggroupGuests.Field("scaling_group_id")))
|
|
q, err = sgm.SGuestResourceBaseManager.QueryDistinctExtraField(q, field)
|
|
if err == nil {
|
|
return q, nil
|
|
}
|
|
return q, httperrors.ErrNotFound
|
|
}
|
|
|
|
func (manager *SScalingGroupManager) OrderByExtraFields(ctx context.Context, q *sqlchemy.SQuery,
|
|
userCred mcclient.TokenCredential, query api.ScalingGroupListInput) (*sqlchemy.SQuery, error) {
|
|
q, err := manager.SVirtualResourceBaseManager.OrderByExtraFields(ctx, q, userCred, query.VirtualResourceListInput)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
q, err = manager.SVpcResourceBaseManager.OrderByExtraFields(ctx, q, userCred, query.VpcFilterListInput)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
q, err = manager.SLoadbalancerBackendgroupResourceBaseManager.OrderByExtraFields(ctx, q, userCred, query.LoadbalancerBackendGroupFilterListInput)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
q, err = manager.SGroupResourceBaseManager.OrderByExtraFields(ctx, q, userCred, query.GroupFilterListInput)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return q, nil
|
|
}
|
|
|
|
func (sg *SScalingGroup) PostCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) {
|
|
// attach with networks
|
|
networks, _ := data.Get("networks")
|
|
networkIds := networks.(*jsonutils.JSONArray).GetStringArray()
|
|
for _, netId := range networkIds {
|
|
err := ScalingGroupNetworkManager.Attach(ctx, sg.Id, netId)
|
|
if err != nil {
|
|
reason := fmt.Sprintf("Attach ScalingGroup '%s' with Network '%s' failed: %s", sg.Id, netId, err.Error())
|
|
sg.SetStatus(userCred, api.SG_STATUS_CREATE_FAILED, reason)
|
|
logclient.AddActionLogWithContext(ctx, sg, logclient.ACT_CREATE, reason, userCred, false)
|
|
return
|
|
}
|
|
}
|
|
now := time.Now()
|
|
db.Update(sg, func() error {
|
|
sg.Status = api.SG_STATUS_READY
|
|
sg.AllowScaleTime = now
|
|
sg.NextCheckTime = now.Add(time.Duration(sg.HealthCheckCycle) * time.Second)
|
|
sg.SetEnabled(true)
|
|
return nil
|
|
})
|
|
logclient.AddActionLogWithContext(ctx, sg, logclient.ACT_CREATE, "", userCred, true)
|
|
}
|
|
|
|
func (sg *SScalingGroup) AllowScale() bool {
|
|
return sg.AllowScaleTime.Before(time.Now())
|
|
}
|
|
|
|
func (sg *SScalingGroup) SetAllowScaleTime(t time.Time) {
|
|
if sg.AllowScaleTime.After(t) {
|
|
return
|
|
}
|
|
_, err := db.Update(sg, func() error {
|
|
sg.AllowScaleTime = t
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
log.Errorf("Set AllowScaleTime error: %s", err)
|
|
}
|
|
return
|
|
}
|
|
|
|
func (sg *SScalingGroup) AllowPerformEnable(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input apis.PerformEnableInput) bool {
|
|
return sg.IsOwner(userCred) || db.IsAdminAllowPerform(userCred, sg, "enable")
|
|
}
|
|
|
|
func (sg *SScalingGroup) PerformEnable(ctx context.Context, userCred mcclient.TokenCredential,
|
|
query jsonutils.JSONObject, input apis.PerformEnableInput) (jsonutils.JSONObject, error) {
|
|
err := db.EnabledPerformEnable(sg, ctx, userCred, true)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "EnabledPerformEnable")
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
func (sg *SScalingGroup) AllowPerformDisable(ctx context.Context, userCred mcclient.TokenCredential,
|
|
query jsonutils.JSONObject, input apis.PerformDisableInput) bool {
|
|
return sg.IsOwner(userCred) || db.IsAdminAllowPerform(userCred, sg, "disable")
|
|
}
|
|
|
|
func (sg *SScalingGroup) PerformDisable(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject,
|
|
input apis.PerformDisableInput) (jsonutils.JSONObject, error) {
|
|
err := db.EnabledPerformEnable(sg, ctx, userCred, false)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "EnabledPerformEnable")
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
func (s *SGuest) AllowPerformDetachScalingGroup(ctx context.Context, userCred mcclient.TokenCredential,
|
|
query jsonutils.JSONObject, input api.SGPerformDetachScalingGroupInput) bool {
|
|
return s.IsOwner(userCred) || db.IsAdminAllowPerform(userCred, s, "detach-scaling-group")
|
|
}
|
|
|
|
func (s *SGuest) PerformDetachScalingGroup(ctx context.Context, userCred mcclient.TokenCredential,
|
|
query jsonutils.JSONObject, input api.SGPerformDetachScalingGroupInput) (jsonutils.JSONObject, error) {
|
|
// check ScalingGroup
|
|
model, err := ScalingGroupManager.FetchByIdOrName(userCred, input.ScalingGroup)
|
|
if err != nil {
|
|
if errors.Cause(err) == sql.ErrNoRows {
|
|
return nil, httperrors.NewInputParameterError("no such ScalingGroup '%s'", input.ScalingGroup)
|
|
}
|
|
return nil, errors.Wrap(err, "ScalingGroupManager.FetchByIdOrName")
|
|
}
|
|
// check ScalingGroupGuest
|
|
sggs, err := ScalingGroupGuestManager.Fetch(model.GetId(), s.Id)
|
|
if err != nil {
|
|
if errors.Cause(err) == sql.ErrNoRows {
|
|
return nil, httperrors.NewInputParameterError("Guest '%s' don't belong to ScalingGroup '%s'", s.Id, model.GetId())
|
|
}
|
|
return nil, errors.Wrap(err, "ScalingGroupGuestManager.Fetch")
|
|
}
|
|
if len(sggs) == 0 {
|
|
return nil, httperrors.NewInputParameterError("Guest '%s' don't belong to ScalingGroup '%s'", s.Id, model.GetId())
|
|
}
|
|
sg := model.(*SScalingGroup)
|
|
input.ScalingGroup = sggs[0].ScalingGroupId
|
|
sggs[0].SetGuestStatus(api.SG_GUEST_STATUS_REMOVING)
|
|
taskData := jsonutils.Marshal(input).(*jsonutils.JSONDict)
|
|
taskData.Set("guest", jsonutils.NewString(s.GetId()))
|
|
task, err := taskman.TaskManager.NewTask(ctx, "GuestDetachScalingGroupTask", sg, userCred, taskData, "", "")
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "Start GuestDetachScalingGroupTask failed")
|
|
}
|
|
task.ScheduleRun(nil)
|
|
return nil, nil
|
|
}
|
|
|
|
func (sg *SScalingGroup) NetworkIds() ([]string, error) {
|
|
sgnQuery := ScalingGroupNetworkManager.Query("network_id").Equals("scaling_group_id", sg.Id)
|
|
rows, err := sgnQuery.Rows()
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "SQuery.Rows")
|
|
}
|
|
defer rows.Close()
|
|
nets := make([]string, 0, 1)
|
|
for rows.Next() {
|
|
var net string
|
|
rows.Scan(&net)
|
|
nets = append(nets, net)
|
|
}
|
|
return nets, nil
|
|
}
|
|
|
|
func (sg *SScalingGroup) Activities() ([]SScalingActivity, error) {
|
|
q := ScalingActivityManager.Query().Equals("scaling_group_id", sg.Id)
|
|
activities := make([]SScalingActivity, 0, 1)
|
|
err := db.FetchModelObjects(ScalingActivityManager, q, &activities)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "db.FetchModelObjects")
|
|
}
|
|
return activities, nil
|
|
}
|