| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396 |
- // 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 db
- import (
- "context"
- "crypto/md5"
- "fmt"
- "strings"
- "time"
- "yunion.io/x/jsonutils"
- "yunion.io/x/log"
- "yunion.io/x/pkg/util/rbacscope"
- "yunion.io/x/pkg/utils"
- "yunion.io/x/sqlchemy"
- "yunion.io/x/onecloud/pkg/apis"
- "yunion.io/x/onecloud/pkg/httperrors"
- "yunion.io/x/onecloud/pkg/mcclient"
- "yunion.io/x/onecloud/pkg/util/hashcache"
- "yunion.io/x/onecloud/pkg/util/rbacutils"
- "yunion.io/x/onecloud/pkg/util/stringutils2"
- "yunion.io/x/onecloud/pkg/util/tagutils"
- )
- type SMetadataResourceBaseModelManager struct{}
- func ObjectIdQueryWithPolicyResult(ctx context.Context, q *sqlchemy.SQuery, manager IModelManager, result rbacutils.SPolicyResult) *sqlchemy.SQuery {
- scope := manager.ResourceScope()
- if scope == rbacscope.ScopeDomain || scope == rbacscope.ScopeProject {
- if !result.DomainTags.IsEmpty() {
- tagFilters := tagutils.STagFilters{}
- tagFilters.AddFilters(result.DomainTags)
- q = ObjectIdQueryWithTagFilters(ctx, q, "domain_id", "domain", tagFilters)
- }
- }
- if scope == rbacscope.ScopeProject {
- if !result.ProjectTags.IsEmpty() {
- tagFilters := tagutils.STagFilters{}
- tagFilters.AddFilters(result.ProjectTags)
- q = ObjectIdQueryWithTagFilters(ctx, q, "tenant_id", "project", tagFilters)
- }
- }
- if !result.ObjectTags.IsEmpty() {
- tagFilters := tagutils.STagFilters{}
- tagFilters.AddFilters(result.ObjectTags)
- q = ObjectIdQueryWithTagFilters(ctx, q, "id", manager.Keyword(), tagFilters)
- }
- return q
- }
- func ObjectIdQueryWithTagFiltersOptimized(ctx context.Context, q *sqlchemy.SQuery, idField string, modelName string, filters tagutils.STagFilters) *sqlchemy.SQuery {
- if len(filters.Filters) > 0 || len(filters.NoFilters) > 0 {
- idSubQ := q.Copy().SubQuery().Query()
- idSubQ.AppendField(sqlchemy.DISTINCT(idField, idSubQ.Field(idField)))
- if len(filters.Filters) > 0 {
- if GetMetadaManagerInContext(ctx) == Metadata {
- sq := tenantIdQueryWithTags(ctx, modelName, filters.Filters)
- q = q.In(idField, sq.SubQuery())
- } else { // clickhouse
- ids := tenantIdQueryWithTagsWithCache(ctx, modelName, filters.Filters)
- if len(ids) > 0 {
- q = q.In(idField, ids)
- }
- }
- }
- if len(filters.NoFilters) > 0 {
- if GetMetadaManagerInContext(ctx) == Metadata {
- sq := tenantIdQueryWithTags(ctx, modelName, filters.Filters)
- q = q.NotIn(idField, sq.SubQuery())
- } else { // clickhouse
- ids := tenantIdQueryWithTagsWithCache(ctx, modelName, filters.NoFilters)
- if len(ids) > 0 {
- q = q.NotIn(idField, ids)
- }
- }
- }
- }
- return q
- }
- func ObjectIdQueryWithTagFilters(ctx context.Context, q *sqlchemy.SQuery, idField string, modelName string, filters tagutils.STagFilters) *sqlchemy.SQuery {
- if len(filters.Filters) > 0 || len(filters.NoFilters) > 0 {
- idSubQ := q.Copy().SubQuery().Query()
- idSubQ.AppendField(sqlchemy.DISTINCT(idField, idSubQ.Field(idField)))
- subQ := idSubQ.SubQuery()
- if len(filters.Filters) > 0 {
- sq := objIdQueryWithTags(ctx, subQ, idField, modelName, filters.Filters)
- if sq != nil {
- sqq := sq.SubQuery()
- q = q.Join(sqq, sqlchemy.Equals(q.Field(idField), sqq.Field(idField)))
- }
- }
- if len(filters.NoFilters) > 0 {
- sq := objIdQueryWithTags(ctx, subQ, idField, modelName, filters.NoFilters)
- if sq != nil {
- sqq := sq.SubQuery()
- q = q.LeftJoin(sqq, sqlchemy.Equals(q.Field(idField), sqq.Field(idField)))
- q = q.Filter(sqlchemy.IsNull(sqq.Field(idField)))
- }
- }
- }
- return q
- }
- func ExtendQueryWithTag(ctx context.Context, q *sqlchemy.SQuery, idField string, modelName string, key string, fieldLabel string) *sqlchemy.SQuery {
- manager := GetMetadaManagerInContext(ctx)
- metadataQ := manager.Query().Equals("obj_type", modelName).Equals("key", key)
- metadataQ = metadataQ.AppendField(metadataQ.Field("value").Label(fieldLabel))
- metadataResQ := metadataQ.SubQuery()
- q = q.LeftJoin(metadataResQ, sqlchemy.Equals(q.Field(idField), metadataResQ.Field("obj_id")))
- q = q.AppendField(metadataResQ.Field(fieldLabel).Label(fieldLabel))
- return q
- }
- func tenantIdQueryWithTags(ctx context.Context, modelName string, tagsList []map[string][]string) *sqlchemy.SQuery {
- manager := GetMetadaManagerInContext(ctx)
- conditions := []sqlchemy.ICondition{}
- sq := manager.Query("obj_id")
- for _, tags := range tagsList {
- if len(tags) == 0 {
- continue
- }
- subconds := []sqlchemy.ICondition{}
- for key, val := range tags {
- if len(val) > 0 {
- sqq := sq.Copy().Equals("obj_type", modelName).Equals("key", key).In("value", val)
- subconds = append(subconds, sqlchemy.In(sq.Field("obj_id"), sqq.SubQuery()))
- } else {
- sqq := sq.Copy().Equals("obj_type", modelName).Equals("key", key)
- subconds = append(subconds, sqlchemy.In(sq.Field("obj_id"), sqq.SubQuery()))
- }
- }
- conditions = append(conditions, sqlchemy.AND(subconds...))
- }
- return sq.Filter(sqlchemy.OR(conditions...)).Distinct()
- }
- var (
- tagsCache = hashcache.NewCache(1024, time.Minute*15)
- )
- func tenantIdQueryWithTagsWithCache(ctx context.Context, modelName string, tagsList []map[string][]string) []string {
- manager := Metadata
- ret := []string{}
- sq := manager.Query("obj_id")
- for _, tags := range tagsList {
- if len(tags) == 0 {
- continue
- }
- hashKeys := []string{modelName, jsonutils.Marshal(tags).String()}
- hash := fmt.Sprintf("%x", md5.Sum([]byte(jsonutils.Marshal(hashKeys).String())))
- cache := tagsCache.Get(hash)
- if cache != nil {
- ids := cache.([]string)
- ret = append(ret, ids...)
- log.Debugf("cache hit %s %s %s", hash, hashKeys, ids)
- continue
- }
- conditions := []sqlchemy.ICondition{}
- for key, val := range tags {
- if len(val) > 0 {
- sqq := sq.Copy().Equals("obj_type", modelName).Equals("key", key).In("value", val)
- conditions = append(conditions, sqlchemy.In(sq.Field("obj_id"), sqq.SubQuery()))
- } else {
- sqq := sq.Copy().Equals("obj_type", modelName).Equals("key", key)
- conditions = append(conditions, sqlchemy.In(sq.Field("obj_id"), sqq.SubQuery()))
- }
- }
- ids, err := FetchIds(sq.Copy().Filter(sqlchemy.AND(conditions...)).Distinct())
- if err != nil {
- log.Errorf("FetchIds %s %v", sq.String(), err)
- continue
- }
- ret = append(ret, ids...)
- log.Debugf("cache miss %s %s %s", hash, hashKeys, ids)
- tagsCache.AtomicSet(hash, ids)
- }
- return ret
- }
- func objIdQueryWithTags(ctx context.Context, objIdSubQ *sqlchemy.SSubQuery, idField string, modelName string, tagsList []map[string][]string) *sqlchemy.SQuery {
- manager := GetMetadaManagerInContext(ctx)
- queries := make([]sqlchemy.IQuery, 0)
- for _, tags := range tagsList {
- if len(tags) == 0 {
- continue
- }
- objIdQ := objIdSubQ.Query()
- objIdQ = objIdQ.AppendField(objIdQ.Field(idField))
- for key, val := range tags {
- sq := manager.Query("obj_id").Equals("obj_type", modelName).Equals("key", key)
- if len(val) > 0 {
- ssq := sq.In("value", val).SubQuery()
- if utils.IsInArray(tagutils.NoValue, val) {
- objIdQ = objIdQ.LeftJoin(ssq, sqlchemy.Equals(objIdQ.Field(idField), ssq.Field("obj_id")))
- } else {
- objIdQ = objIdQ.Join(ssq, sqlchemy.Equals(objIdQ.Field(idField), ssq.Field("obj_id")))
- }
- } else {
- ssq := sq.SubQuery()
- objIdQ = objIdQ.Join(ssq, sqlchemy.Equals(objIdQ.Field(idField), ssq.Field("obj_id")))
- }
- }
- queries = append(queries, objIdQ.Distinct())
- }
- if len(queries) == 0 {
- return nil
- }
- var query *sqlchemy.SQuery
- if len(queries) == 1 {
- query = queries[0].(*sqlchemy.SQuery)
- } else {
- uq, _ := sqlchemy.UnionWithError(queries...)
- query = uq.Query()
- }
- return query
- }
- func (meta *SMetadataResourceBaseModelManager) ListItemFilter(
- ctx context.Context,
- manager IModelManager,
- q *sqlchemy.SQuery,
- input apis.MetadataResourceListInput,
- ) *sqlchemy.SQuery {
- metadataMan := GetMetadaManagerInContext(ctx)
- inputTagFilters := tagutils.STagFilters{}
- if len(input.Tags) > 0 {
- inputTagFilters.AddFilter(input.Tags)
- }
- if !input.ObjTags.IsEmpty() {
- inputTagFilters.AddFilters(input.ObjTags)
- }
- if len(input.NoTags) > 0 {
- inputTagFilters.AddNoFilter(input.NoTags)
- }
- if !input.NoObjTags.IsEmpty() {
- inputTagFilters.AddNoFilters(input.NoObjTags)
- }
- q = ObjectIdQueryWithTagFilters(ctx, q, "id", manager.Keyword(), inputTagFilters)
- //if !input.PolicyObjectTags.IsEmpty() {
- // projTagFilters := tagutils.STagFilters{}
- // projTagFilters.AddFilters(input.PolicyObjectTags)
- // q = ObjectIdQueryWithTagFilters(q, "id", manager.Keyword(), projTagFilters)
- //}
- if input.WithoutUserMeta != nil || input.WithUserMeta != nil {
- metadatas := metadataMan.Query().Equals("obj_type", manager.Keyword()).SubQuery()
- sq := metadatas.Query(metadatas.Field("obj_id")).Startswith("key", USER_TAG_PREFIX).Distinct().SubQuery()
- if (input.WithoutUserMeta != nil && *input.WithoutUserMeta) || (input.WithUserMeta != nil && !*input.WithUserMeta) {
- q = q.Filter(sqlchemy.NotIn(q.Field("id"), sq))
- } else {
- q = q.Filter(sqlchemy.In(q.Field("id"), sq))
- }
- }
- if input.WithCloudMeta != nil {
- metadatas := metadataMan.Query().Equals("obj_type", manager.Keyword()).SubQuery()
- sq := metadatas.Query(metadatas.Field("obj_id")).Startswith("key", CLOUD_TAG_PREFIX).Distinct().SubQuery()
- if *input.WithCloudMeta {
- q = q.Filter(sqlchemy.In(q.Field("id"), sq))
- } else {
- q = q.Filter(sqlchemy.NotIn(q.Field("id"), sq))
- }
- }
- if input.WithAnyMeta != nil {
- metadatas := metadataMan.Query().Equals("obj_type", manager.Keyword()).SubQuery()
- sq := metadatas.Query(metadatas.Field("obj_id")).Distinct().SubQuery()
- if *input.WithAnyMeta {
- q = q.Filter(sqlchemy.In(q.Field("id"), sq))
- } else {
- q = q.Filter(sqlchemy.NotIn(q.Field("id"), sq))
- }
- }
- return q
- }
- func (meta *SMetadataResourceBaseModelManager) QueryDistinctExtraField(manager IModelManager, q *sqlchemy.SQuery, field string) (*sqlchemy.SQuery, error) {
- if strings.HasPrefix(field, "tag:") {
- tagKey := field[4:]
- metaQ := Metadata.Query("obj_id", "value").Equals("obj_type", manager.Keyword()).Equals("key", tagKey).SubQuery()
- q = q.AppendField(metaQ.Field("value", field)).Distinct()
- q = q.LeftJoin(metaQ, sqlchemy.Equals(q.Field("id"), metaQ.Field("obj_id")))
- q = q.Asc(metaQ.Field("value"))
- return q, nil
- }
- return q, httperrors.ErrNotFound
- }
- func (meta *SMetadataResourceBaseModelManager) OrderByExtraFields(
- manager IModelManager,
- q *sqlchemy.SQuery,
- input apis.MetadataResourceListInput,
- ) *sqlchemy.SQuery {
- if len(input.OrderByTag) > 0 {
- order := sqlchemy.SQL_ORDER_ASC
- tagKey := input.OrderByTag
- if stringutils2.HasSuffixIgnoreCase(input.OrderByTag, string(sqlchemy.SQL_ORDER_ASC)) {
- tagKey = tagKey[0 : len(tagKey)-len(sqlchemy.SQL_ORDER_ASC)-1]
- } else if stringutils2.HasSuffixIgnoreCase(input.OrderByTag, string(sqlchemy.SQL_ORDER_DESC)) {
- tagKey = tagKey[0 : len(tagKey)-len(sqlchemy.SQL_ORDER_DESC)-1]
- order = sqlchemy.SQL_ORDER_DESC
- }
- metaQ := Metadata.Query("obj_id", "value").Equals("obj_type", manager.Keyword()).Equals("key", tagKey).SubQuery()
- q = q.LeftJoin(metaQ, sqlchemy.Equals(q.Field("id"), metaQ.Field("obj_id")))
- if order == sqlchemy.SQL_ORDER_ASC {
- q = q.Asc(metaQ.Field("value"))
- } else {
- q = q.Desc(metaQ.Field("value"))
- }
- }
- return q
- }
- func (meta *SMetadataResourceBaseModelManager) FetchCustomizeColumns(
- manager IModelManager,
- userCred mcclient.TokenCredential,
- objs []interface{},
- fields stringutils2.SSortedStrings,
- ) []apis.MetadataResourceInfo {
- ret := make([]apis.MetadataResourceInfo, len(objs))
- resIds := make([]string, len(objs))
- for i := range objs {
- resIds[i] = GetModelIdstr(objs[i].(IModel))
- }
- if fields == nil || fields.Contains("__meta__") || fields.Contains("metadata") {
- q := Metadata.Query("id", "key", "value")
- metaKeyValues := make(map[string][]SMetadata)
- err := FetchQueryObjectsByIds(q, "id", resIds, &metaKeyValues)
- if err != nil {
- log.Errorf("FetchQueryObjectsByIds metadata fail %s", err)
- return ret
- }
- for i := range objs {
- if metaList, ok := metaKeyValues[resIds[i]]; ok {
- ret[i].Metadata = metaList2Map(manager.(IMetadataBaseModelManager), userCred, metaList)
- }
- }
- }
- return ret
- }
- const (
- TAG_EXPORT_KEY_PREFIX = "tag:"
- )
- func (meta *SMetadataResourceBaseModelManager) GetExportExtraKeys(keys stringutils2.SSortedStrings, rowMap map[string]string) *jsonutils.JSONDict {
- res := jsonutils.NewDict()
- for _, key := range keys {
- if strings.HasPrefix(key, TAG_EXPORT_KEY_PREFIX) {
- res.Add(jsonutils.NewString(rowMap[key]), key)
- }
- }
- return res
- }
- func (meta *SMetadataResourceBaseModelManager) ListItemExportKeys(manager IModelManager, q *sqlchemy.SQuery, keys stringutils2.SSortedStrings) *sqlchemy.SQuery {
- keyMaps := map[string]bool{}
- for _, key := range keys {
- if strings.HasPrefix(key, TAG_EXPORT_KEY_PREFIX) {
- tagKey := key[len(TAG_EXPORT_KEY_PREFIX):]
- if _, ok := keyMaps[strings.ToLower(tagKey)]; !ok {
- metaQ := Metadata.Query("obj_id", "value").Equals("obj_type", manager.Keyword()).Equals("key", tagKey).SubQuery()
- q = q.LeftJoin(metaQ, sqlchemy.Equals(q.Field("id"), metaQ.Field("obj_id")))
- q = q.AppendField(metaQ.Field("value", key))
- keyMaps[strings.ToLower(tagKey)] = true
- }
- }
- }
- return q
- }
|