Files
cloudpods/pkg/compute/models/skustools.go
2019-07-06 16:49:51 +08:00

418 lines
11 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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"
"strings"
"yunion.io/x/jsonutils"
"yunion.io/x/log"
"yunion.io/x/pkg/utils"
api "yunion.io/x/onecloud/pkg/apis/compute"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
"yunion.io/x/onecloud/pkg/cloudprovider"
"yunion.io/x/onecloud/pkg/compute/options"
"yunion.io/x/onecloud/pkg/mcclient"
"yunion.io/x/onecloud/pkg/mcclient/auth"
"yunion.io/x/onecloud/pkg/mcclient/modules"
)
type SkusZone struct {
Provider string
RegionId string
ZoneId string
ExternalZoneId string
ExternalRegionId string
skus []jsonutils.JSONObject
total int
updated int
created int
}
func mergeSkuData(odata, ndata jsonutils.JSONObject) jsonutils.JSONObject {
data, ok := odata.(*jsonutils.JSONDict)
if !ok {
log.Debugf("invalid sku dict data: %s", odata)
}
new := processSkuData(ndata)
if !ok {
log.Debugf("invalid sku dict data: %s", ndata)
}
// merge os_name
o_osname, _ := data.GetString("os_name")
if o_osname != "Any" {
n_osname, nerr := new.GetString("os_name")
if nerr != nil || n_osname != o_osname {
data.Set("os_name", jsonutils.NewString("Any"))
}
}
// merge data_disk_typs
o_disks, oerr := data.GetString("data_disk_types")
n_disks, nerr := new.GetString("data_disk_types")
if oerr != nil || nerr != nil || o_disks == "" {
data.Set("data_disk_types", jsonutils.NewString(""))
} else {
if n_disks == "" {
data.Set("data_disk_types", jsonutils.NewString(""))
} else {
data.Set("data_disk_types", jsonutils.NewString(fmt.Sprintf("%s,%s", o_disks, n_disks)))
}
}
return data
}
func processSkuData(ndata jsonutils.JSONObject) jsonutils.JSONObject {
// 从返回结果中。将os_name统一成windows|Linux|any
// 将external_id 统一替换成 id
data, ok := ndata.(*jsonutils.JSONDict)
if !ok {
log.Debugf("invalid sku dict data: %s", ndata)
}
// 处理os name
os_name, _ := ndata.GetString("os_name")
os_name = strings.ToLower(os_name)
if strings.Contains(os_name, "any") || strings.Contains(os_name, "na") || os_name == "" {
data.Set("os_name", jsonutils.NewString("Any"))
} else if os_name != "windows" {
data.Set("os_name", jsonutils.NewString("Linux"))
} else {
data.Set("os_name", jsonutils.NewString("Windows"))
}
// 将external_id 统一替换成 id.
id, err := ndata.GetString("id")
if err != nil {
data.Set("external_id", jsonutils.NewString(""))
} else {
data.Set("external_id", jsonutils.NewString(id))
data.Remove("id")
}
return data
}
func (self *SkusZone) Init() error {
s := auth.GetAdminSession(context.Background(), options.Options.Region, "")
p, r, z := self.getExternalZone()
limit := 1024
offset := 0
total := 1024
records := map[string]jsonutils.JSONObject{}
for offset < total {
ret, e := modules.CloudmetaSkus.GetSkus(s, p, r, z, limit, offset)
if e != nil {
log.Debugf("SkusZone %s init failed, %s", z, e.Error())
return e
}
for _, sku := range ret.Data {
name, err := sku.GetString("name")
if err != nil {
log.Debugf("SkusZone sku name empty : %s", sku)
return err
}
if odata, exists := records[name]; exists {
records[name] = mergeSkuData(odata, sku)
} else {
records[name] = processSkuData(sku)
}
}
offset += limit
total = ret.Total
}
filtedData := []jsonutils.JSONObject{}
for _, item := range records {
filtedData = append(filtedData, item)
}
self.total = len(records)
self.skus = filtedData
return nil
}
func (self *SkusZone) doCreate(data SServerSku) error {
data.CloudregionId = self.RegionId
data.ZoneId = self.ZoneId
data.Provider = self.Provider
data.Status = api.SkuStatusReady
data.Enabled = true
if err := ServerSkuManager.TableSpec().Insert(&data); err != nil {
log.Debugf("SkusZone doCreate fail: %s", err.Error())
return err
}
self.created += 1
return nil
}
func (self *SkusZone) doUpdate(odata *SServerSku, sku jsonutils.JSONObject) error {
_, err := db.Update(odata, func() error {
if err := sku.Unmarshal(&odata); err != nil {
return err
}
odata.CloudregionId = self.RegionId
odata.ZoneId = self.ZoneId
odata.Provider = self.Provider
// 公有云默认都是ready并启用
odata.Status = api.SkuStatusReady
odata.Enabled = true
return nil
})
if err != nil {
log.Debugf("SkusZone doUpdate fail: %s", err.Error())
return err
}
self.updated += 1
return nil
}
func (self *SkusZone) SyncToLocalDB() error {
log.Debugf("SkusZone %s start sync.", self.ExternalZoneId)
// 更新已经soldout的sku
localIds, err := ServerSkuManager.FetchAllAvailableSkuIdByZoneId(self.ZoneId)
if err != nil {
return err
}
// 本次已被更新的sku id
updatedIds := make([]string, 0)
for _, sku := range self.skus {
name, _ := sku.GetString("name")
if obj, err := ServerSkuManager.FetchByZoneId(self.ZoneId, name); err != nil {
if err != sql.ErrNoRows {
log.Debugf("SyncToLocalDB zone %s name %s : %s", self.ZoneId, name, err.Error())
return err
}
data := SServerSku{}
if e := sku.Unmarshal(&data); e != nil {
log.Debugf("sku Unmarshal failed: %s, %s", sku, e.Error())
return e
}
if err := self.doCreate(data); err != nil {
return err
}
} else {
odata, ok := obj.(*SServerSku)
if !ok {
return fmt.Errorf("SkusZone model assertion error. %s", obj)
}
if err := self.doUpdate(odata, sku); err != nil {
return err
}
updatedIds = append(updatedIds, odata.Id)
}
}
// 处理已经下架的sku 将本次未更新且处于available状态的sku置为soldout状态
abandonIds := diff(localIds, updatedIds)
log.Debugf("SyncToLocalDB abandon sku %s", abandonIds)
err = ServerSkuManager.MarkAllAsSoldout(abandonIds)
if err != nil {
return err
}
defer log.Debugf("SkusZone %s sync to local db.total %d,created %d,updated %d. abandoned %d", self.ExternalZoneId, self.total, self.created, self.updated, len(abandonIds))
return nil
}
func (self *SkusZone) getExternalZone() (string, string, string) {
parts := strings.Split(self.ExternalZoneId, "/")
if len(parts) == 3 {
// provider, region, zone
return parts[0], parts[1], parts[2]
} else if len(parts) == 2 && parts[0] == api.CLOUD_PROVIDER_AZURE {
// azure 没有zone的概念
return parts[0], parts[1], parts[1]
}
log.Debugf("SkusZone invalid external zone id %s", self.ExternalZoneId)
return "", "", ""
}
type SkusZoneList struct {
Data []*SkusZone
total int
scuccesed int
failed int
}
func (self *SkusZoneList) initData(provider string, region SCloudregion, zones []SZone) {
for _, z := range zones {
log.Debugf("SkusZoneList initData provider %s zone %s", provider, z.GetId())
skusZone := &SkusZone{
Provider: provider,
RegionId: region.GetId(),
ZoneId: z.GetId(),
ExternalZoneId: z.GetExternalId(),
ExternalRegionId: region.GetExternalId(),
}
self.Data = append(self.Data, skusZone)
}
}
func (self *SkusZoneList) Refresh(providerIds *[]string) error {
self.Data = []*SkusZone{}
var pIds []string
if providerIds == nil {
pIds = cloudprovider.GetRegistedProviderIds()
} else {
pIds = *providerIds
}
for _, p := range pIds {
regions, e := CloudregionManager.GetRegionByProvider(p)
if e != nil {
return e
}
for _, r := range regions {
zones, e := ZoneManager.GetZonesByRegion(&r)
if e != nil {
return e
}
self.initData(p, r, zones)
}
}
self.refresh()
return nil
}
func (self *SkusZoneList) refresh() {
self.total = len(self.Data)
self.scuccesed = 0
self.failed = 0
}
func (self *SkusZoneList) SyncToLocalDB() error {
var err error
log.Debugf("######################Start Sync Skus To LocalDB######################")
for _, d := range self.Data {
if e := d.Init(); e != nil {
log.Errorf("SkusZoneList init failed: %s", e.Error())
self.failed += 1
err = e
continue
}
if e := d.SyncToLocalDB(); e != nil {
log.Errorf("SkusZoneList SyncToLocalDB failed: %s", e.Error())
self.failed += 1
err = e
continue
}
self.scuccesed += 1
remain := self.total - self.scuccesed - self.failed
log.Infof("SkusZoneList total %d, success %d.fail %d, remain %d. sync zone %s.", self.total, self.scuccesed, self.failed, remain, d.ExternalZoneId)
}
log.Debugf("######################Finished Sync Skus To LocalDB######################")
return err
}
// 全量同步sku列表.
func SyncSkus(ctx context.Context, userCred mcclient.TokenCredential, isStart bool) {
if isStart {
cnt, err := ServerSkuManager.GetPublicCloudSkuCount()
if err != nil {
log.Errorf("GetPublicCloudSkuCount fail %s", err)
return
}
if cnt > 0 {
log.Debugf("GetPublicCloudSkuCount synced skus, skip...")
return
}
}
skulist := SkusZoneList{}
if e := skulist.Refresh(nil); e != nil {
log.Errorf("SyncSkus refresh failed, %s", e.Error())
}
if e := skulist.SyncToLocalDB(); e != nil {
log.Errorf("SyncSkus sync to local db failed, %s", e.Error())
}
// 清理无效的sku
log.Debugf("DeleteInvalidSkus in processing...")
ServerSkuManager.PendingDeleteInvalidSku()
}
// 同步指定provider sku列表
func SyncSkusByProviderIds(providerIds []string) error {
skulist := SkusZoneList{}
log.Debugf("SyncSkusByProviderIds %s", providerIds)
if e := skulist.Refresh(&providerIds); e != nil {
return fmt.Errorf("SyncSkus refresh failed, %s", e.Error())
}
if e := skulist.SyncToLocalDB(); e != nil {
return fmt.Errorf("SyncSkus sync to local db failed, %s", e.Error())
}
return nil
}
// 同步指定region sku列表
func syncSkusByRegion(region *SCloudregion) error {
skulist := SkusZoneList{}
zones, err := ZoneManager.GetZonesByRegion(region)
if err != nil {
return err
}
log.Debugf("SyncSkusByRegion %s", region.GetName())
skulist.initData(region.Provider, *region, zones)
skulist.refresh()
if e := skulist.SyncToLocalDB(); e != nil {
return fmt.Errorf("SyncSkus sync to local db failed, %s", e.Error())
}
return nil
}
// 找出origins中存在但是compares中不存在的element
func diff(origins, compares []string) []string {
ret := make([]string, 0)
for _, o := range origins {
if !utils.IsInStringArray(o, compares) && len(o) > 0 {
ret = append(ret, o)
}
}
return ret
}