elasticcache_skus.go 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865
  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. "strings"
  20. "sync"
  21. "yunion.io/x/cloudmux/pkg/cloudprovider"
  22. "yunion.io/x/jsonutils"
  23. "yunion.io/x/log"
  24. "yunion.io/x/pkg/errors"
  25. "yunion.io/x/pkg/util/compare"
  26. "yunion.io/x/sqlchemy"
  27. "yunion.io/x/onecloud/pkg/apis"
  28. "yunion.io/x/onecloud/pkg/apis/billing"
  29. api "yunion.io/x/onecloud/pkg/apis/compute"
  30. "yunion.io/x/onecloud/pkg/cloudcommon/db"
  31. "yunion.io/x/onecloud/pkg/cloudcommon/db/lockman"
  32. "yunion.io/x/onecloud/pkg/cloudcommon/policy"
  33. "yunion.io/x/onecloud/pkg/cloudcommon/validators"
  34. "yunion.io/x/onecloud/pkg/compute/options"
  35. "yunion.io/x/onecloud/pkg/httperrors"
  36. "yunion.io/x/onecloud/pkg/mcclient"
  37. "yunion.io/x/onecloud/pkg/util/stringutils2"
  38. "yunion.io/x/onecloud/pkg/util/yunionmeta"
  39. )
  40. // +onecloud:swagger-gen-model-singular=elasticcachesku
  41. // +onecloud:swagger-gen-model-plural=elasticcacheskus
  42. type SElasticcacheSkuManager struct {
  43. db.SStatusStandaloneResourceBaseManager
  44. db.SExternalizedResourceBaseManager
  45. SCloudregionResourceBaseManager
  46. SZoneResourceBaseManager
  47. }
  48. var ElasticcacheSkuManager *SElasticcacheSkuManager
  49. func init() {
  50. ElasticcacheSkuManager = &SElasticcacheSkuManager{
  51. SStatusStandaloneResourceBaseManager: db.NewStatusStandaloneResourceBaseManager(
  52. SElasticcacheSku{},
  53. "elasticcacheskus_tbl",
  54. "elasticcachesku",
  55. "elasticcacheskus",
  56. ),
  57. }
  58. ElasticcacheSkuManager.NameRequireAscii = false
  59. ElasticcacheSkuManager.SetVirtualObject(ElasticcacheSkuManager)
  60. }
  61. type SElasticcacheSku struct {
  62. db.SStatusStandaloneResourceBase
  63. db.SExternalizedResourceBase
  64. SCloudregionResourceBase // 区域
  65. SZoneResourceBase // 主可用区
  66. SlaveZoneId string `width:"64" charset:"ascii" nullable:"false" list:"user" create:"admin_optional" update:"admin"` // 备可用区
  67. InstanceSpec string `width:"96" charset:"ascii" nullable:"false" list:"user" create:"admin_optional" update:"admin"`
  68. EngineArch string `width:"32" charset:"utf8" nullable:"false" list:"user" create:"admin_optional" update:"admin"`
  69. LocalCategory string `width:"32" charset:"utf8" nullable:"false" list:"user" create:"admin_optional" update:"admin" default:""`
  70. PrepaidStatus string `width:"32" charset:"utf8" nullable:"false" list:"user" create:"admin_optional" update:"admin" default:"available"`
  71. PostpaidStatus string `width:"32" charset:"utf8" nullable:"false" list:"user" create:"admin_optional" update:"admin" default:"available"`
  72. // 引擎 redis|memcached
  73. Engine string `width:"16" charset:"ascii" nullable:"false" list:"user" create:"admin_optional" update:"admin"`
  74. // 引擎版本 3.0
  75. EngineVersion string `width:"16" charset:"ascii" nullable:"false" list:"user" create:"admin_optional" update:"admin"`
  76. // CPU 架构 x86|ARM
  77. CpuArch string `width:"16" charset:"ascii" nullable:"false" list:"user" create:"admin_optional" update:"admin"`
  78. // 存储类型 DRAM|SCM
  79. StorageType string `width:"16" charset:"ascii" nullable:"false" list:"user" create:"admin_optional" update:"admin"`
  80. // standrad|enhanced
  81. PerformanceType string `width:"16" charset:"ascii" nullable:"false" list:"user" create:"admin_optional" update:"admin"`
  82. // single(单副本) | double(双副本) | readone (单可读) | readthree (3可读) | readfive(5只读)
  83. NodeType string `width:"16" charset:"ascii" nullable:"false" list:"user" create:"admin_optional" update:"admin"`
  84. // 内存容量
  85. MemorySizeMB int `nullable:"false" list:"user" create:"admin_required" update:"admin"`
  86. // 套餐附带硬盘容量
  87. DiskSizeGB int `nullable:"false" list:"user" create:"admin_required" update:"admin"`
  88. // 最小分片数量
  89. ShardNum int `nullable:"false" list:"user" create:"admin_optional" update:"admin"`
  90. // 最大分片数量
  91. MaxShardNum int `nullable:"false" list:"user" create:"admin_optional" update:"admin"`
  92. // 最小副本数量
  93. ReplicasNum int `nullable:"false" list:"user" create:"admin_optional" update:"admin"`
  94. // 最大副本数量
  95. MaxReplicasNum int `nullable:"false" list:"user" create:"admin_optional" update:"admin"`
  96. // 最大客户端数
  97. MaxClients int `nullable:"false" list:"user" create:"admin_optional" update:"admin"`
  98. // 最大连接数
  99. MaxConnections int `nullable:"false" list:"user" create:"admin_optional" update:"admin"`
  100. // 最大内网带宽
  101. MaxInBandwidthMb int `nullable:"false" list:"user" create:"admin_optional" update:"admin"`
  102. // 实际可使用的最大内存
  103. MaxMemoryMB int `nullable:"false" list:"user" create:"admin_optional" update:"admin"`
  104. // QPS参考值
  105. QPS int `nullable:"false" list:"user" create:"admin_optional" update:"admin"`
  106. // 公有云厂商 Aliyun/Azure/AWS/Qcloud/...
  107. Provider string `width:"32" charset:"ascii" nullable:"false" list:"user" create:"admin_required" update:"admin"`
  108. }
  109. func (self SElasticcacheSku) GetGlobalId() string {
  110. return self.ExternalId
  111. }
  112. func (manager *SElasticcacheSkuManager) FetchCustomizeColumns(
  113. ctx context.Context,
  114. userCred mcclient.TokenCredential,
  115. query jsonutils.JSONObject,
  116. objs []interface{},
  117. fields stringutils2.SSortedStrings,
  118. isList bool,
  119. ) []api.ElasticcacheSkuDetails {
  120. rows := make([]api.ElasticcacheSkuDetails, len(objs))
  121. stdRows := manager.SStatusStandaloneResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList)
  122. regRows := manager.SCloudregionResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList)
  123. zoneRows := manager.SZoneResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList)
  124. slavezoneRows := manager.FetchSlaveZoneResourceInfos(ctx, userCred, query, objs)
  125. for i := range rows {
  126. rows[i] = api.ElasticcacheSkuDetails{
  127. StatusStandaloneResourceDetails: stdRows[i],
  128. CloudregionResourceInfo: regRows[i],
  129. ZoneResourceInfoBase: zoneRows[i].ZoneResourceInfoBase,
  130. SlaveZoneResourceInfoBase: slavezoneRows[i],
  131. }
  132. rows[i].CloudEnv = strings.Split(regRows[i].RegionExternalId, "/")[0]
  133. }
  134. return rows
  135. }
  136. func (self *SElasticcacheSkuManager) FetchSlaveZoneResourceInfos(ctx context.Context,
  137. userCred mcclient.TokenCredential,
  138. query jsonutils.JSONObject,
  139. objs []interface{}) []api.SlaveZoneResourceInfoBase {
  140. rows := make([]api.SlaveZoneResourceInfoBase, len(objs))
  141. zoneIds := []string{}
  142. for i := range objs {
  143. slavezone := objs[i].(*SElasticcacheSku).SlaveZoneId
  144. if len(slavezone) > 0 {
  145. zoneIds = append(zoneIds, slavezone)
  146. }
  147. }
  148. zones := make(map[string]SZone)
  149. err := db.FetchStandaloneObjectsByIds(ZoneManager, zoneIds, &zones)
  150. if err != nil {
  151. log.Errorf("FetchStandaloneObjectsByIds fail %s", err)
  152. return rows
  153. }
  154. for i := range objs {
  155. if zone, ok := zones[objs[i].(*SElasticcacheSku).SlaveZoneId]; ok {
  156. rows[i].SlaveZone = zone.GetName()
  157. rows[i].SlaveZoneExtId = fetchExternalId(zone.GetExternalId())
  158. }
  159. }
  160. return rows
  161. }
  162. func (manager *SElasticcacheSkuManager) GetSkuCountByRegion(regionId string) (int, error) {
  163. q := manager.Query().Equals("cloudregion_id", regionId)
  164. return q.CountWithError()
  165. }
  166. // 弹性缓存套餐规格列表
  167. func (manager *SElasticcacheSkuManager) ListItemFilter(
  168. ctx context.Context,
  169. q *sqlchemy.SQuery,
  170. userCred mcclient.TokenCredential,
  171. query api.ElasticcacheSkuListInput,
  172. ) (*sqlchemy.SQuery, error) {
  173. q, err := manager.SStatusStandaloneResourceBaseManager.ListItemFilter(ctx, q, userCred, query.StatusStandaloneResourceListInput)
  174. if err != nil {
  175. return nil, errors.Wrap(err, "SStatusStandaloneResourceBaseManager.ListItemFilter")
  176. }
  177. q, err = manager.SExternalizedResourceBaseManager.ListItemFilter(ctx, q, userCred, query.ExternalizedResourceBaseListInput)
  178. if err != nil {
  179. return nil, errors.Wrap(err, "SExternalizedResourceBaseManager.ListItemFilter")
  180. }
  181. q, err = manager.SCloudregionResourceBaseManager.ListItemFilter(ctx, q, userCred, query.RegionalFilterListInput)
  182. if err != nil {
  183. return nil, errors.Wrap(err, "SCloudregionResourceBaseManager.ListItemFilter")
  184. }
  185. zoneQuery := api.ZonalFilterListInput{
  186. ZonalFilterListBase: query.ZonalFilterListBase,
  187. }
  188. q, err = manager.SZoneResourceBaseManager.ListItemFilter(ctx, q, userCred, zoneQuery)
  189. if err != nil {
  190. return nil, errors.Wrap(err, "SZoneResourceBaseManager.ListItemFilter")
  191. }
  192. if query.Usable != nil && *query.Usable {
  193. q, err = usableFilter(q, true)
  194. if err != nil {
  195. return nil, err
  196. }
  197. sq := sqlchemy.OR(sqlchemy.Equals(q.Field("prepaid_status"), api.SkuStatusAvailable), sqlchemy.Equals(q.Field("postpaid_status"), api.SkuStatusAvailable))
  198. q = q.Filter(sq)
  199. }
  200. if b := query.BillingType; len(b) > 0 {
  201. switch b {
  202. case billing.BILLING_TYPE_POSTPAID:
  203. q = q.Equals("postpaid_status", api.SkuStatusAvailable)
  204. case billing.BILLING_TYPE_PREPAID:
  205. q = q.Equals("prepaid_status", api.SkuStatusAvailable)
  206. }
  207. }
  208. if domainStr := query.ProjectDomainId; len(domainStr) > 0 {
  209. domain, err := db.TenantCacheManager.FetchDomainByIdOrName(context.Background(), domainStr)
  210. if err != nil {
  211. if errors.Cause(err) == sql.ErrNoRows {
  212. return nil, httperrors.NewResourceNotFoundError2("domains", domainStr)
  213. }
  214. return nil, httperrors.NewGeneralError(err)
  215. }
  216. query.ProjectDomainId = domain.GetId()
  217. }
  218. q = listItemDomainFilter(q, query.Providers, query.ProjectDomainId)
  219. // 按区间查询内存, 避免0.75G这样的套餐不好过滤
  220. memSizeMB := query.MemorySizeMb
  221. if memSizeMB > 0 {
  222. s, e := intervalMem(int(memSizeMB))
  223. q.GT("memory_size_mb", s)
  224. q.LE("memory_size_mb", e)
  225. }
  226. if len(query.InstanceSpec) > 0 {
  227. q = q.In("instance_spec", query.InstanceSpec)
  228. }
  229. if len(query.EngineArch) > 0 {
  230. q = q.In("engine_arch", query.EngineArch)
  231. }
  232. if len(query.LocalCategory) > 0 {
  233. q = q.In("local_category", query.LocalCategory)
  234. }
  235. if len(query.PrepaidStatus) > 0 {
  236. q = q.In("prepaid_status", query.PrepaidStatus)
  237. }
  238. if len(query.PostpaidStatus) > 0 {
  239. q = q.In("postpaid_sStatus", query.PostpaidStatus)
  240. }
  241. if len(query.Engine) > 0 {
  242. q = q.In("engine", query.Engine)
  243. }
  244. if len(query.EngineVersion) > 0 {
  245. q = q.In("engine_version", query.EngineVersion)
  246. }
  247. if len(query.CpuArch) > 0 {
  248. q = q.In("cpu_arch", query.CpuArch)
  249. }
  250. if len(query.StorageType) > 0 {
  251. q = q.In("storage_type", query.StorageType)
  252. }
  253. if len(query.PerformanceType) > 0 {
  254. q = q.In("performance_type", query.PerformanceType)
  255. }
  256. if len(query.NodeType) > 0 {
  257. q = q.In("node_type", query.NodeType)
  258. }
  259. if len(query.Providers) > 0 {
  260. q = q.In("provider", query.Providers)
  261. }
  262. return q, nil
  263. }
  264. func (manager *SElasticcacheSkuManager) OrderByExtraFields(
  265. ctx context.Context,
  266. q *sqlchemy.SQuery,
  267. userCred mcclient.TokenCredential,
  268. query api.ElasticcacheSkuListInput,
  269. ) (*sqlchemy.SQuery, error) {
  270. var err error
  271. q, err = manager.SStatusStandaloneResourceBaseManager.OrderByExtraFields(ctx, q, userCred, query.StatusStandaloneResourceListInput)
  272. if err != nil {
  273. return nil, errors.Wrap(err, "SStatusStandaloneResourceBaseManager.OrderByExtraFields")
  274. }
  275. q, err = manager.SCloudregionResourceBaseManager.OrderByExtraFields(ctx, q, userCred, query.RegionalFilterListInput)
  276. if err != nil {
  277. return nil, errors.Wrap(err, "SCloudregionResourceBaseManager.OrderByExtraFields")
  278. }
  279. q, err = manager.SZoneResourceBaseManager.OrderByExtraFields(ctx, q, userCred, query.ZonalFilterListInput)
  280. if err != nil {
  281. return nil, errors.Wrap(err, "SZoneResourceBaseManager.OrderByExtraFields")
  282. }
  283. return q, nil
  284. }
  285. func (manager *SElasticcacheSkuManager) QueryDistinctExtraField(q *sqlchemy.SQuery, field string) (*sqlchemy.SQuery, error) {
  286. var err error
  287. q, err = manager.SStatusStandaloneResourceBaseManager.QueryDistinctExtraField(q, field)
  288. if err == nil {
  289. return q, nil
  290. }
  291. q, err = manager.SCloudregionResourceBaseManager.QueryDistinctExtraField(q, field)
  292. if err == nil {
  293. return q, nil
  294. }
  295. q, err = manager.SZoneResourceBaseManager.QueryDistinctExtraField(q, field)
  296. if err == nil {
  297. return q, nil
  298. }
  299. return q, httperrors.ErrNotFound
  300. }
  301. // 获取region下所有Available状态的sku id
  302. func (manager *SElasticcacheSkuManager) FetchSkusByRegion(regionID string) ([]SElasticcacheSku, error) {
  303. q := manager.Query()
  304. q = q.Equals("cloudregion_id", regionID)
  305. skus := make([]SElasticcacheSku, 0)
  306. err := db.FetchModelObjects(manager, q, &skus)
  307. if err != nil {
  308. return nil, errors.Wrap(err, "ElasticcacheSkuManager.FetchSkusByRegion")
  309. }
  310. return skus, nil
  311. }
  312. func (self *SElasticcacheSku) GetElasticcacheCount() (int, error) {
  313. q := ElasticcacheManager.Query().Equals("instance_type", self.Name).Equals("zone_id", self.ZoneId)
  314. return q.CountWithError()
  315. }
  316. func (manager *SElasticcacheSkuManager) SyncElasticcacheSkus(ctx context.Context, userCred mcclient.TokenCredential, region *SCloudregion, xor bool) compare.SyncResult {
  317. lockman.LockRawObject(ctx, manager.Keyword(), region.Id)
  318. defer lockman.ReleaseRawObject(ctx, manager.Keyword(), region.Id)
  319. syncResult := compare.SyncResult{}
  320. meta, err := yunionmeta.FetchYunionmeta(ctx)
  321. if err != nil {
  322. return syncResult
  323. }
  324. extSkus := []SElasticcacheSku{}
  325. err = meta.List(manager.Keyword(), region.ExternalId, &extSkus)
  326. if err != nil {
  327. syncResult.Error(err)
  328. return syncResult
  329. }
  330. dbSkus, err := manager.FetchSkusByRegion(region.GetId())
  331. if err != nil {
  332. syncResult.Error(err)
  333. return syncResult
  334. }
  335. removed := make([]SElasticcacheSku, 0)
  336. commondb := make([]SElasticcacheSku, 0)
  337. commonext := make([]SElasticcacheSku, 0)
  338. added := make([]SElasticcacheSku, 0)
  339. err = compare.CompareSets(dbSkus, extSkus, &removed, &commondb, &commonext, &added)
  340. if err != nil {
  341. syncResult.Error(err)
  342. return syncResult
  343. }
  344. for i := 0; i < len(removed); i += 1 {
  345. if cnt, _ := removed[i].GetElasticcacheCount(); cnt > 0 {
  346. err = removed[i].MarkAsSoldout(ctx)
  347. } else {
  348. err = db.RealDeleteModel(ctx, userCred, &removed[i])
  349. }
  350. if err != nil {
  351. syncResult.DeleteError(err)
  352. } else {
  353. syncResult.Delete()
  354. }
  355. }
  356. if !xor {
  357. for i := 0; i < len(commondb); i += 1 {
  358. err = commondb[i].syncWithCloudSku(ctx, userCred, commonext[i])
  359. if err != nil {
  360. syncResult.UpdateError(err)
  361. } else {
  362. syncResult.Update()
  363. }
  364. }
  365. }
  366. ch := make(chan struct{}, options.Options.SkuBatchSync)
  367. defer close(ch)
  368. var wg sync.WaitGroup
  369. for i := 0; i < len(added); i += 1 {
  370. ch <- struct{}{}
  371. wg.Add(1)
  372. go func(sku SElasticcacheSku) {
  373. defer func() {
  374. wg.Done()
  375. <-ch
  376. }()
  377. err = region.newFromPublicCloudSku(ctx, userCred, sku.GetExternalId())
  378. if err != nil {
  379. syncResult.AddError(err)
  380. return
  381. }
  382. syncResult.Add()
  383. }(added[i])
  384. }
  385. wg.Wait()
  386. return syncResult
  387. }
  388. func (self *SElasticcacheSku) MarkAsSoldout(ctx context.Context) error {
  389. _, err := db.UpdateWithLock(ctx, self, func() error {
  390. self.PrepaidStatus = api.SkuStatusSoldout
  391. self.PostpaidStatus = api.SkuStatusSoldout
  392. return nil
  393. })
  394. return errors.Wrap(err, "MarkAsSoldout")
  395. }
  396. func (self *SElasticcacheSku) syncWithCloudSku(ctx context.Context, userCred mcclient.TokenCredential, extSku SElasticcacheSku) error {
  397. _, err := db.Update(self, func() error {
  398. self.PrepaidStatus = extSku.PrepaidStatus
  399. self.PostpaidStatus = extSku.PostpaidStatus
  400. return nil
  401. })
  402. return err
  403. }
  404. func (self *SCloudregion) newFromPublicCloudSku(ctx context.Context, userCred mcclient.TokenCredential, externalId string) error {
  405. meta, err := yunionmeta.FetchYunionmeta(ctx)
  406. if err != nil {
  407. return err
  408. }
  409. zones, err := self.GetZones()
  410. if err != nil {
  411. return errors.Wrap(err, "GetZones")
  412. }
  413. zoneMaps := map[string]string{}
  414. for _, zone := range zones {
  415. zoneMaps[zone.ExternalId] = zone.Id
  416. }
  417. skuUrl := self.getMetaUrl(meta.ElasticCacheBase, externalId)
  418. sku := &SElasticcacheSku{}
  419. sku.SetModelManager(ElasticcacheSkuManager, sku)
  420. err = meta.Get(skuUrl, sku)
  421. if err != nil {
  422. return errors.Wrapf(err, "Get")
  423. }
  424. sku.Status = api.SkuStatusAvailable
  425. sku.CloudregionId = self.Id
  426. sku.Provider = self.Provider
  427. if len(sku.ZoneId) > 0 {
  428. zoneId := yunionmeta.GetZoneIdBySuffix(zoneMaps, sku.ZoneId)
  429. if len(zoneId) == 0 {
  430. return errors.Wrapf(err, "empty zoneId for %s", sku.ZoneId)
  431. }
  432. sku.ZoneId = zoneId
  433. }
  434. if len(sku.SlaveZoneId) > 0 {
  435. zoneId := yunionmeta.GetZoneIdBySuffix(zoneMaps, sku.SlaveZoneId)
  436. if len(zoneId) == 0 {
  437. return errors.Wrapf(err, "empty zoneId for %s", sku.SlaveZoneId)
  438. }
  439. sku.SlaveZoneId = zoneId
  440. }
  441. return ElasticcacheSkuManager.TableSpec().Insert(ctx, sku)
  442. }
  443. func (manager *SElasticcacheSkuManager) GetPropertyInstanceSpecs(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (jsonutils.JSONObject, error) {
  444. q := manager.Query("memory_size_mb")
  445. q, err := db.ListItemQueryFilters(manager, ctx, q, userCred, query, policy.PolicyActionList)
  446. if err != nil {
  447. return nil, errors.Wrap(err, "db.ListItemQueryFilters")
  448. }
  449. listQuery := api.ElasticcacheSkuListInput{}
  450. err = query.Unmarshal(&listQuery)
  451. if err != nil {
  452. return nil, errors.Wrap(err, "query.Unmarshal")
  453. }
  454. q, err = manager.ListItemFilter(ctx, q, userCred, listQuery)
  455. if err != nil {
  456. return nil, errors.Wrap(err, "manager.ListItemFilter")
  457. }
  458. q = q.GroupBy(q.Field("memory_size_mb")).Asc(q.Field("memory_size_mb")).Distinct()
  459. rows, err := q.Rows()
  460. if err != nil {
  461. return nil, err
  462. }
  463. defer rows.Close()
  464. mems := map[int]bool{}
  465. mems_mb := jsonutils.NewArray()
  466. for rows.Next() {
  467. var ms int
  468. err := rows.Scan(&ms)
  469. if err == nil {
  470. m := roundMem(ms)
  471. if _, exist := mems[m]; !exist {
  472. if ms > 0 {
  473. mems_mb.Add(jsonutils.NewInt(int64(m)))
  474. }
  475. mems[m] = true
  476. }
  477. } else {
  478. log.Debugf("SElasticcacheSkuManager.GetPropertyInstanceSpecs %s", err)
  479. }
  480. }
  481. ret := jsonutils.NewDict()
  482. ret.Add(mems_mb, "mems_mb")
  483. return ret, nil
  484. }
  485. func (manager *SElasticcacheSkuManager) GetPropertyCapability(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (jsonutils.JSONObject, error) {
  486. q := manager.Query("engine", "engine_version", "local_category", "node_type", "performance_type")
  487. q, err := db.ListItemQueryFilters(manager, ctx, q, userCred, query, policy.PolicyActionList)
  488. if err != nil {
  489. return nil, errors.Wrap(err, "db.ListItemQueryFilters")
  490. }
  491. listQuery := api.ElasticcacheSkuListInput{}
  492. err = query.Unmarshal(&listQuery)
  493. if err != nil {
  494. return nil, errors.Wrap(err, "query.Unmarshal")
  495. }
  496. q, err = manager.ListItemFilter(ctx, q, userCred, listQuery)
  497. if err != nil {
  498. return nil, errors.Wrap(err, "manager.ListItemFilter")
  499. }
  500. f1 := q.Field("engine")
  501. f2 := q.Field("engine_version")
  502. f3 := q.Field("local_category")
  503. f4 := q.Field("node_type")
  504. f5 := q.Field("performance_type")
  505. q = q.GroupBy(f1, f2, f3, f4, f5).Asc(f1, f2, f3, f4, f5)
  506. rows, err := q.Rows()
  507. if err != nil {
  508. return nil, err
  509. }
  510. var addNode func(src *jsonutils.JSONDict, keys ...string)
  511. // keys至少2位,最后一位为叶子节点
  512. addNode = func(src *jsonutils.JSONDict, keys ...string) {
  513. length := len(keys)
  514. if length < 2 {
  515. return
  516. }
  517. if length == 2 {
  518. if t, err := src.Get(keys[0]); err != nil {
  519. n := jsonutils.NewArray()
  520. n.Add(jsonutils.NewString(keys[1]))
  521. src.Set(keys[0], n)
  522. } else {
  523. t.(*jsonutils.JSONArray).Add(jsonutils.NewString(keys[1]))
  524. }
  525. return
  526. }
  527. var temp *jsonutils.JSONDict
  528. if t, err := src.Get(keys[0]); err != nil {
  529. temp = jsonutils.NewDict()
  530. src.Set(keys[0], temp)
  531. } else {
  532. temp = t.(*jsonutils.JSONDict)
  533. }
  534. nkeys := keys[1:]
  535. addNode(temp, nkeys...)
  536. }
  537. result := jsonutils.NewDict()
  538. defer rows.Close()
  539. for rows.Next() {
  540. var engine, version, category, node, performance string
  541. err := rows.Scan(&engine, &version, &category, &node, &performance)
  542. if err != nil {
  543. log.Debugf("SElasticcacheSkuManager.GetPropertyCapability %s", err)
  544. continue
  545. }
  546. switch engine {
  547. case "redis":
  548. addNode(result, engine, version, category, node, performance)
  549. case "memcached":
  550. addNode(result, engine, category)
  551. }
  552. }
  553. return result, nil
  554. }
  555. func (manager *SElasticcacheSkuManager) PerformActionSync(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (jsonutils.JSONObject, error) {
  556. data := query.(*jsonutils.JSONDict)
  557. cloudprovider := validators.NewModelIdOrNameValidator("cloudprovider", "cloudprovider", nil)
  558. cloudregion := validators.NewModelIdOrNameValidator("cloudregion", "cloudregion", nil)
  559. keyV := map[string]validators.IValidator{
  560. "provider": cloudprovider.Optional(true),
  561. "cloudregion": cloudregion.Optional(true),
  562. }
  563. for _, v := range keyV {
  564. if err := v.Validate(ctx, data); err != nil {
  565. return nil, err
  566. }
  567. }
  568. regions := []SCloudregion{}
  569. if region, err := data.GetString("cloudregion"); err == nil && len(region) > 0 {
  570. regions = append(regions, *cloudregion.Model.(*SCloudregion))
  571. } else if provider, err := data.GetString("cloudprovider"); err == nil && len(provider) > 0 {
  572. regions, err = CloudregionManager.GetRegionByProvider(provider)
  573. if err != nil {
  574. return nil, err
  575. }
  576. }
  577. return nil, nil
  578. }
  579. func (manager *SElasticcacheSkuManager) ListItemExportKeys(ctx context.Context,
  580. q *sqlchemy.SQuery,
  581. userCred mcclient.TokenCredential,
  582. keys stringutils2.SSortedStrings,
  583. ) (*sqlchemy.SQuery, error) {
  584. var err error
  585. q, err = manager.SStatusStandaloneResourceBaseManager.ListItemExportKeys(ctx, q, userCred, keys)
  586. if err != nil {
  587. return nil, errors.Wrap(err, "SStatusStandaloneResourceBaseManager.ListItemExportKeys")
  588. }
  589. if keys.ContainsAny(manager.SCloudregionResourceBaseManager.GetExportKeys()...) {
  590. q, err = manager.SCloudregionResourceBaseManager.ListItemExportKeys(ctx, q, userCred, keys)
  591. if err != nil {
  592. return nil, errors.Wrap(err, "SCloudregionResourceBaseManager.ListItemExportKeys")
  593. }
  594. }
  595. if keys.Contains("zone") {
  596. q, err = manager.SZoneResourceBaseManager.ListItemExportKeys(ctx, q, userCred, keys)
  597. if err != nil {
  598. return nil, errors.Wrap(err, "SZoneResourceBaseManager.ListItemExportKeys")
  599. }
  600. }
  601. return q, nil
  602. }
  603. func (manager *SElasticcacheSkuManager) PerformSyncSkus(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.SkuSyncInput) (jsonutils.JSONObject, error) {
  604. return PerformActionSyncSkus(ctx, userCred, manager.Keyword(), input)
  605. }
  606. func (manager *SElasticcacheSkuManager) GetPropertySyncTasks(ctx context.Context, userCred mcclient.TokenCredential, query api.SkuTaskQueryInput) (jsonutils.JSONObject, error) {
  607. return GetPropertySkusSyncTasks(ctx, userCred, query)
  608. }
  609. func (self *SCloudregion) SyncPrivateCloudCacheSkus(ctx context.Context, userCred mcclient.TokenCredential, iskus []cloudprovider.ICloudElasticcacheSku) compare.SyncResult {
  610. lockman.LockRawObject(ctx, self.Id, ElasticcacheSkuManager.Keyword())
  611. defer lockman.ReleaseRawObject(ctx, self.Id, ElasticcacheSkuManager.Keyword())
  612. result := compare.SyncResult{}
  613. dbSkus, err := self.GetElasticcacheSkus()
  614. if err != nil {
  615. result.Error(err)
  616. return result
  617. }
  618. removed := make([]SElasticcacheSku, 0)
  619. commondb := make([]SElasticcacheSku, 0)
  620. commonext := make([]cloudprovider.ICloudElasticcacheSku, 0)
  621. added := make([]cloudprovider.ICloudElasticcacheSku, 0)
  622. err = compare.CompareSets(dbSkus, iskus, &removed, &commondb, &commonext, &added)
  623. if err != nil {
  624. result.Error(err)
  625. return result
  626. }
  627. for i := 0; i < len(removed); i += 1 {
  628. err = removed[i].Delete(ctx, userCred)
  629. if err != nil {
  630. result.DeleteError(err)
  631. continue
  632. }
  633. result.Delete()
  634. }
  635. for i := 0; i < len(added); i += 1 {
  636. err = self.newFromCloudElasticcacheSku(ctx, userCred, added[i])
  637. if err != nil {
  638. result.AddError(err)
  639. } else {
  640. result.Add()
  641. }
  642. }
  643. return result
  644. }
  645. func (self *SCloudregion) newFromCloudElasticcacheSku(ctx context.Context, userCred mcclient.TokenCredential, ext cloudprovider.ICloudElasticcacheSku) error {
  646. sku := &SElasticcacheSku{}
  647. sku.SetModelManager(ElasticcacheSkuManager, sku)
  648. sku.Name = ext.GetName()
  649. sku.InstanceSpec = ext.GetName()
  650. sku.Status = apis.SKU_STATUS_AVAILABLE
  651. sku.CloudregionId = self.Id
  652. sku.ExternalId = ext.GetGlobalId()
  653. sku.Provider = self.Provider
  654. sku.EngineArch = ext.GetEngineArch()
  655. sku.LocalCategory = ext.GetLocalCategory()
  656. sku.PrepaidStatus = ext.GetPrepaidStatus()
  657. sku.PostpaidStatus = ext.GetPostpaidStatus()
  658. sku.Engine = ext.GetEngine()
  659. sku.EngineVersion = ext.GetEngineVersion()
  660. sku.CpuArch = ext.GetCpuArch()
  661. sku.StorageType = ext.GetStorageType()
  662. sku.MemorySizeMB = ext.GetMemorySizeMb()
  663. sku.PerformanceType = ext.GetPerformanceType()
  664. sku.NodeType = ext.GetNodeType()
  665. sku.DiskSizeGB = ext.GetDiskSizeGb()
  666. sku.ShardNum = ext.GetShardNum()
  667. sku.MaxShardNum = ext.GetMaxShardNum()
  668. sku.ReplicasNum = ext.GetReplicasNum()
  669. sku.MaxReplicasNum = ext.GetMaxReplicasNum()
  670. sku.MaxClients = ext.GetMaxClients()
  671. sku.MaxConnections = ext.GetMaxConnections()
  672. sku.MaxInBandwidthMb = ext.GetMaxInBandwidthMb()
  673. sku.MaxMemoryMB = ext.GetMaxMemoryMb()
  674. sku.QPS = ext.GetQps()
  675. zones, err := self.GetZones()
  676. if err != nil {
  677. return errors.Wrapf(err, "GetZones")
  678. }
  679. zoneId := ext.GetZoneId()
  680. slaveZoneId := ext.GetSlaveZoneId()
  681. for i := range zones {
  682. if len(zoneId) > 0 && strings.HasSuffix(zones[i].ExternalId, zoneId) {
  683. sku.ZoneId = zones[i].Id
  684. }
  685. if len(slaveZoneId) > 0 && strings.HasSuffix(zones[i].ExternalId, slaveZoneId) {
  686. sku.SlaveZoneId = zones[i].Id
  687. }
  688. }
  689. return ElasticcacheSkuManager.TableSpec().Insert(ctx, sku)
  690. }
  691. // 全量同步elasticcache sku列表.
  692. func SyncElasticCacheSkus(ctx context.Context, userCred mcclient.TokenCredential, isStart bool) {
  693. if isStart {
  694. cnt, err := CloudaccountManager.Query().IsTrue("is_public_cloud").CountWithError()
  695. if err != nil && err != sql.ErrNoRows {
  696. log.Debugf("SyncElasticCacheSkus %s.sync skipped...", err)
  697. return
  698. } else if cnt == 0 {
  699. log.Debugf("SyncElasticCacheSkus no public cloud.sync skipped...")
  700. return
  701. }
  702. cnt, err = ElasticcacheSkuManager.Query().Limit(1).CountWithError()
  703. if err != nil && err != sql.ErrNoRows {
  704. log.Errorf("SyncElasticCacheSkus.QueryElasticcacheSku %s", err)
  705. return
  706. } else if cnt > 0 {
  707. log.Debugf("SyncElasticCacheSkus synced skus, skip...")
  708. return
  709. }
  710. }
  711. cloudregions := fetchSkuSyncCloudregions()
  712. if len(cloudregions) == 0 {
  713. return
  714. }
  715. meta, err := yunionmeta.FetchYunionmeta(ctx)
  716. if err != nil {
  717. log.Errorf("FetchYunionmeta %v", err)
  718. return
  719. }
  720. index, err := meta.Index(ElasticcacheSkuManager.Keyword())
  721. if err != nil {
  722. log.Errorf("get cache sku index error: %v", err)
  723. return
  724. }
  725. for i := range cloudregions {
  726. region := &cloudregions[i]
  727. if !region.GetDriver().IsSupportedElasticcache() {
  728. continue
  729. }
  730. skuMeta := &SElasticcacheSku{}
  731. skuMeta.SetModelManager(ElasticcacheSkuManager, skuMeta)
  732. skuMeta.Id = region.ExternalId
  733. oldMd5 := db.Metadata.GetStringValue(ctx, skuMeta, db.SKU_METADAT_KEY, userCred)
  734. newMd5, ok := index[region.ExternalId]
  735. if !ok || newMd5 == yunionmeta.EMPTY_MD5 || len(oldMd5) > 0 && newMd5 == oldMd5 {
  736. continue
  737. }
  738. db.Metadata.SetValue(ctx, skuMeta, db.SKU_METADAT_KEY, newMd5, userCred)
  739. result := ElasticcacheSkuManager.SyncElasticcacheSkus(ctx, userCred, region, false)
  740. notes := fmt.Sprintf("SyncElasticCacheSkusByRegion %s result: %s", region.Name, result.Result())
  741. log.Debugf("%s", notes)
  742. }
  743. }
  744. // 同步Region elasticcache sku列表.
  745. func SyncElasticCacheSkusByRegion(ctx context.Context, userCred mcclient.TokenCredential, region *SCloudregion, xor bool) error {
  746. if !region.GetDriver().IsSupportedElasticcache() {
  747. notes := fmt.Sprintf("SyncElasticCacheSkusByRegion %s not support elasticcache", region.Name)
  748. log.Infof("%s", notes)
  749. return nil
  750. }
  751. result := ElasticcacheSkuManager.SyncElasticcacheSkus(ctx, userCred, region, xor)
  752. notes := fmt.Sprintf("SyncElasticCacheSkusByRegion %s result: %s", region.Name, result.Result())
  753. log.Infof("%s", notes)
  754. return nil
  755. }