scaling_group.go 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787
  1. // Copyright 2019 Yunion
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package models
  15. import (
  16. "context"
  17. "database/sql"
  18. "fmt"
  19. "time"
  20. "yunion.io/x/jsonutils"
  21. "yunion.io/x/log"
  22. "yunion.io/x/pkg/errors"
  23. "yunion.io/x/pkg/utils"
  24. "yunion.io/x/sqlchemy"
  25. "yunion.io/x/onecloud/pkg/apis"
  26. api "yunion.io/x/onecloud/pkg/apis/compute"
  27. "yunion.io/x/onecloud/pkg/cloudcommon/db"
  28. "yunion.io/x/onecloud/pkg/cloudcommon/db/lockman"
  29. "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman"
  30. "yunion.io/x/onecloud/pkg/cloudcommon/notifyclient"
  31. "yunion.io/x/onecloud/pkg/cloudcommon/validators"
  32. "yunion.io/x/onecloud/pkg/httperrors"
  33. "yunion.io/x/onecloud/pkg/mcclient"
  34. "yunion.io/x/onecloud/pkg/util/logclient"
  35. "yunion.io/x/onecloud/pkg/util/stringutils2"
  36. )
  37. // +onecloud:swagger-gen-model-singular=scalinggroup
  38. // +onecloud:swagger-gen-model-plural=scalinggroups
  39. type SScalingGroupManager struct {
  40. db.SVirtualResourceBaseManager
  41. SCloudregionResourceBaseManager
  42. SVpcResourceBaseManager
  43. SLoadbalancerBackendgroupResourceBaseManager
  44. SGroupResourceBaseManager
  45. SGuestTemplateResourceBaseManager
  46. SGuestResourceBaseManager
  47. db.SEnabledResourceBaseManager
  48. }
  49. type SScalingGroup struct {
  50. db.SVirtualResourceBase
  51. SCloudregionResourceBase
  52. SVpcResourceBase
  53. SLoadbalancerBackendgroupResourceBase
  54. // GuestGroupId represent the guest gropu related to this scaling group.
  55. // Every scaling group will have only one guest group related to itself.
  56. SGroupResourceBase
  57. SGuestTemplateResourceBase
  58. db.SEnabledResourceBase
  59. Hypervisor string `width:"16" charset:"ascii" default:"kvm" create:"required" list:"user" get:"user" update:"user"`
  60. MinInstanceNumber int `nullable:"false" default:"0" create:"required" list:"user" get:"user" update:"user"`
  61. MaxInstanceNumber int `nullable:"false" default:"10" create:"required" list:"user" get:"user" update:"user"`
  62. // DesireInstanceNumber represent the number of instances that should exist in the scaling group.
  63. // Scaling controller will monitor and ensure this in real time.
  64. // Scaling activities triggered by various policies will also modify this value.
  65. // This value should between MinInstanceNumber and MaxInstanceNumber
  66. DesireInstanceNumber int `nullable:"false" default:"0" create:"required" list:"user" get:"user" update:"user"`
  67. // ExpansionPrinciple represent the principle when creating new instance to join in.
  68. ExpansionPrinciple string `width:"32" charset:"ascii" default:"balanced" create:"optional" list:"user" update:"user" get:"user"`
  69. // ShrinkPrinciple represent the principle when removing instance from scaling group.
  70. ShrinkPrinciple string `width:"32" charset:"ascii" default:"earliest" create:"optional" list:"user" update:"user" get:"user"`
  71. HealthCheckMode string `width:"32" charset:"ascii" default:"normal" create:"optional" list:"user" update:"user" get:"user"`
  72. HealthCheckCycle int `nullable:"false" default:"300" create:"optional" list:"user" update:"user" get:"user"`
  73. HealthCheckGov int `nullable:"false" default:"180" create:"optional" list:"user" update:"user" get:"user"`
  74. LoadbalancerBackendPort int `nullable:"false" default:"80" create:"optional" list:"user" get:"user"`
  75. LoadbalancerBackendWeight int `nillable:"false" default:"1" create:"optional" list:"user" get:"user"`
  76. // Time to allow scale
  77. AllowScaleTime time.Time
  78. // NextCheckTime descripe the next time to check instance's health
  79. NextCheckTime time.Time
  80. }
  81. var ScalingGroupManager *SScalingGroupManager
  82. func init() {
  83. ScalingGroupManager = &SScalingGroupManager{
  84. SVirtualResourceBaseManager: db.NewVirtualResourceBaseManager(
  85. SScalingGroup{},
  86. "scalinggroups_tbl",
  87. "scalinggroup",
  88. "scalinggroups",
  89. ),
  90. }
  91. ScalingGroupManager.SetVirtualObject(ScalingGroupManager)
  92. }
  93. func (sgm *SScalingGroupManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential,
  94. ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, input api.ScalingGroupCreateInput) (api.ScalingGroupCreateInput,
  95. error) {
  96. var err error
  97. input.VirtualResourceCreateInput, err = sgm.SVirtualResourceBaseManager.ValidateCreateData(ctx, userCred,
  98. ownerId, query, input.VirtualResourceCreateInput)
  99. // check InstanceNumber
  100. if input.MinInstanceNumber < 0 {
  101. return input, httperrors.NewInputParameterError("min_instance_number should not be smaller than 0")
  102. }
  103. if input.MinInstanceNumber > input.MaxInstanceNumber {
  104. return input, httperrors.NewInputParameterError(
  105. "min_instance_number should not be bigger than max_instance_number")
  106. }
  107. if input.DesireInstanceNumber < input.MinInstanceNumber || input.DesireInstanceNumber > input.MaxInstanceNumber {
  108. return input, httperrors.NewInputParameterError(
  109. "desire_instance_number should between min_instance_number and max_instance_number")
  110. }
  111. // check cloudregion
  112. idOrName := input.Cloudregion
  113. if len(input.CloudregionId) != 0 {
  114. idOrName = input.CloudregionId
  115. }
  116. cloudregion, err := CloudregionManager.FetchByIdOrName(ctx, userCred, idOrName)
  117. if errors.Cause(err) == sql.ErrNoRows {
  118. return input, httperrors.NewInputParameterError("no such cloud region %s", idOrName)
  119. }
  120. if err != nil {
  121. return input, errors.Wrap(err, "CloudregionManager.FetchByIdOrName")
  122. }
  123. input.CloudregionId = cloudregion.GetId()
  124. // check vpc
  125. _, err = validators.ValidateModel(ctx, userCred, VpcManager, &input.VpcId)
  126. if err != nil {
  127. return input, err
  128. }
  129. // check networks
  130. if len(input.Networks) == 0 {
  131. return input, httperrors.NewInputParameterError("ScalingGroup should have some networks")
  132. }
  133. networks := make([]SNetwork, 0, len(input.Networks))
  134. q := NetworkManager.Query()
  135. if len(input.Networks) == 1 {
  136. q = q.Filter(sqlchemy.OR(sqlchemy.Equals(q.Field("id"), input.Networks[0]), sqlchemy.Equals(q.Field("name"), input.Networks[0])))
  137. } else {
  138. q = q.Filter(sqlchemy.OR(sqlchemy.In(q.Field("id"), input.Networks), sqlchemy.In(q.Field("name"), input.Networks)))
  139. }
  140. err = db.FetchModelObjects(NetworkManager, q, &networks)
  141. if err != nil {
  142. return input, errors.Wrap(err, "db.FetchModelObjects")
  143. }
  144. if len(networks) != len(input.Networks) {
  145. return input, httperrors.NewInputParameterError("some networks not exist")
  146. }
  147. // check networks in vpc
  148. for i := range networks {
  149. vpc, _ := networks[i].GetVpc()
  150. if vpc == nil {
  151. return input, fmt.Errorf("Get vpc of network '%s' failed", networks[i].Id)
  152. }
  153. if vpc.Id != input.VpcId {
  154. return input, httperrors.NewInputParameterError("network '%s' not in vpc '%s'", networks[i].Id, input.VpcId)
  155. }
  156. input.Networks[i] = networks[i].Id
  157. }
  158. // check Guest Template
  159. idOrName = input.GuestTemplate
  160. if len(input.GuestTemplateId) != 0 {
  161. idOrName = input.GuestTemplateId
  162. }
  163. guestTemplate, err := GuestTemplateManager.FetchByIdOrName(ctx, userCred, idOrName)
  164. if errors.Cause(err) == sql.ErrNoRows {
  165. return input, httperrors.NewInputParameterError("no such guest template %s", idOrName)
  166. }
  167. if err != nil {
  168. return input, errors.Wrap(err, "GuestTempalteManager.FetchByIdOrName")
  169. }
  170. if ok, reason := guestTemplate.(*SGuestTemplate).Validate(ctx, userCred, ownerId,
  171. SGuestTemplateValidate{input.Hypervisor, input.CloudregionId, input.VpcId, input.Networks}); !ok {
  172. return input, httperrors.NewInputParameterError("the guest template %s is not valid in cloudregion %s, "+
  173. "reason: %s", idOrName, input.CloudregionId, reason)
  174. }
  175. input.GuestTemplateId = guestTemplate.GetId()
  176. // check Expansion Principle
  177. if !utils.IsInStringArray(input.ExpansionPrinciple, []string{api.EXPANSION_BALANCED, ""}) {
  178. return input, httperrors.NewInputParameterError("unkown expansion principle %s", input.ExpansionPrinciple)
  179. }
  180. // check Shrink Principle
  181. if !utils.IsInStringArray(input.ShrinkPrinciple, []string{api.SHRINK_EARLIEST_CREATION_FIRST, api.SHRINK_LATEST_CREATION_FIRST,
  182. api.SHRINK_CONFIG_EARLIEST_CREATION_FIRST, api.SHRINK_CONFIG_LATEST_CREATION_FIRST, ""}) {
  183. return input, httperrors.NewInputParameterError("unkown shrink principle %s", input.ShrinkPrinciple)
  184. }
  185. // check health check mod
  186. if !utils.IsInStringArray(input.HealthCheckMode, []string{api.HEALTH_CHECK_MODE_LOADBALANCER, api.HEALTH_CHECK_MODE_NORMAL, ""}) {
  187. return input, httperrors.NewInputParameterError("unkown health check mode %s", input.HealthCheckMode)
  188. }
  189. // check lb
  190. if len(input.LbBackendGroup) != 0 {
  191. idOrName = input.LbBackendGroup
  192. lb, err := LoadbalancerBackendGroupManager.FetchByIdOrName(ctx, userCred, idOrName)
  193. if errors.Cause(err) == sql.ErrNoRows {
  194. return input, httperrors.NewInputParameterError("no such loadbalancer backend group '%s'", idOrName)
  195. }
  196. if err != nil {
  197. return input, errors.Wrap(err, "LoadbalancerBackendGroupManager.FetchByIdOrName")
  198. }
  199. input.BackendGroupId = lb.GetId()
  200. // check lbeg port
  201. if input.LoadbalancerBackendPort < 1 || input.LoadbalancerBackendPort > 65535 {
  202. return input, httperrors.NewInputParameterError("invalid loadbalancer backend port '%d'", input.LoadbalancerBackendPort)
  203. }
  204. // check lbeg weight
  205. if input.LoadbalancerBackendWeight < 1 {
  206. return input, httperrors.NewInputParameterError("invalid loadbalancer backend weight '%d'", input.LoadbalancerBackendWeight)
  207. }
  208. }
  209. return input, nil
  210. }
  211. func (sg *SScalingGroup) ValidateDeleteCondition(ctx context.Context, info jsonutils.JSONObject) error {
  212. // check enabled
  213. if sg.Enabled.IsTrue() {
  214. return httperrors.NewForbiddenError("Please disable this ScalingGroup firstly")
  215. }
  216. count, err := sg.GuestNumber()
  217. if err != nil {
  218. return errors.Wrap(err, "ScalingGroup.GuestNumber error")
  219. }
  220. if count != 0 {
  221. return httperrors.NewForbiddenError("There are some guests in this ScalingGroup, please delete them firstly")
  222. }
  223. return nil
  224. }
  225. func (sg *SScalingGroup) Delete(ctx context.Context, userCred mcclient.TokenCredential) error {
  226. log.Infof("SScaling Group delete do nothing")
  227. return nil
  228. }
  229. func (sg *SScalingGroup) RealDelete(ctx context.Context, userCred mcclient.TokenCredential) error {
  230. err := db.DeleteModel(ctx, userCred, sg)
  231. if err != nil {
  232. return errors.Wrap(err, "db.DeleteModel")
  233. }
  234. sg.SetStatus(ctx, userCred, api.SG_STATUS_DELETED, "")
  235. return nil
  236. }
  237. func (sg *SScalingGroup) CustomizeDelete(ctx context.Context, userCred mcclient.TokenCredential,
  238. query jsonutils.JSONObject, data jsonutils.JSONObject) error {
  239. return sg.StartScalingGroupDeleteTask(ctx, userCred)
  240. }
  241. func (sg *SScalingGroup) StartScalingGroupDeleteTask(ctx context.Context, userCred mcclient.TokenCredential) error {
  242. task, err := taskman.TaskManager.NewTask(ctx, "ScalingGroupDeleteTask", sg, userCred, nil, "", "", nil)
  243. if err != nil {
  244. return errors.Wrap(err, "Start ScalingGroupDeleteTask failed")
  245. }
  246. task.ScheduleRun(nil)
  247. return nil
  248. }
  249. func (sg *SScalingGroup) StartScalingGroupCreateTask(ctx context.Context, userCred mcclient.TokenCredential) error {
  250. task, err := taskman.TaskManager.NewTask(ctx, "ScalingGroupCreateTask", sg, userCred, nil, "", "", nil)
  251. if err != nil {
  252. return errors.Wrap(err, "Start ScalingGroupCreateTask failed")
  253. }
  254. task.ScheduleRun(nil)
  255. return nil
  256. }
  257. func (sg *SScalingGroup) ScalingPolicies() ([]SScalingPolicy, error) {
  258. ret := make([]SScalingPolicy, 0)
  259. q := ScalingPolicyManager.Query().Equals("scaling_group_id", sg.Id)
  260. err := db.FetchModelObjects(ScalingPolicyManager, q, &ret)
  261. if err != nil {
  262. return nil, errors.Wrap(err, "db.FetchModelObjects")
  263. }
  264. return ret, nil
  265. }
  266. func (sgm *SScalingGroupManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery,
  267. userCred mcclient.TokenCredential, input api.ScalingGroupListInput) (*sqlchemy.SQuery, error) {
  268. // hack
  269. // vpc and backendgroup may be empty, and these subresoruce shouldn't be fiter by brand
  270. brand := input.Brand
  271. input.Brand = ""
  272. q, err := sgm.SCloudregionResourceBaseManager.ListItemFilter(ctx, q, userCred, input.RegionalFilterListInput)
  273. if err != nil {
  274. return q, err
  275. }
  276. q, err = sgm.SVpcResourceBaseManager.ListItemFilter(ctx, q, userCred, input.VpcFilterListInput)
  277. if err != nil {
  278. return q, err
  279. }
  280. q, err = sgm.SLoadbalancerBackendgroupResourceBaseManager.ListItemFilter(ctx, q, userCred, input.LoadbalancerBackendGroupFilterListInput)
  281. if err != nil {
  282. return q, err
  283. }
  284. q, err = sgm.SGroupResourceBaseManager.ListItemFilter(ctx, q, userCred, input.GroupFilterListInput)
  285. if err != nil {
  286. return q, err
  287. }
  288. q, err = sgm.SGuestTemplateResourceBaseManager.ListItemFilter(ctx, q, userCred, input.GuestTemplateFilterListInput)
  289. if err != nil {
  290. return q, err
  291. }
  292. q, err = sgm.SEnabledResourceBaseManager.ListItemFilter(ctx, q, userCred, input.EnabledResourceBaseListInput)
  293. if err != nil {
  294. return q, err
  295. }
  296. if len(input.Hypervisor) > 0 {
  297. q = q.Equals("hypervisor", input.Hypervisor)
  298. }
  299. if len(brand) > 0 {
  300. q = q.Equals("hypervisor", Brand2Hypervisor(input.Brand))
  301. }
  302. input.Brand = brand
  303. return q, nil
  304. }
  305. func (sgm *SScalingGroupManager) FetchCustomizeColumns(
  306. ctx context.Context,
  307. userCred mcclient.TokenCredential,
  308. query jsonutils.JSONObject,
  309. objs []interface{},
  310. fields stringutils2.SSortedStrings,
  311. isList bool,
  312. ) []api.ScalingGroupDetails {
  313. virtRows := sgm.SVirtualResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList)
  314. crRows := sgm.SCloudregionResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList)
  315. vpcRows := sgm.SVpcResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList)
  316. lbbgRows := sgm.SLoadbalancerBackendgroupResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList)
  317. groupRows := sgm.SGroupResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList)
  318. gtRows := sgm.SGuestTemplateResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList)
  319. rows := make([]api.ScalingGroupDetails, len(objs))
  320. for i := range rows {
  321. rows[i] = api.ScalingGroupDetails{
  322. VirtualResourceDetails: virtRows[i],
  323. CloudregionResourceInfo: crRows[i],
  324. LoadbalancerBackendGroupResourceInfo: lbbgRows[i],
  325. VpcResourceInfo: vpcRows[i],
  326. GroupResourceInfo: groupRows[i],
  327. GuestTemplateResourceInfo: gtRows[i],
  328. }
  329. sg := objs[i].(*SScalingGroup)
  330. n, _ := sg.GuestNumber()
  331. rows[i].InstanceNumber = n
  332. n, _ = sg.ScalingPolicyNumber()
  333. rows[i].ScalingPolicyNumber = n
  334. rows[i].Brand = Hypervisor2Brand(sg.Hypervisor)
  335. nets, err := sg.Networks()
  336. if err != nil {
  337. log.Errorf("sg.Networks error: %s", err)
  338. continue
  339. }
  340. sgNets := make([]api.ScalingGroupNetwork, 0, len(nets))
  341. for i := range nets {
  342. sgNets = append(sgNets, api.ScalingGroupNetwork{
  343. Id: nets[i].GetId(),
  344. Name: nets[i].GetName(),
  345. GuestIpStart: nets[i].GuestIpStart,
  346. GuestIpEnd: nets[i].GuestIpEnd,
  347. })
  348. }
  349. rows[i].Networks = sgNets
  350. }
  351. return rows
  352. }
  353. func (sg *SScalingGroup) GuestNumber() (int, error) {
  354. q := GuestManager.Query().In("id", ScalingGroupGuestManager.Query("guest_id").Equals("scaling_group_id",
  355. sg.Id).SubQuery()).IsFalse("pending_deleted")
  356. return q.CountWithError()
  357. }
  358. func (sg *SScalingGroup) ScalingPolicyNumber() (int, error) {
  359. q := ScalingPolicyManager.Query().Equals("scaling_group_id", sg.Id)
  360. return q.CountWithError()
  361. }
  362. /*func (sg *SScalingGroup) RemoveAllGuests(ctx context.Context, userCred mcclient.TokenCredential) error {
  363. q := ScalingGroupGuestManager.Query().Equals("scaling_group_id", sg.Id)
  364. scalingGroupGuests := make([]SScalingGroupGuest, 0)
  365. err := db.FetchModelObjects(ScalingGroupGuestManager, q, &scalingGroupGuests)
  366. if err != nil {
  367. return errors.Wrap(err, "db.FetchModelObjects")
  368. }
  369. for i := range scalingGroupGuests {
  370. err := scalingGroupGuests[i].Delete(ctx, userCred)
  371. return errors.Wrapf(err, "delete ScalingGroupGuests(ScalingGroupId: '%s', GuestId: '%s') failed",
  372. scalingGroupGuests[i].ScalingGroupId, scalingGroupGuests[i].GuestId)
  373. }
  374. return nil
  375. }*/
  376. func (sg *SScalingGroup) ScalingGroupGuests(guestIds []string) ([]SScalingGroupGuest, error) {
  377. q := ScalingGroupGuestManager.Query().Equals("scaling_group_id", sg.GetId())
  378. if len(guestIds) == 0 {
  379. return nil, nil
  380. }
  381. if len(guestIds) == 1 {
  382. q = q.Equals("guest_id", guestIds[0])
  383. }
  384. if len(guestIds) > 1 {
  385. q = q.In("guest_id", guestIds)
  386. }
  387. sggs := make([]SScalingGroupGuest, 0, len(guestIds))
  388. err := db.FetchModelObjects(ScalingGroupGuestManager, q, &sggs)
  389. return sggs, err
  390. }
  391. func (sg *SScalingGroup) Guests() ([]SGuest, error) {
  392. q := GuestManager.Query().In("id", ScalingGroupGuestManager.Query("guest_id").Equals("scaling_group_id", sg.Id).SubQuery())
  393. guests := make([]SGuest, 0, 1)
  394. err := db.FetchModelObjects(GuestManager, q, &guests)
  395. if err != nil {
  396. return nil, errors.Wrap(err, "db.FetchModelObjects")
  397. }
  398. return guests, nil
  399. }
  400. type sExecResult struct {
  401. // 0: success; 1: part success; 2: reject; 3: fail
  402. code uint8
  403. actionStr string
  404. reason string
  405. intanceNum int
  406. }
  407. func (sg *SScalingGroup) exec(ctx context.Context, action IScalingAction) (ret sExecResult) {
  408. ret.code = 3
  409. ret.intanceNum = -1
  410. // query again to fetch the latest desire instance number of sg
  411. model, err := ScalingGroupManager.FetchById(sg.Id)
  412. if err != nil {
  413. ret.reason = fmt.Sprintf("fail to get ScalingGroup: %s", err.Error())
  414. return
  415. }
  416. sg = model.(*SScalingGroup)
  417. targetNum := action.Exec(sg.DesireInstanceNumber)
  418. // targetNum must between sg.MinInstanceNumber and sg.MaxInstanceNumber
  419. ret.code = 0
  420. if targetNum > sg.MaxInstanceNumber {
  421. if sg.DesireInstanceNumber == sg.MaxInstanceNumber {
  422. ret.code = 2
  423. ret.reason = fmt.Sprintf(
  424. `Want to change the Desired Instance Number from "%d" to "%d", but the Desired Instance Number has reached the Max Instance Number`,
  425. sg.DesireInstanceNumber, targetNum,
  426. )
  427. return
  428. }
  429. ret.code = 1
  430. ret.reason = fmt.Sprintf(
  431. `Want to change the Desired Instance Number from "%d" to "%d", but "%d" is greater than the Max Instance Number "%d"`,
  432. sg.DesireInstanceNumber, targetNum, targetNum, sg.MaxInstanceNumber,
  433. )
  434. targetNum = sg.MaxInstanceNumber
  435. } else if targetNum < sg.MinInstanceNumber {
  436. if sg.DesireInstanceNumber == sg.MinInstanceNumber {
  437. ret.code = 2
  438. ret.reason = fmt.Sprintf(
  439. `Want to change the Desired Instance Number from "%d" to "%d", but the Desired Instance Number has reached the Min Instance Number`,
  440. sg.DesireInstanceNumber, targetNum,
  441. )
  442. return
  443. }
  444. ret.code = 1
  445. ret.reason = fmt.Sprintf(
  446. `Want to change the Desired Instance Number from "%d" to "%d", but "%d" is less than the Min Instance Number "%d"`,
  447. sg.DesireInstanceNumber, targetNum, targetNum, sg.MinInstanceNumber,
  448. )
  449. targetNum = sg.MinInstanceNumber
  450. }
  451. ret.actionStr = fmt.Sprintf(`Change the Desired Instance Number from "%d" to "%d"`, sg.DesireInstanceNumber, targetNum)
  452. _, err = db.Update(sg, func() error {
  453. sg.DesireInstanceNumber = targetNum
  454. return nil
  455. })
  456. if err != nil {
  457. ret.code = 3
  458. ret.reason = fmt.Sprintf("Update ScalingGroup's DesireInstanceNumber failed: %s", err.Error())
  459. }
  460. return
  461. }
  462. var CoolingTimeLocation, _ = time.LoadLocation("Asia/Shanghai")
  463. // Scale will modify SScalingGroup.DesireInstanceNumber and generate SScalingActivity based on the trigger and its
  464. // corresponding SScalingPolicy.
  465. func (sg *SScalingGroup) Scale(ctx context.Context, triggerDesc IScalingTriggerDesc, action IScalingAction,
  466. coolingTime int) error {
  467. lockman.LockObject(ctx, sg)
  468. defer lockman.ReleaseObject(ctx, sg)
  469. isExec := false
  470. defer func() {
  471. if isExec && coolingTime > 0 {
  472. sg.SetAllowScaleTime(time.Now().Add(time.Duration(coolingTime) * time.Second))
  473. }
  474. }()
  475. if sg.Enabled.IsFalse() {
  476. return nil
  477. }
  478. scalingActivity, err := ScalingActivityManager.CreateScalingActivity(ctx, sg.Id, triggerDesc.TriggerDescription(), api.SA_STATUS_EXEC)
  479. if err != nil {
  480. return errors.Wrapf(err, "create ScalingActivity whose ScalingGroup is %s error", sg.Id)
  481. }
  482. if action.CheckCoolTime() && !sg.AllowScale() {
  483. err = scalingActivity.SetReject("",
  484. fmt.Sprintf("The Cooling Time limit the execution time of the policy to at least: %s",
  485. sg.AllowScaleTime.In(CoolingTimeLocation).Format("2006-01-02 15:04:05 -0700")))
  486. return nil
  487. }
  488. ret := sg.exec(ctx, action)
  489. switch ret.code {
  490. case 0:
  491. err = scalingActivity.SetResult(ret.actionStr, api.SA_STATUS_SUCCEED, "", ret.intanceNum)
  492. isExec = true
  493. case 1:
  494. err = scalingActivity.SetResult(ret.actionStr, api.SA_STATUS_PART_SUCCEED, ret.reason, ret.intanceNum)
  495. isExec = true
  496. case 2:
  497. err = scalingActivity.SetReject("", ret.reason)
  498. case 3:
  499. err = scalingActivity.SetFailed(ret.actionStr, ret.reason)
  500. }
  501. if err != nil {
  502. log.Errorf("ScalingActivity set result failed: %s", err.Error())
  503. }
  504. return nil
  505. }
  506. func (sgm *SScalingGroupManager) QueryDistinctExtraField(q *sqlchemy.SQuery, field string) (*sqlchemy.SQuery, error) {
  507. q, err := sgm.SVirtualResourceBaseManager.QueryDistinctExtraField(q, field)
  508. if err == nil {
  509. return q, nil
  510. }
  511. q, err = sgm.SCloudregionResourceBaseManager.QueryDistinctExtraField(q, field)
  512. if err == nil {
  513. return q, nil
  514. }
  515. q, err = sgm.SVpcResourceBaseManager.QueryDistinctExtraField(q, field)
  516. if err == nil {
  517. return q, nil
  518. }
  519. q, err = sgm.SLoadbalancerBackendgroupResourceBaseManager.QueryDistinctExtraField(q, field)
  520. if err == nil {
  521. return q, nil
  522. }
  523. q, err = sgm.SGroupResourceBaseManager.QueryDistinctExtraField(q, field)
  524. if err == nil {
  525. return q, nil
  526. }
  527. q, err = sgm.SGuestTemplateResourceBaseManager.QueryDistinctExtraField(q, field)
  528. if err == nil {
  529. return q, nil
  530. }
  531. scalinggroupGuests := ScalingGroupGuestManager.Query("scaling_group_id", "guest_id").SubQuery()
  532. q = q.LeftJoin(scalinggroupGuests, sqlchemy.Equals(q.Field("id"), scalinggroupGuests.Field("scaling_group_id")))
  533. q, err = sgm.SGuestResourceBaseManager.QueryDistinctExtraField(q, field)
  534. if err == nil {
  535. return q, nil
  536. }
  537. return q, httperrors.ErrNotFound
  538. }
  539. func (manager *SScalingGroupManager) OrderByExtraFields(ctx context.Context, q *sqlchemy.SQuery,
  540. userCred mcclient.TokenCredential, query api.ScalingGroupListInput) (*sqlchemy.SQuery, error) {
  541. q, err := manager.SVirtualResourceBaseManager.OrderByExtraFields(ctx, q, userCred, query.VirtualResourceListInput)
  542. if err != nil {
  543. return nil, err
  544. }
  545. q, err = manager.SVpcResourceBaseManager.OrderByExtraFields(ctx, q, userCred, query.VpcFilterListInput)
  546. if err != nil {
  547. return nil, err
  548. }
  549. q, err = manager.SLoadbalancerBackendgroupResourceBaseManager.OrderByExtraFields(ctx, q, userCred, query.LoadbalancerBackendGroupFilterListInput)
  550. if err != nil {
  551. return nil, err
  552. }
  553. q, err = manager.SGroupResourceBaseManager.OrderByExtraFields(ctx, q, userCred, query.GroupFilterListInput)
  554. if err != nil {
  555. return nil, err
  556. }
  557. return q, nil
  558. }
  559. func (sg *SScalingGroup) PostCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) {
  560. // attach with networks
  561. networks, _ := data.Get("networks")
  562. networkIds := networks.(*jsonutils.JSONArray).GetStringArray()
  563. for _, netId := range networkIds {
  564. err := ScalingGroupNetworkManager.Attach(ctx, sg.Id, netId)
  565. if err != nil {
  566. reason := fmt.Sprintf("Attach ScalingGroup '%s' with Network '%s' failed: %s", sg.Id, netId, err.Error())
  567. sg.SetStatus(ctx, userCred, api.SG_STATUS_CREATE_FAILED, reason)
  568. logclient.AddActionLogWithContext(ctx, sg, logclient.ACT_CREATE, reason, userCred, false)
  569. return
  570. }
  571. }
  572. now := time.Now()
  573. db.Update(sg, func() error {
  574. sg.Status = api.SG_STATUS_READY
  575. sg.AllowScaleTime = now
  576. sg.NextCheckTime = now.Add(time.Duration(sg.HealthCheckCycle) * time.Second)
  577. sg.SetEnabled(true)
  578. return nil
  579. })
  580. logclient.AddActionLogWithContext(ctx, sg, logclient.ACT_CREATE, "", userCred, true)
  581. notifyclient.EventNotify(ctx, userCred, notifyclient.SEventNotifyParam{
  582. Obj: sg,
  583. Action: notifyclient.ActionCreate,
  584. })
  585. }
  586. func (sg *SScalingGroup) AllowScale() bool {
  587. return sg.AllowScaleTime.Before(time.Now())
  588. }
  589. func (sg *SScalingGroup) SetAllowScaleTime(t time.Time) {
  590. if sg.AllowScaleTime.After(t) {
  591. return
  592. }
  593. _, err := db.Update(sg, func() error {
  594. sg.AllowScaleTime = t
  595. return nil
  596. })
  597. if err != nil {
  598. log.Errorf("Set AllowScaleTime error: %s", err)
  599. }
  600. return
  601. }
  602. func (sg *SScalingGroup) PerformEnable(ctx context.Context, userCred mcclient.TokenCredential,
  603. query jsonutils.JSONObject, input apis.PerformEnableInput) (jsonutils.JSONObject, error) {
  604. err := db.EnabledPerformEnable(sg, ctx, userCred, true)
  605. if err != nil {
  606. return nil, errors.Wrap(err, "EnabledPerformEnable")
  607. }
  608. return nil, nil
  609. }
  610. func (sg *SScalingGroup) PerformDisable(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject,
  611. input apis.PerformDisableInput) (jsonutils.JSONObject, error) {
  612. err := db.EnabledPerformEnable(sg, ctx, userCred, false)
  613. if err != nil {
  614. return nil, errors.Wrap(err, "EnabledPerformEnable")
  615. }
  616. return nil, nil
  617. }
  618. func (s *SGuest) PerformDetachScalingGroup(ctx context.Context, userCred mcclient.TokenCredential,
  619. query jsonutils.JSONObject, input api.SGPerformDetachScalingGroupInput) (jsonutils.JSONObject, error) {
  620. // check ScalingGroup
  621. model, err := ScalingGroupManager.FetchByIdOrName(ctx, userCred, input.ScalingGroup)
  622. if err != nil {
  623. if errors.Cause(err) == sql.ErrNoRows {
  624. return nil, httperrors.NewInputParameterError("no such ScalingGroup '%s'", input.ScalingGroup)
  625. }
  626. return nil, errors.Wrap(err, "ScalingGroupManager.FetchByIdOrName")
  627. }
  628. // check ScalingGroupGuest
  629. sggs, err := ScalingGroupGuestManager.Fetch(model.GetId(), s.Id)
  630. if err != nil {
  631. if errors.Cause(err) == sql.ErrNoRows {
  632. return nil, httperrors.NewInputParameterError("Guest '%s' don't belong to ScalingGroup '%s'", s.Id, model.GetId())
  633. }
  634. return nil, errors.Wrap(err, "ScalingGroupGuestManager.Fetch")
  635. }
  636. if len(sggs) == 0 {
  637. return nil, httperrors.NewInputParameterError("Guest '%s' don't belong to ScalingGroup '%s'", s.Id, model.GetId())
  638. }
  639. sg := model.(*SScalingGroup)
  640. input.ScalingGroup = sggs[0].ScalingGroupId
  641. sggs[0].SetGuestStatus(api.SG_GUEST_STATUS_REMOVING)
  642. taskData := jsonutils.Marshal(input).(*jsonutils.JSONDict)
  643. taskData.Set("guest", jsonutils.NewString(s.GetId()))
  644. task, err := taskman.TaskManager.NewTask(ctx, "GuestDetachScalingGroupTask", sg, userCred, taskData, "", "")
  645. if err != nil {
  646. return nil, errors.Wrap(err, "Start GuestDetachScalingGroupTask failed")
  647. }
  648. task.ScheduleRun(nil)
  649. return nil, nil
  650. }
  651. func (sg *SScalingGroup) Networks() ([]SNetwork, error) {
  652. nets := make([]SNetwork, 0, 1)
  653. sgnQuery := ScalingGroupNetworkManager.Query("network_id").Equals("scaling_group_id", sg.Id).SubQuery()
  654. netQuery := NetworkManager.Query().In("id", sgnQuery)
  655. err := db.FetchModelObjects(NetworkManager, netQuery, &nets)
  656. if err != nil {
  657. return nil, errors.Wrap(err, "db.FetchModelObjects")
  658. }
  659. return nets, nil
  660. }
  661. func (sg *SScalingGroup) NetworkIds() ([]string, error) {
  662. sgnQuery := ScalingGroupNetworkManager.Query("network_id").Equals("scaling_group_id", sg.Id)
  663. rows, err := sgnQuery.Rows()
  664. if err != nil {
  665. return nil, errors.Wrap(err, "SQuery.Rows")
  666. }
  667. defer rows.Close()
  668. nets := make([]string, 0, 1)
  669. for rows.Next() {
  670. var net string
  671. rows.Scan(&net)
  672. nets = append(nets, net)
  673. }
  674. return nets, nil
  675. }
  676. func (sg *SScalingGroup) Activities() ([]SScalingActivity, error) {
  677. q := ScalingActivityManager.Query().Equals("scaling_group_id", sg.Id)
  678. activities := make([]SScalingActivity, 0, 1)
  679. err := db.FetchModelObjects(ScalingActivityManager, q, &activities)
  680. if err != nil {
  681. return nil, errors.Wrap(err, "db.FetchModelObjects")
  682. }
  683. return activities, nil
  684. }
  685. func (manager *SScalingGroupManager) ListItemExportKeys(ctx context.Context,
  686. q *sqlchemy.SQuery,
  687. userCred mcclient.TokenCredential,
  688. keys stringutils2.SSortedStrings,
  689. ) (*sqlchemy.SQuery, error) {
  690. var err error
  691. q, err = manager.SVirtualResourceBaseManager.ListItemExportKeys(ctx, q, userCred, keys)
  692. if err != nil {
  693. return nil, errors.Wrap(err, "SVirtualResourceBaseManager.ListItemExportKeys")
  694. }
  695. if keys.ContainsAny(manager.SVpcResourceBaseManager.GetExportKeys()...) {
  696. // SCloudregionResourceBaseManager
  697. q, err = manager.SVpcResourceBaseManager.ListItemExportKeys(ctx, q, userCred, keys)
  698. if err != nil {
  699. return nil, errors.Wrap(err, "SVpcResourceBaseManager.ListItemExportKeys")
  700. }
  701. }
  702. if keys.ContainsAny(manager.SLoadbalancerBackendgroupResourceBaseManager.GetExportKeys()...) {
  703. q, err = manager.SLoadbalancerBackendgroupResourceBaseManager.ListItemExportKeys(ctx, q, userCred, keys)
  704. if err != nil {
  705. return nil, errors.Wrap(err, "SLoadbalancerBackendgroupResourceBaseManager.ListItemExportKeys")
  706. }
  707. }
  708. if keys.ContainsAny(manager.SGroupResourceBaseManager.GetExportKeys()...) {
  709. q, err = manager.SGroupResourceBaseManager.ListItemExportKeys(ctx, q, userCred, keys)
  710. if err != nil {
  711. return nil, errors.Wrap(err, "SGroupResourceBaseManager.ListItemExportKeys")
  712. }
  713. }
  714. if keys.ContainsAny(manager.SGuestTemplateResourceBaseManager.GetExportKeys()...) {
  715. q, err = manager.SGuestTemplateResourceBaseManager.ListItemExportKeys(ctx, q, userCred, keys)
  716. if err != nil {
  717. return nil, errors.Wrap(err, "SGuestTemplateResourceBaseManager.ListItemExportKeys")
  718. }
  719. }
  720. return q, nil
  721. }