metadataresource.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396
  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 db
  15. import (
  16. "context"
  17. "crypto/md5"
  18. "fmt"
  19. "strings"
  20. "time"
  21. "yunion.io/x/jsonutils"
  22. "yunion.io/x/log"
  23. "yunion.io/x/pkg/util/rbacscope"
  24. "yunion.io/x/pkg/utils"
  25. "yunion.io/x/sqlchemy"
  26. "yunion.io/x/onecloud/pkg/apis"
  27. "yunion.io/x/onecloud/pkg/httperrors"
  28. "yunion.io/x/onecloud/pkg/mcclient"
  29. "yunion.io/x/onecloud/pkg/util/hashcache"
  30. "yunion.io/x/onecloud/pkg/util/rbacutils"
  31. "yunion.io/x/onecloud/pkg/util/stringutils2"
  32. "yunion.io/x/onecloud/pkg/util/tagutils"
  33. )
  34. type SMetadataResourceBaseModelManager struct{}
  35. func ObjectIdQueryWithPolicyResult(ctx context.Context, q *sqlchemy.SQuery, manager IModelManager, result rbacutils.SPolicyResult) *sqlchemy.SQuery {
  36. scope := manager.ResourceScope()
  37. if scope == rbacscope.ScopeDomain || scope == rbacscope.ScopeProject {
  38. if !result.DomainTags.IsEmpty() {
  39. tagFilters := tagutils.STagFilters{}
  40. tagFilters.AddFilters(result.DomainTags)
  41. q = ObjectIdQueryWithTagFilters(ctx, q, "domain_id", "domain", tagFilters)
  42. }
  43. }
  44. if scope == rbacscope.ScopeProject {
  45. if !result.ProjectTags.IsEmpty() {
  46. tagFilters := tagutils.STagFilters{}
  47. tagFilters.AddFilters(result.ProjectTags)
  48. q = ObjectIdQueryWithTagFilters(ctx, q, "tenant_id", "project", tagFilters)
  49. }
  50. }
  51. if !result.ObjectTags.IsEmpty() {
  52. tagFilters := tagutils.STagFilters{}
  53. tagFilters.AddFilters(result.ObjectTags)
  54. q = ObjectIdQueryWithTagFilters(ctx, q, "id", manager.Keyword(), tagFilters)
  55. }
  56. return q
  57. }
  58. func ObjectIdQueryWithTagFiltersOptimized(ctx context.Context, q *sqlchemy.SQuery, idField string, modelName string, filters tagutils.STagFilters) *sqlchemy.SQuery {
  59. if len(filters.Filters) > 0 || len(filters.NoFilters) > 0 {
  60. idSubQ := q.Copy().SubQuery().Query()
  61. idSubQ.AppendField(sqlchemy.DISTINCT(idField, idSubQ.Field(idField)))
  62. if len(filters.Filters) > 0 {
  63. if GetMetadaManagerInContext(ctx) == Metadata {
  64. sq := tenantIdQueryWithTags(ctx, modelName, filters.Filters)
  65. q = q.In(idField, sq.SubQuery())
  66. } else { // clickhouse
  67. ids := tenantIdQueryWithTagsWithCache(ctx, modelName, filters.Filters)
  68. if len(ids) > 0 {
  69. q = q.In(idField, ids)
  70. }
  71. }
  72. }
  73. if len(filters.NoFilters) > 0 {
  74. if GetMetadaManagerInContext(ctx) == Metadata {
  75. sq := tenantIdQueryWithTags(ctx, modelName, filters.Filters)
  76. q = q.NotIn(idField, sq.SubQuery())
  77. } else { // clickhouse
  78. ids := tenantIdQueryWithTagsWithCache(ctx, modelName, filters.NoFilters)
  79. if len(ids) > 0 {
  80. q = q.NotIn(idField, ids)
  81. }
  82. }
  83. }
  84. }
  85. return q
  86. }
  87. func ObjectIdQueryWithTagFilters(ctx context.Context, q *sqlchemy.SQuery, idField string, modelName string, filters tagutils.STagFilters) *sqlchemy.SQuery {
  88. if len(filters.Filters) > 0 || len(filters.NoFilters) > 0 {
  89. idSubQ := q.Copy().SubQuery().Query()
  90. idSubQ.AppendField(sqlchemy.DISTINCT(idField, idSubQ.Field(idField)))
  91. subQ := idSubQ.SubQuery()
  92. if len(filters.Filters) > 0 {
  93. sq := objIdQueryWithTags(ctx, subQ, idField, modelName, filters.Filters)
  94. if sq != nil {
  95. sqq := sq.SubQuery()
  96. q = q.Join(sqq, sqlchemy.Equals(q.Field(idField), sqq.Field(idField)))
  97. }
  98. }
  99. if len(filters.NoFilters) > 0 {
  100. sq := objIdQueryWithTags(ctx, subQ, idField, modelName, filters.NoFilters)
  101. if sq != nil {
  102. sqq := sq.SubQuery()
  103. q = q.LeftJoin(sqq, sqlchemy.Equals(q.Field(idField), sqq.Field(idField)))
  104. q = q.Filter(sqlchemy.IsNull(sqq.Field(idField)))
  105. }
  106. }
  107. }
  108. return q
  109. }
  110. func ExtendQueryWithTag(ctx context.Context, q *sqlchemy.SQuery, idField string, modelName string, key string, fieldLabel string) *sqlchemy.SQuery {
  111. manager := GetMetadaManagerInContext(ctx)
  112. metadataQ := manager.Query().Equals("obj_type", modelName).Equals("key", key)
  113. metadataQ = metadataQ.AppendField(metadataQ.Field("value").Label(fieldLabel))
  114. metadataResQ := metadataQ.SubQuery()
  115. q = q.LeftJoin(metadataResQ, sqlchemy.Equals(q.Field(idField), metadataResQ.Field("obj_id")))
  116. q = q.AppendField(metadataResQ.Field(fieldLabel).Label(fieldLabel))
  117. return q
  118. }
  119. func tenantIdQueryWithTags(ctx context.Context, modelName string, tagsList []map[string][]string) *sqlchemy.SQuery {
  120. manager := GetMetadaManagerInContext(ctx)
  121. conditions := []sqlchemy.ICondition{}
  122. sq := manager.Query("obj_id")
  123. for _, tags := range tagsList {
  124. if len(tags) == 0 {
  125. continue
  126. }
  127. subconds := []sqlchemy.ICondition{}
  128. for key, val := range tags {
  129. if len(val) > 0 {
  130. sqq := sq.Copy().Equals("obj_type", modelName).Equals("key", key).In("value", val)
  131. subconds = append(subconds, sqlchemy.In(sq.Field("obj_id"), sqq.SubQuery()))
  132. } else {
  133. sqq := sq.Copy().Equals("obj_type", modelName).Equals("key", key)
  134. subconds = append(subconds, sqlchemy.In(sq.Field("obj_id"), sqq.SubQuery()))
  135. }
  136. }
  137. conditions = append(conditions, sqlchemy.AND(subconds...))
  138. }
  139. return sq.Filter(sqlchemy.OR(conditions...)).Distinct()
  140. }
  141. var (
  142. tagsCache = hashcache.NewCache(1024, time.Minute*15)
  143. )
  144. func tenantIdQueryWithTagsWithCache(ctx context.Context, modelName string, tagsList []map[string][]string) []string {
  145. manager := Metadata
  146. ret := []string{}
  147. sq := manager.Query("obj_id")
  148. for _, tags := range tagsList {
  149. if len(tags) == 0 {
  150. continue
  151. }
  152. hashKeys := []string{modelName, jsonutils.Marshal(tags).String()}
  153. hash := fmt.Sprintf("%x", md5.Sum([]byte(jsonutils.Marshal(hashKeys).String())))
  154. cache := tagsCache.Get(hash)
  155. if cache != nil {
  156. ids := cache.([]string)
  157. ret = append(ret, ids...)
  158. log.Debugf("cache hit %s %s %s", hash, hashKeys, ids)
  159. continue
  160. }
  161. conditions := []sqlchemy.ICondition{}
  162. for key, val := range tags {
  163. if len(val) > 0 {
  164. sqq := sq.Copy().Equals("obj_type", modelName).Equals("key", key).In("value", val)
  165. conditions = append(conditions, sqlchemy.In(sq.Field("obj_id"), sqq.SubQuery()))
  166. } else {
  167. sqq := sq.Copy().Equals("obj_type", modelName).Equals("key", key)
  168. conditions = append(conditions, sqlchemy.In(sq.Field("obj_id"), sqq.SubQuery()))
  169. }
  170. }
  171. ids, err := FetchIds(sq.Copy().Filter(sqlchemy.AND(conditions...)).Distinct())
  172. if err != nil {
  173. log.Errorf("FetchIds %s %v", sq.String(), err)
  174. continue
  175. }
  176. ret = append(ret, ids...)
  177. log.Debugf("cache miss %s %s %s", hash, hashKeys, ids)
  178. tagsCache.AtomicSet(hash, ids)
  179. }
  180. return ret
  181. }
  182. func objIdQueryWithTags(ctx context.Context, objIdSubQ *sqlchemy.SSubQuery, idField string, modelName string, tagsList []map[string][]string) *sqlchemy.SQuery {
  183. manager := GetMetadaManagerInContext(ctx)
  184. queries := make([]sqlchemy.IQuery, 0)
  185. for _, tags := range tagsList {
  186. if len(tags) == 0 {
  187. continue
  188. }
  189. objIdQ := objIdSubQ.Query()
  190. objIdQ = objIdQ.AppendField(objIdQ.Field(idField))
  191. for key, val := range tags {
  192. sq := manager.Query("obj_id").Equals("obj_type", modelName).Equals("key", key)
  193. if len(val) > 0 {
  194. ssq := sq.In("value", val).SubQuery()
  195. if utils.IsInArray(tagutils.NoValue, val) {
  196. objIdQ = objIdQ.LeftJoin(ssq, sqlchemy.Equals(objIdQ.Field(idField), ssq.Field("obj_id")))
  197. } else {
  198. objIdQ = objIdQ.Join(ssq, sqlchemy.Equals(objIdQ.Field(idField), ssq.Field("obj_id")))
  199. }
  200. } else {
  201. ssq := sq.SubQuery()
  202. objIdQ = objIdQ.Join(ssq, sqlchemy.Equals(objIdQ.Field(idField), ssq.Field("obj_id")))
  203. }
  204. }
  205. queries = append(queries, objIdQ.Distinct())
  206. }
  207. if len(queries) == 0 {
  208. return nil
  209. }
  210. var query *sqlchemy.SQuery
  211. if len(queries) == 1 {
  212. query = queries[0].(*sqlchemy.SQuery)
  213. } else {
  214. uq, _ := sqlchemy.UnionWithError(queries...)
  215. query = uq.Query()
  216. }
  217. return query
  218. }
  219. func (meta *SMetadataResourceBaseModelManager) ListItemFilter(
  220. ctx context.Context,
  221. manager IModelManager,
  222. q *sqlchemy.SQuery,
  223. input apis.MetadataResourceListInput,
  224. ) *sqlchemy.SQuery {
  225. metadataMan := GetMetadaManagerInContext(ctx)
  226. inputTagFilters := tagutils.STagFilters{}
  227. if len(input.Tags) > 0 {
  228. inputTagFilters.AddFilter(input.Tags)
  229. }
  230. if !input.ObjTags.IsEmpty() {
  231. inputTagFilters.AddFilters(input.ObjTags)
  232. }
  233. if len(input.NoTags) > 0 {
  234. inputTagFilters.AddNoFilter(input.NoTags)
  235. }
  236. if !input.NoObjTags.IsEmpty() {
  237. inputTagFilters.AddNoFilters(input.NoObjTags)
  238. }
  239. q = ObjectIdQueryWithTagFilters(ctx, q, "id", manager.Keyword(), inputTagFilters)
  240. //if !input.PolicyObjectTags.IsEmpty() {
  241. // projTagFilters := tagutils.STagFilters{}
  242. // projTagFilters.AddFilters(input.PolicyObjectTags)
  243. // q = ObjectIdQueryWithTagFilters(q, "id", manager.Keyword(), projTagFilters)
  244. //}
  245. if input.WithoutUserMeta != nil || input.WithUserMeta != nil {
  246. metadatas := metadataMan.Query().Equals("obj_type", manager.Keyword()).SubQuery()
  247. sq := metadatas.Query(metadatas.Field("obj_id")).Startswith("key", USER_TAG_PREFIX).Distinct().SubQuery()
  248. if (input.WithoutUserMeta != nil && *input.WithoutUserMeta) || (input.WithUserMeta != nil && !*input.WithUserMeta) {
  249. q = q.Filter(sqlchemy.NotIn(q.Field("id"), sq))
  250. } else {
  251. q = q.Filter(sqlchemy.In(q.Field("id"), sq))
  252. }
  253. }
  254. if input.WithCloudMeta != nil {
  255. metadatas := metadataMan.Query().Equals("obj_type", manager.Keyword()).SubQuery()
  256. sq := metadatas.Query(metadatas.Field("obj_id")).Startswith("key", CLOUD_TAG_PREFIX).Distinct().SubQuery()
  257. if *input.WithCloudMeta {
  258. q = q.Filter(sqlchemy.In(q.Field("id"), sq))
  259. } else {
  260. q = q.Filter(sqlchemy.NotIn(q.Field("id"), sq))
  261. }
  262. }
  263. if input.WithAnyMeta != nil {
  264. metadatas := metadataMan.Query().Equals("obj_type", manager.Keyword()).SubQuery()
  265. sq := metadatas.Query(metadatas.Field("obj_id")).Distinct().SubQuery()
  266. if *input.WithAnyMeta {
  267. q = q.Filter(sqlchemy.In(q.Field("id"), sq))
  268. } else {
  269. q = q.Filter(sqlchemy.NotIn(q.Field("id"), sq))
  270. }
  271. }
  272. return q
  273. }
  274. func (meta *SMetadataResourceBaseModelManager) QueryDistinctExtraField(manager IModelManager, q *sqlchemy.SQuery, field string) (*sqlchemy.SQuery, error) {
  275. if strings.HasPrefix(field, "tag:") {
  276. tagKey := field[4:]
  277. metaQ := Metadata.Query("obj_id", "value").Equals("obj_type", manager.Keyword()).Equals("key", tagKey).SubQuery()
  278. q = q.AppendField(metaQ.Field("value", field)).Distinct()
  279. q = q.LeftJoin(metaQ, sqlchemy.Equals(q.Field("id"), metaQ.Field("obj_id")))
  280. q = q.Asc(metaQ.Field("value"))
  281. return q, nil
  282. }
  283. return q, httperrors.ErrNotFound
  284. }
  285. func (meta *SMetadataResourceBaseModelManager) OrderByExtraFields(
  286. manager IModelManager,
  287. q *sqlchemy.SQuery,
  288. input apis.MetadataResourceListInput,
  289. ) *sqlchemy.SQuery {
  290. if len(input.OrderByTag) > 0 {
  291. order := sqlchemy.SQL_ORDER_ASC
  292. tagKey := input.OrderByTag
  293. if stringutils2.HasSuffixIgnoreCase(input.OrderByTag, string(sqlchemy.SQL_ORDER_ASC)) {
  294. tagKey = tagKey[0 : len(tagKey)-len(sqlchemy.SQL_ORDER_ASC)-1]
  295. } else if stringutils2.HasSuffixIgnoreCase(input.OrderByTag, string(sqlchemy.SQL_ORDER_DESC)) {
  296. tagKey = tagKey[0 : len(tagKey)-len(sqlchemy.SQL_ORDER_DESC)-1]
  297. order = sqlchemy.SQL_ORDER_DESC
  298. }
  299. metaQ := Metadata.Query("obj_id", "value").Equals("obj_type", manager.Keyword()).Equals("key", tagKey).SubQuery()
  300. q = q.LeftJoin(metaQ, sqlchemy.Equals(q.Field("id"), metaQ.Field("obj_id")))
  301. if order == sqlchemy.SQL_ORDER_ASC {
  302. q = q.Asc(metaQ.Field("value"))
  303. } else {
  304. q = q.Desc(metaQ.Field("value"))
  305. }
  306. }
  307. return q
  308. }
  309. func (meta *SMetadataResourceBaseModelManager) FetchCustomizeColumns(
  310. manager IModelManager,
  311. userCred mcclient.TokenCredential,
  312. objs []interface{},
  313. fields stringutils2.SSortedStrings,
  314. ) []apis.MetadataResourceInfo {
  315. ret := make([]apis.MetadataResourceInfo, len(objs))
  316. resIds := make([]string, len(objs))
  317. for i := range objs {
  318. resIds[i] = GetModelIdstr(objs[i].(IModel))
  319. }
  320. if fields == nil || fields.Contains("__meta__") || fields.Contains("metadata") {
  321. q := Metadata.Query("id", "key", "value")
  322. metaKeyValues := make(map[string][]SMetadata)
  323. err := FetchQueryObjectsByIds(q, "id", resIds, &metaKeyValues)
  324. if err != nil {
  325. log.Errorf("FetchQueryObjectsByIds metadata fail %s", err)
  326. return ret
  327. }
  328. for i := range objs {
  329. if metaList, ok := metaKeyValues[resIds[i]]; ok {
  330. ret[i].Metadata = metaList2Map(manager.(IMetadataBaseModelManager), userCred, metaList)
  331. }
  332. }
  333. }
  334. return ret
  335. }
  336. const (
  337. TAG_EXPORT_KEY_PREFIX = "tag:"
  338. )
  339. func (meta *SMetadataResourceBaseModelManager) GetExportExtraKeys(keys stringutils2.SSortedStrings, rowMap map[string]string) *jsonutils.JSONDict {
  340. res := jsonutils.NewDict()
  341. for _, key := range keys {
  342. if strings.HasPrefix(key, TAG_EXPORT_KEY_PREFIX) {
  343. res.Add(jsonutils.NewString(rowMap[key]), key)
  344. }
  345. }
  346. return res
  347. }
  348. func (meta *SMetadataResourceBaseModelManager) ListItemExportKeys(manager IModelManager, q *sqlchemy.SQuery, keys stringutils2.SSortedStrings) *sqlchemy.SQuery {
  349. keyMaps := map[string]bool{}
  350. for _, key := range keys {
  351. if strings.HasPrefix(key, TAG_EXPORT_KEY_PREFIX) {
  352. tagKey := key[len(TAG_EXPORT_KEY_PREFIX):]
  353. if _, ok := keyMaps[strings.ToLower(tagKey)]; !ok {
  354. metaQ := Metadata.Query("obj_id", "value").Equals("obj_type", manager.Keyword()).Equals("key", tagKey).SubQuery()
  355. q = q.LeftJoin(metaQ, sqlchemy.Equals(q.Field("id"), metaQ.Field("obj_id")))
  356. q = q.AppendField(metaQ.Field("value", key))
  357. keyMaps[strings.ToLower(tagKey)] = true
  358. }
  359. }
  360. }
  361. return q
  362. }