From 42a8eb837bb37802358a6255229e9f4b514274ce Mon Sep 17 00:00:00 2001 From: rainzm Date: Tue, 15 Feb 2022 18:22:01 +0800 Subject: [PATCH] feat: add project with class metadata --- cmd/climc/shell/identity/projects.go | 2 + cmd/climc/shell/image/images.go | 26 ---- pkg/cloudcommon/db/standalone_anon.go | 24 +-- pkg/cloudcommon/db/virtualresource.go | 40 +++++ pkg/compute/models/cachedimages.go | 28 +++- pkg/compute/models/guests.go | 4 - .../predicates/class_metadata_predicate.go | 141 ++++++++++-------- pkg/scheduler/cache/candidate/base.go | 19 +++ pkg/scheduler/core/types.go | 2 + 9 files changed, 175 insertions(+), 111 deletions(-) diff --git a/cmd/climc/shell/identity/projects.go b/cmd/climc/shell/identity/projects.go index 2134af0a5f..a1ee148bd9 100644 --- a/cmd/climc/shell/identity/projects.go +++ b/cmd/climc/shell/identity/projects.go @@ -31,6 +31,8 @@ func init() { cmd.List(&identity_options.ProjectListOptions{}) cmd.Perform("user-metadata", &options.ResourceMetadataOptions{}) cmd.Perform("set-user-metadata", &options.ResourceMetadataOptions{}) + cmd.Perform("class-metadata", &options.ResourceMetadataOptions{}) + cmd.Perform("set-class-metadata", &options.ResourceMetadataOptions{}) cmd.GetProperty(&identity_options.ProjectGetPropertyTagValuePairOptions{}) cmd.GetProperty(&identity_options.ProjectGetPropertyTagValueTreeOptions{}) cmd.GetProperty(&identity_options.ProjectGetPropertyDomainTagValuePairOptions{}) diff --git a/cmd/climc/shell/image/images.go b/cmd/climc/shell/image/images.go index 776258131b..2941adf77c 100644 --- a/cmd/climc/shell/image/images.go +++ b/cmd/climc/shell/image/images.go @@ -497,32 +497,6 @@ func init() { return nil }) - R(&options.ResourceMetadataOptions{}, "image-set-class-metadata", "set class metadata for image", func(s *mcclient.ClientSession, opts *options.ResourceMetadataOptions) error { - params, err := opts.Params() - if err != nil { - return err - } - img, err := modules.Images.PerformAction(s, opts.ID, "set-class-metadata", params) - if err != nil { - return err - } - printObject(img) - return nil - }) - - R(&options.ResourceMetadataOptions{}, "image-class-metadata", "set class metadata for image", func(s *mcclient.ClientSession, opts *options.ResourceMetadataOptions) error { - params, err := opts.Params() - if err != nil { - return err - } - img, err := modules.Images.PerformAction(s, opts.ID, "class-metadata", params) - if err != nil { - return err - } - printObject(img) - return nil - }) - type ImageProbeOptions struct { ID string `help:"ID or name of image to probe"` } diff --git a/pkg/cloudcommon/db/standalone_anon.go b/pkg/cloudcommon/db/standalone_anon.go index 5dfad8ff05..d61f2659ca 100644 --- a/pkg/cloudcommon/db/standalone_anon.go +++ b/pkg/cloudcommon/db/standalone_anon.go @@ -283,7 +283,7 @@ func (model *SStandaloneAnonResourceBase) SetClassMetadataValues(ctx context.Con return nil } -func (model *SStandaloneAnonResourceBase) SetClassMetadataAll(ctx context.Context, dictstore map[string]interface{}, userCred mcclient.TokenCredential) error { +func (model *SStandaloneAnonResourceBase) SetClassMetadataAll(ctx context.Context, dictstore map[string]string, userCred mcclient.TokenCredential) error { afterCheck := make(map[string]interface{}, len(dictstore)) for k, v := range dictstore { if !strings.HasPrefix(k, CLASS_TAG_PREFIX) { @@ -308,19 +308,19 @@ func (model *SStandaloneAnonResourceBase) Inherit(ctx context.Context, sonModel return nil } userCred := auth.AdminCredential() - dictstore := make(map[string]interface{}, 0) - for k, v := range metadata { - dictstore[k] = v - } - return sonModel.SetClassMetadataAll(ctx, dictstore, userCred) + return sonModel.SetClassMetadataAll(ctx, metadata, userCred) } -func (model *SStandaloneAnonResourceBase) IsInSameClass(ctx context.Context, pModel *SStandaloneAnonResourceBase) (bool, error) { - pureTags, err := model.GetAllClassMetadata() +type IClassMetadataOwner interface { + GetAllClassMetadata() (map[string]string, error) +} + +func IsInSameClass(ctx context.Context, cmo1, cmo2 IClassMetadataOwner) (bool, error) { + pureTags, err := cmo1.GetAllClassMetadata() if err != nil { return false, errors.Wrap(err, "GetAllPureMetadata") } - pureTagsP, err := pModel.GetAllClassMetadata() + pureTagsP, err := cmo2.GetAllClassMetadata() if err != nil { return false, errors.Wrap(err, "GetAllPureMetadata") } @@ -335,6 +335,10 @@ func (model *SStandaloneAnonResourceBase) IsInSameClass(ctx context.Context, pMo return true, nil } +func (model *SStandaloneAnonResourceBase) IsInSameClass(ctx context.Context, pModel *SStandaloneAnonResourceBase) (bool, error) { + return IsInSameClass(ctx, model, pModel) +} + func (model *SStandaloneAnonResourceBase) SetSysCloudMetadataAll(ctx context.Context, dictstore map[string]interface{}, userCred mcclient.TokenCredential) error { err := Metadata.SetAll(ctx, model, dictstore, userCred, SYS_CLOUD_TAG_PREFIX) if err != nil { @@ -481,7 +485,7 @@ func (model *SStandaloneAnonResourceBase) PerformClassMetadata(ctx context.Conte // 全量替换资源的所有 class 标签 func (model *SStandaloneAnonResourceBase) PerformSetClassMetadata(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input apis.PerformSetClassMetadataInput) (jsonutils.JSONObject, error) { - dictStore := make(map[string]interface{}) + dictStore := make(map[string]string) for k, v := range input { if len(k) > 64-len(CLASS_TAG_PREFIX) { return nil, httperrors.NewInputParameterError("input key too long > %d", 64-len(CLASS_TAG_PREFIX)) diff --git a/pkg/cloudcommon/db/virtualresource.go b/pkg/cloudcommon/db/virtualresource.go index e2ae5cd40b..a366f67531 100644 --- a/pkg/cloudcommon/db/virtualresource.go +++ b/pkg/cloudcommon/db/virtualresource.go @@ -282,6 +282,18 @@ func (model *SVirtualResourceBase) CustomizeCreate(ctx context.Context, userCred return model.SStatusStandaloneResourceBase.CustomizeCreate(ctx, userCred, ownerId, query, data) } +func (model *SVirtualResourceBase) PostCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) { + project, err := model.GetTenantCache(ctx) + if err != nil { + log.Errorf("unable to GetTenantCache: %s", err.Error()) + return + } + err = project.Inherit(ctx, &model.SStandaloneAnonResourceBase) + if err != nil { + log.Errorf("unable to inherit class metadata from poject %s: %s", project.GetId(), err.Error()) + } +} + func (manager *SVirtualResourceBaseManager) FetchCustomizeColumns( ctx context.Context, userCred mcclient.TokenCredential, @@ -416,6 +428,27 @@ func (model *SVirtualResourceBase) PerformChangeOwner(ctx context.Context, userC log.Warningf("tenant_id %s not found", model.ProjectId) formerObj := NewTenant(model.ProjectId, "unknown", model.DomainId, "unknown") former = &formerObj + } else { + // check fromer's class metadata + cm, err := former.GetAllClassMetadata() + if err != nil { + return nil, errors.Wrap(err, "unable to GetAllClassMetadata") + } + if len(cm) > 0 { + return nil, httperrors.NewForbiddenError("can't change owner for resource in project with class metadata") + } + } + + toer, err := TenantCacheManager.FetchTenantById(ctx, ownerId.GetProjectId()) + if err != nil { + return nil, errors.Wrapf(err, "unable to get project %s", ownerId.GetProjectId()) + } + toCm, err := toer.GetAllClassMetadata() + if err != nil { + return nil, errors.Wrap(err, "unable to GetAllClassMetadata") + } + if model.Keyword() != "image" && len(toCm) > 0 { + return nil, httperrors.NewForbiddenError("can't change resource's owner as that in project with class metadata") } // clean shared projects before update project id @@ -461,6 +494,13 @@ func (model *SVirtualResourceBase) PerformChangeOwner(ctx context.Context, userC NewDomainId: ownerId.GetProjectDomainId(), NewDomain: ownerId.GetProjectDomain(), } + + // set class metadata + + err = model.SetClassMetadataAll(ctx, toCm, userCred) + if err != nil { + return nil, errors.Wrap(err, "unable to SetClassMetadataAll") + } logclient.AddActionLogWithContext(ctx, model, logclient.ACT_CHANGE_OWNER, notes, userCred, true) return nil, nil } diff --git a/pkg/compute/models/cachedimages.go b/pkg/compute/models/cachedimages.go index d996206ef7..f95499e3d2 100644 --- a/pkg/compute/models/cachedimages.go +++ b/pkg/compute/models/cachedimages.go @@ -216,11 +216,7 @@ func (self *SCachedimage) syncClassMetadata(ctx context.Context, userCred mcclie return err } - cm := make(map[string]interface{}, len(classMetadata)) - for k, v := range classMetadata { - cm[k] = v - } - return self.SetClassMetadataAll(ctx, cm, userCred) + return self.SetClassMetadataAll(ctx, classMetadata, userCred) } func (manager *SCachedimageManager) cacheGlanceImageInfo(ctx context.Context, userCred mcclient.TokenCredential, info jsonutils.JSONObject) (*SCachedimage, error) { @@ -317,6 +313,28 @@ func (image *SCachedimage) GetStorages() ([]SStorage, error) { return storages, nil } +func (manager *SCachedimageManager) GetCachedimageById(ctx context.Context, imageId string) (*SCachedimage, error) { + img, err := manager.FetchById(imageId) + if err == nil { + return img.(*SCachedimage), nil + } + if errors.Cause(err) != sql.ErrNoRows { + return nil, err + } + s := auth.GetAdminSession(ctx, options.Options.Region, "") + obj, err := image.Images.Get(s, imageId, nil) + if err != nil { + log.Errorf("GetImageById %s error %s", imageId, err) + return nil, errors.Wrap(err, "modules.Images.Get") + } + userCred := s.GetToken() + cachedImage, err := manager.cacheGlanceImageInfo(ctx, userCred, obj) + if err != nil { + return nil, errors.Wrap(err, "manager.cacheGlanceImageInfo") + } + return cachedImage, nil +} + func (manager *SCachedimageManager) GetImageById(ctx context.Context, userCred mcclient.TokenCredential, imageId string, refresh bool) (*cloudprovider.SImage, error) { imgObj, _ := manager.FetchById(imageId) if imgObj != nil { diff --git a/pkg/compute/models/guests.go b/pkg/compute/models/guests.go index 3e62402762..ecad29457b 100644 --- a/pkg/compute/models/guests.go +++ b/pkg/compute/models/guests.go @@ -5271,10 +5271,6 @@ func (self *SGuest) OnScheduleToHost(ctx context.Context, userCred mcclient.Toke db.OpsLog.LogEvent(self, db.ACT_SCHEDULE, notes, userCred) host, _ := self.GetHost() - err = host.Inherit(ctx, &self.SStandaloneAnonResourceBase) - if err != nil { - return errors.Wrapf(err, "unable to inherit from host %s to guest %s", host.GetId(), self.GetId()) - } return host.ClearSchedDescCache() } diff --git a/pkg/scheduler/algorithm/predicates/class_metadata_predicate.go b/pkg/scheduler/algorithm/predicates/class_metadata_predicate.go index 62b47beee5..7eba908813 100644 --- a/pkg/scheduler/algorithm/predicates/class_metadata_predicate.go +++ b/pkg/scheduler/algorithm/predicates/class_metadata_predicate.go @@ -16,7 +16,6 @@ package predicates import ( "context" - "database/sql" "fmt" "yunion.io/x/pkg/errors" @@ -28,12 +27,23 @@ import ( type ClassMetadataPredicate struct { BasePredicate - cacheImage *models.SCachedimage - //TODO GuestImage - snapshot *models.SSnapshot - instanceSnapshot *models.SInstanceSnapshot - backup *models.SDiskBackup - instanceBackup *models.SInstanceBackup + + guestSource *ResourceWithClassMetadata + tenant *ResourceWithClassMetadata +} + +type ResourceWithClassMetadata struct { + keyword string + name string + classMedata map[string]string +} + +func (rcm *ResourceWithClassMetadata) GetAllClassMetadata() (map[string]string, error) { + return rcm.classMedata, nil +} + +func (rcm *ResourceWithClassMetadata) GetDescription() string { + return fmt.Sprintf("%s %s", rcm.keyword, rcm.name) } func (p *ClassMetadataPredicate) Name() string { @@ -42,95 +52,94 @@ func (p *ClassMetadataPredicate) Name() string { func (p *ClassMetadataPredicate) Clone() core.FitPredicate { return &ClassMetadataPredicate{ - cacheImage: p.cacheImage, - snapshot: p.snapshot, - instanceSnapshot: p.instanceSnapshot, - backup: p.backup, - instanceBackup: p.instanceBackup, + guestSource: p.guestSource, + tenant: p.tenant, } } func (p *ClassMetadataPredicate) PreExecute(u *core.Unit, cs []core.Candidater) (bool, error) { info := u.SchedData() - if len(info.InstanceSnapshotId) > 0 { + tenant, err := db.TenantCacheManager.FetchTenantById(context.Background(), info.Project) + if err != nil { + return false, errors.Wrapf(err, "unable to fetch tenant %s", info.Project) + } + tcm, err := tenant.GetAllClassMetadata() + if err != nil { + return false, errors.Wrapf(err, "unable to GetAllClassMetadata of project %s", info.Project) + } + p.tenant = &ResourceWithClassMetadata{ + classMedata: tcm, + keyword: tenant.Keyword(), + name: tenant.GetName(), + } + + // guest source + guestSource := &ResourceWithClassMetadata{} + disks := info.Disks + var stand *db.SStandaloneAnonResourceBase + // TODO GuestImage + switch { + case len(info.InstanceBackupId) > 0: + obj, err := models.InstanceBackupManager.FetchById(info.InstanceBackupId) + if err != nil { + return false, errors.Wrapf(err, "unable to fetch instanceBackup %s", info.InstanceSnapshotId) + } + stand = &obj.(*models.SInstanceBackup).SStandaloneAnonResourceBase + case len(info.InstanceSnapshotId) > 0: obj, err := models.InstanceSnapshotManager.FetchById(info.InstanceSnapshotId) if err != nil { return false, errors.Wrapf(err, "unable to fetch instanceSnapshot %s", info.InstanceSnapshotId) } - p.instanceSnapshot = obj.(*models.SInstanceSnapshot) - return true, nil - } - if len(info.InstanceBackupId) > 0 { - obj, err := models.InstanceBackupManager.FetchById(info.InstanceBackupId) - if err != nil { - return false, errors.Wrapf(err, "unable to fetch instanceBackup %s", info.InstanceBackupId) - } - p.instanceBackup = obj.(*models.SInstanceBackup) - return true, nil - } - disks := info.Disks - if len(disks) == 0 { - return false, nil - } - switch { + stand = &obj.(*models.SInstanceSnapshot).SStandaloneAnonResourceBase + case len(disks) == 0: case disks[0].ImageId != "": - obj, err := models.CachedimageManager.FetchById(disks[0].ImageId) + obj, err := models.CachedimageManager.GetCachedimageById(context.Background(), disks[0].ImageId) if err != nil { - // 忽略第一次上传到glance镜像后未缓存的记录 - if err == sql.ErrNoRows { - return false, nil - } return false, errors.Wrapf(err, "unable to fetch cachedimage %s", disks[0].ImageId) } - p.cacheImage = obj.(*models.SCachedimage) - return true, nil + stand = &obj.SStandaloneAnonResourceBase + guestSource.keyword = "image" case disks[0].SnapshotId != "": obj, err := models.SnapshotManager.FetchById(disks[0].SnapshotId) if err != nil { return false, errors.Wrapf(err, "unable to fetch snapshot %s", disks[0].SnapshotId) } - p.snapshot = obj.(*models.SSnapshot) - return true, nil + stand = &obj.(*models.SSnapshot).SStandaloneAnonResourceBase case disks[0].BackupId != "": obj, err := models.DiskBackupManager.FetchById(disks[0].BackupId) if err != nil { return false, errors.Wrapf(err, "unable to fetch diskbackup %s", disks[0].BackupId) } - p.backup = obj.(*models.SDiskBackup) + stand = &obj.(*models.SDiskBackup).SStandaloneAnonResourceBase + } + if stand == nil { return true, nil } - return false, nil + cm, err := stand.GetAllClassMetadata() + if err != nil { + return false, errors.Wrapf(err, "unable to GetAllClassMetadata %s", stand.GetId()) + } + guestSource.classMedata = cm + if guestSource.keyword == "" { + guestSource.keyword = stand.Keyword() + } + guestSource.name = stand.GetName() + p.guestSource = guestSource + return true, nil } func (p *ClassMetadataPredicate) Execute(u *core.Unit, c core.Candidater) (bool, []core.PredicateFailureReason, error) { h := NewPredicateHelper(p, u, c) - host := c.Getter().Host() ctx := context.Background() - resourceDesc := "" - var sara *db.SStandaloneAnonResourceBase - switch { - case p.cacheImage != nil: - sara = &p.cacheImage.SStandaloneAnonResourceBase - resourceDesc = fmt.Sprintf("image %s", p.cacheImage.GetName()) - case p.snapshot != nil: - sara = &p.snapshot.SStandaloneAnonResourceBase - resourceDesc = fmt.Sprintf("snapshot %s", p.snapshot.GetName()) - case p.backup != nil: - sara = &p.backup.SStandaloneAnonResourceBase - resourceDesc = fmt.Sprintf("backup %s", p.backup.GetName()) - case p.instanceBackup != nil: - sara = &p.instanceBackup.SStandaloneAnonResourceBase - resourceDesc = fmt.Sprintf("instance backup %s", p.instanceBackup.GetName()) - case p.instanceSnapshot != nil: - sara = &p.instanceSnapshot.SStandaloneAnonResourceBase - resourceDesc = fmt.Sprintf("instance snapshot %s", p.instanceSnapshot.GetName()) - } - ic, err := host.IsInSameClass(ctx, sara) - if err != nil { - return false, nil, errors.Wrap(err, "unable to determine whether they are in a class") - } - if !ic { - h.Exclude(fmt.Sprintf("The host doesn't have the same class metadata as the choosen %s.", resourceDesc)) + for _, resource := range []*ResourceWithClassMetadata{p.tenant, p.guestSource} { + ic, err := db.IsInSameClass(ctx, c.Getter(), resource) + if err != nil { + return false, nil, errors.Wrap(err, "unable to determine whether they are in a class") + } + if !ic { + h.Exclude(fmt.Sprintf("The host doesn't have the same class metadata as the choosen %s.", resource.GetDescription())) + break + } } return h.GetResult() } diff --git a/pkg/scheduler/cache/candidate/base.go b/pkg/scheduler/cache/candidate/base.go index a719159391..f10fbd1a1a 100644 --- a/pkg/scheduler/cache/candidate/base.go +++ b/pkg/scheduler/cache/candidate/base.go @@ -56,6 +56,8 @@ type BaseHostDesc struct { SharedDomains []string `json:"shared_domains"` PendingUsage map[string]interface{} `json:"pending_usage"` + + ClassMetadata map[string]string `json:"class_metadata"` } type baseHostGetter struct { @@ -148,6 +150,10 @@ func (b baseHostGetter) InstanceGroups() map[string]*api.CandidateGroup { return b.h.InstanceGroups } +func (b baseHostGetter) GetAllClassMetadata() (map[string]string, error) { + return b.h.ClassMetadata, nil +} + func (b baseHostGetter) GetFreeGroupCount(groupId string) (int, error) { // Must Be scg, ok := b.h.InstanceGroups[groupId] @@ -333,6 +339,10 @@ func newBaseHostDesc(b *baseBuilder, host *computemodels.SHost) (*BaseHostDesc, return nil, fmt.Errorf("Fill instance group error: %v", err) } + if err := desc.fillClassMetadata(host); err != nil { + return nil, fmt.Errorf("Fill class metadata error: %v", err) + } + if err := desc.fillIpmiInfo(host); err != nil { return nil, fmt.Errorf("Fill ipmi info error: %v", err) } @@ -674,6 +684,15 @@ func (b *BaseHostDesc) fillInstanceGroups(host *computemodels.SHost) error { return nil } +func (b *BaseHostDesc) fillClassMetadata(host *computemodels.SHost) error { + cm, err := host.GetAllClassMetadata() + if err != nil { + return err + } + b.ClassMetadata = cm + return nil +} + func (b *BaseHostDesc) fillIpmiInfo(host *computemodels.SHost) error { info, err := host.GetIpmiInfo() if err != nil { diff --git a/pkg/scheduler/core/types.go b/pkg/scheduler/core/types.go index 57abe15a71..d9a36ccb8f 100644 --- a/pkg/scheduler/core/types.go +++ b/pkg/scheduler/core/types.go @@ -106,6 +106,8 @@ type CandidatePropertyGetter interface { InstanceGroups() map[string]*api.CandidateGroup GetFreeGroupCount(groupId string) (int, error) + GetAllClassMetadata() (map[string]string, error) + GetIpmiInfo() types.SIPMIInfo GetNics() []*types.SNic