mirror of
https://github.com/yunionio/cloudpods.git
synced 2026-09-21 14:19:49 +08:00
324 lines
10 KiB
Go
324 lines
10 KiB
Go
package models
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"fmt"
|
|
"math/rand"
|
|
"time"
|
|
|
|
"yunion.io/x/jsonutils"
|
|
"yunion.io/x/log"
|
|
"yunion.io/x/onecloud/pkg/httperrors"
|
|
"yunion.io/x/onecloud/pkg/mcclient"
|
|
"yunion.io/x/onecloud/pkg/mcclient/auth"
|
|
"yunion.io/x/onecloud/pkg/mcclient/modules"
|
|
"yunion.io/x/pkg/util/timeutils"
|
|
"yunion.io/x/sqlchemy"
|
|
|
|
"yunion.io/x/onecloud/pkg/cloudcommon/db"
|
|
"yunion.io/x/onecloud/pkg/cloudcommon/db/lockman"
|
|
"yunion.io/x/onecloud/pkg/compute/options"
|
|
)
|
|
|
|
const (
|
|
CACHED_IMAGE_REFRESH_SECONDS = 900 // 15 minutes
|
|
CACHED_IMAGE_REFERENCE_SESSION_EXPIRE_SECONDS = 86400 // 1 day
|
|
)
|
|
|
|
type SCachedimageManager struct {
|
|
db.SStandaloneResourceBaseManager
|
|
SInfrastructureManager
|
|
}
|
|
|
|
var CachedimageManager *SCachedimageManager
|
|
|
|
func init() {
|
|
CachedimageManager = &SCachedimageManager{SStandaloneResourceBaseManager: db.NewStandaloneResourceBaseManager(SCachedimage{}, "cachedimages_tbl", "cachedimage", "cachedimages")}
|
|
}
|
|
|
|
type SCachedimage struct {
|
|
db.SStandaloneResourceBase
|
|
SInfrastructure
|
|
|
|
Size int64 `nullable:"false" list:"admin" update:"admin" create:"admin_required"` // = Column(BigInteger, nullable=False) # in Byte
|
|
// virtual_size = Column(BigInteger, nullable=False) # in Byte
|
|
Info jsonutils.JSONObject `nullable:"true" get:"admin" update:"admin" create:"admin_required"` // Column(JSONEncodedDict, nullable=True)
|
|
LastSync time.Time `list:"admin"` // = Column(DateTime)
|
|
LastRef time.Time `list:"admin"` // = Column(DateTime)
|
|
RefCount int `default:"0" list:"admin"` // = Column(Integer, default=0, server_default='0')
|
|
}
|
|
|
|
func (self *SCachedimage) ValidateDeleteCondition(ctx context.Context) error {
|
|
if self.getStoragecacheCount() > 0 {
|
|
return httperrors.NewNotEmptyError("The image has been cached on storages")
|
|
}
|
|
if self.getStatus() == "active" && !self.isReferenceSessionExpire() {
|
|
return httperrors.NewConflictError("the image reference session has not been expired!")
|
|
}
|
|
return self.SStandaloneResourceBase.ValidateDeleteCondition(ctx)
|
|
}
|
|
|
|
func (self *SCachedimage) isReferenceSessionExpire() bool {
|
|
if !self.LastRef.IsZero() && time.Now().Sub(self.LastRef) < CACHED_IMAGE_REFERENCE_SESSION_EXPIRE_SECONDS*time.Second {
|
|
return false
|
|
} else {
|
|
return true
|
|
}
|
|
}
|
|
|
|
func (self *SCachedimage) isRefreshSessionExpire() bool {
|
|
if !self.LastRef.IsZero() && time.Now().Sub(self.LastRef) < CACHED_IMAGE_REFRESH_SECONDS*time.Second {
|
|
return false
|
|
} else {
|
|
return true
|
|
}
|
|
}
|
|
|
|
func (self *SCachedimage) getName() string {
|
|
name, _ := self.Info.GetString("name")
|
|
return name
|
|
}
|
|
|
|
func (self *SCachedimage) getOwner() string {
|
|
owner, _ := self.Info.GetString("owner")
|
|
return owner
|
|
}
|
|
|
|
func (self *SCachedimage) getFormat() string {
|
|
format, _ := self.Info.GetString("disk_format")
|
|
return format
|
|
}
|
|
|
|
func (self *SCachedimage) getStatus() string {
|
|
status, _ := self.Info.GetString("status")
|
|
return status
|
|
}
|
|
|
|
func (self *SCachedimage) getOSType() string {
|
|
osType, _ := self.Info.GetString("properties", "os_type")
|
|
return osType
|
|
}
|
|
|
|
func (self *SCachedimage) getStoragecacheQuery() *sqlchemy.SQuery {
|
|
q := StoragecachedimageManager.Query().Equals("cachedimage_id", self.Id)
|
|
return q
|
|
}
|
|
|
|
func (self *SCachedimage) getStoragecacheCount() int {
|
|
return self.getStoragecacheQuery().Count()
|
|
}
|
|
|
|
func (self *SCachedimage) getImage() (*SImage, error) {
|
|
image := SImage{}
|
|
|
|
err := self.Info.Unmarshal(&image)
|
|
if err != nil {
|
|
return nil, err
|
|
} else {
|
|
return &image, nil
|
|
}
|
|
}
|
|
|
|
func (manager *SCachedimageManager) cacheImageInfo(ctx context.Context, userCred mcclient.TokenCredential, info jsonutils.JSONObject) (*SCachedimage, error) {
|
|
lockman.LockClass(ctx, manager, userCred.GetProjectId())
|
|
defer lockman.ReleaseClass(ctx, manager, userCred.GetProjectId())
|
|
|
|
imgId, _ := info.GetString("id")
|
|
if len(imgId) == 0 {
|
|
return nil, fmt.Errorf("invalid image info")
|
|
}
|
|
|
|
imageCache := SCachedimage{}
|
|
imageCache.SetModelManager(manager)
|
|
|
|
size, _ := info.Int("size")
|
|
|
|
err := manager.Query().Equals("id", imgId).First(&imageCache)
|
|
if err != nil {
|
|
if err == sql.ErrNoRows { // insert
|
|
imageCache.Id = imgId
|
|
imageCache.Name = imgId
|
|
imageCache.Size = size
|
|
imageCache.Info = info
|
|
imageCache.LastSync = timeutils.UtcNow()
|
|
|
|
err = manager.TableSpec().Insert(&imageCache)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
db.OpsLog.LogEvent(&imageCache, db.ACT_CREATE, info, userCred)
|
|
|
|
return &imageCache, nil
|
|
} else {
|
|
log.Errorf("fail to query image cahe %d", imgId)
|
|
return nil, err
|
|
}
|
|
} else { // update
|
|
diff, err := manager.TableSpec().Update(&imageCache, func() error {
|
|
imageCache.Size = size
|
|
imageCache.Info = info
|
|
imageCache.LastSync = timeutils.UtcNow()
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
db.OpsLog.LogEvent(&imageCache, db.ACT_UPDATE, sqlchemy.UpdateDiffString(diff), userCred)
|
|
|
|
return &imageCache, nil
|
|
}
|
|
}
|
|
|
|
func (manager *SCachedimageManager) GetImageById(ctx context.Context, userCred mcclient.TokenCredential, imageId string, refresh bool) (*SImage, error) {
|
|
if !refresh {
|
|
imgObj, _ := manager.FetchById(imageId)
|
|
if imgObj != nil {
|
|
cachedImage := imgObj.(*SCachedimage)
|
|
if cachedImage.getStatus() == "active" && len(cachedImage.getOSType()) > 0 && cachedImage.isRefreshSessionExpire() {
|
|
return cachedImage.getImage()
|
|
}
|
|
}
|
|
}
|
|
s := auth.GetAdminSession(options.Options.Region, "")
|
|
obj, err := modules.Images.Get(s, imageId, nil)
|
|
if err != nil {
|
|
log.Errorf("GetImageById %s error %s", imageId, err)
|
|
return nil, err
|
|
}
|
|
cachedImage, err := manager.cacheImageInfo(ctx, userCred, obj)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return cachedImage.getImage()
|
|
}
|
|
|
|
func (manager *SCachedimageManager) getImageByName(ctx context.Context, userCred mcclient.TokenCredential, imageId string) (*SImage, error) {
|
|
s := auth.GetSession(userCred, options.Options.Region, "")
|
|
obj, err := modules.Images.GetByName(s, imageId, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
cachedImage, err := manager.cacheImageInfo(ctx, userCred, obj)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return cachedImage.getImage()
|
|
}
|
|
|
|
func (manager *SCachedimageManager) getImageInfo(ctx context.Context, userCred mcclient.TokenCredential, imageId string, refresh bool) (*SImage, error) {
|
|
img, err := manager.GetImageById(ctx, userCred, imageId, refresh)
|
|
if err == nil {
|
|
return img, nil
|
|
}
|
|
log.Errorf("getImageInfoById %s fail %s", imageId, err)
|
|
return manager.getImageByName(ctx, userCred, imageId)
|
|
}
|
|
|
|
func (self *SCachedimage) GetCustomizeColumns(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) *jsonutils.JSONDict {
|
|
extra := self.SStandaloneResourceBase.GetCustomizeColumns(ctx, userCred, query)
|
|
extra.Add(jsonutils.NewString(self.getName()), "name")
|
|
extra.Add(jsonutils.NewString(self.getOwner()), "owner")
|
|
extra.Add(jsonutils.NewString(self.getFormat()), "format")
|
|
extra.Add(jsonutils.NewString(self.getStatus()), "status")
|
|
for _, k := range []string{"os_type", "os_distribution", "os_version", "hypervisor"} {
|
|
val, _ := self.Info.GetString("properties", k)
|
|
if len(val) > 0 {
|
|
extra.Add(jsonutils.NewString(val), k)
|
|
}
|
|
}
|
|
extra.Add(jsonutils.NewInt(int64(self.getStoragecacheCount())), "storage_cache_count")
|
|
return extra
|
|
}
|
|
|
|
func (self *SCachedimage) AllowPerformRefresh(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool {
|
|
return userCred.IsSystemAdmin()
|
|
}
|
|
|
|
func (self *SCachedimage) PerformRefresh(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
|
|
img, err := CachedimageManager.GetImageById(ctx, userCred, self.Id, true)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return jsonutils.Marshal(img), nil
|
|
}
|
|
|
|
func (self *SCachedimage) addRefCount() {
|
|
if self.getStatus() != "active" {
|
|
return
|
|
}
|
|
_, err := CachedimageManager.TableSpec().Update(self, func() error {
|
|
self.RefCount += 1
|
|
self.LastRef = timeutils.UtcNow()
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
log.Errorf("addRefCount fail %s", err)
|
|
}
|
|
}
|
|
|
|
func (self *SCachedimage) ChooseSourceStoragecacheInRange(hostType string, excludes []string, rangeObjs interface{}) (*SStoragecachedimage, error) {
|
|
storageCachedImage := StoragecachedimageManager.Query().SubQuery()
|
|
storage := StorageManager.Query().SubQuery()
|
|
hostStorage := HoststorageManager.Query().SubQuery()
|
|
host := HostManager.Query().SubQuery()
|
|
|
|
scimgs := make([]SStoragecachedimage, 0)
|
|
q := storageCachedImage.Query().
|
|
Join(storage, sqlchemy.AND(sqlchemy.Equals(storage.Field("storagecache_id"), storageCachedImage.Field("storagecache_id")))).
|
|
Join(hostStorage, sqlchemy.AND(sqlchemy.Equals(hostStorage.Field("storage_id"), storage.Field("id")))).
|
|
Join(host, sqlchemy.AND(sqlchemy.Equals(hostStorage.Field("host_id"), host.Field("id")))).
|
|
Filter(sqlchemy.Equals(storageCachedImage.Field("cachedimage_id"), self.Id)).
|
|
Filter(sqlchemy.Equals(storageCachedImage.Field("status"), CACHED_IMAGE_STATUS_READY)).
|
|
Filter(sqlchemy.Equals(host.Field("status"), HOST_STATUS_RUNNING)).
|
|
Filter(sqlchemy.IsTrue(host.Field("enabled"))).
|
|
Filter(sqlchemy.Equals(host.Field("host_status"), HOST_ONLINE))
|
|
|
|
if len(excludes) > 0 {
|
|
q = q.Filter(sqlchemy.NotIn(host.Field("id"), excludes))
|
|
}
|
|
if len(hostType) > 0 {
|
|
q = q.Filter(sqlchemy.Equals(host.Field("host_type"), hostType))
|
|
}
|
|
|
|
switch v := rangeObjs.(type) {
|
|
case []*SZone:
|
|
for _, obj := range v {
|
|
q = q.Filter(sqlchemy.Equals(host.Field("zone_id"), obj.Id))
|
|
}
|
|
case []*SVCenter:
|
|
for _, obj := range v {
|
|
q = q.Filter(sqlchemy.Equals(host.Field("manager_id"), obj.Id))
|
|
}
|
|
}
|
|
err := db.FetchModelObjects(StoragecachedimageManager, q, &scimgs)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(scimgs) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
rand.Seed(time.Now().Unix())
|
|
return &scimgs[rand.Intn(len(scimgs))], nil
|
|
}
|
|
|
|
func (manager *SCachedimageManager) ImageAddRefCount(imageId string) {
|
|
cachedObj, _ := manager.FetchById(imageId)
|
|
if cachedObj != nil {
|
|
cachedImage := (cachedObj).(*SCachedimage)
|
|
cachedImage.addRefCount()
|
|
}
|
|
}
|
|
|
|
func (self *SCachedimage) canDeleteLastCache() bool {
|
|
if self.getStoragecacheCount() != 1 {
|
|
return true
|
|
}
|
|
if self.isReferenceSessionExpire() {
|
|
return true
|
|
}
|
|
return false
|
|
}
|