project.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  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. "database/sql"
  18. "time"
  19. "yunion.io/x/jsonutils"
  20. "yunion.io/x/log"
  21. "yunion.io/x/pkg/errors"
  22. "yunion.io/x/pkg/util/rbacscope"
  23. "yunion.io/x/pkg/util/reflectutils"
  24. "yunion.io/x/sqlchemy"
  25. "yunion.io/x/onecloud/pkg/apis"
  26. identityapi "yunion.io/x/onecloud/pkg/apis/identity"
  27. "yunion.io/x/onecloud/pkg/cloudcommon/consts"
  28. "yunion.io/x/onecloud/pkg/cloudcommon/policy"
  29. "yunion.io/x/onecloud/pkg/httperrors"
  30. "yunion.io/x/onecloud/pkg/mcclient"
  31. "yunion.io/x/onecloud/pkg/util/stringutils2"
  32. "yunion.io/x/onecloud/pkg/util/tagutils"
  33. )
  34. type SProjectizedResourceBaseManager struct {
  35. SDomainizedResourceBaseManager
  36. }
  37. type SProjectizedResourceBase struct {
  38. SDomainizedResourceBase
  39. // 项目Id
  40. ProjectId string `name:"tenant_id" width:"128" charset:"ascii" nullable:"false" index:"true" list:"user" json:"tenant_id"`
  41. }
  42. func (model *SProjectizedResourceBase) GetOwnerId() mcclient.IIdentityProvider {
  43. owner := SOwnerId{DomainId: model.DomainId, ProjectId: model.ProjectId}
  44. return &owner
  45. }
  46. func (manager *SProjectizedResourceBaseManager) FilterByOwner(ctx context.Context, q *sqlchemy.SQuery, man FilterByOwnerProvider, userCred mcclient.TokenCredential, owner mcclient.IIdentityProvider, scope rbacscope.TRbacScope) *sqlchemy.SQuery {
  47. if owner != nil {
  48. switch scope {
  49. case rbacscope.ScopeProject:
  50. q = q.Equals("tenant_id", owner.GetProjectId())
  51. if userCred != nil {
  52. result := policy.PolicyManager.Allow(scope, userCred, consts.GetServiceType(), man.KeywordPlural(), policy.PolicyActionList)
  53. if !result.ObjectTags.IsEmpty() {
  54. policyTagFilters := tagutils.STagFilters{}
  55. policyTagFilters.AddFilters(result.ObjectTags)
  56. q = ObjectIdQueryWithTagFiltersOptimized(ctx, q, "id", man.Keyword(), policyTagFilters)
  57. }
  58. }
  59. case rbacscope.ScopeDomain:
  60. q = q.Equals("domain_id", owner.GetProjectDomainId())
  61. if userCred != nil {
  62. result := policy.PolicyManager.Allow(scope, userCred, consts.GetServiceType(), man.KeywordPlural(), policy.PolicyActionList)
  63. if !result.ProjectTags.IsEmpty() {
  64. policyTagFilters := tagutils.STagFilters{}
  65. policyTagFilters.AddFilters(result.ProjectTags)
  66. q = ObjectIdQueryWithTagFiltersOptimized(ctx, q, "tenant_id", "project", policyTagFilters)
  67. }
  68. if !result.ObjectTags.IsEmpty() {
  69. policyTagFilters := tagutils.STagFilters{}
  70. policyTagFilters.AddFilters(result.ObjectTags)
  71. q = ObjectIdQueryWithTagFiltersOptimized(ctx, q, "id", man.Keyword(), policyTagFilters)
  72. }
  73. }
  74. case rbacscope.ScopeSystem:
  75. if userCred != nil {
  76. result := policy.PolicyManager.Allow(scope, userCred, consts.GetServiceType(), man.KeywordPlural(), policy.PolicyActionList)
  77. if !result.DomainTags.IsEmpty() {
  78. policyTagFilters := tagutils.STagFilters{}
  79. policyTagFilters.AddFilters(result.DomainTags)
  80. q = ObjectIdQueryWithTagFiltersOptimized(ctx, q, "domain_id", "domain", policyTagFilters)
  81. }
  82. if !result.ProjectTags.IsEmpty() {
  83. policyTagFilters := tagutils.STagFilters{}
  84. policyTagFilters.AddFilters(result.ProjectTags)
  85. q = ObjectIdQueryWithTagFiltersOptimized(ctx, q, "tenant_id", "project", policyTagFilters)
  86. }
  87. if !result.ObjectTags.IsEmpty() {
  88. policyTagFilters := tagutils.STagFilters{}
  89. policyTagFilters.AddFilters(result.ObjectTags)
  90. q = ObjectIdQueryWithTagFiltersOptimized(ctx, q, "id", man.Keyword(), policyTagFilters)
  91. }
  92. }
  93. }
  94. }
  95. return q
  96. }
  97. func (manager *SProjectizedResourceBaseManager) ResourceScope() rbacscope.TRbacScope {
  98. return rbacscope.ScopeProject
  99. }
  100. func (manager *SProjectizedResourceBaseManager) FetchOwnerId(ctx context.Context, data jsonutils.JSONObject) (mcclient.IIdentityProvider, error) {
  101. return FetchProjectInfo(ctx, data)
  102. }
  103. func (manager *SProjectizedResourceBaseManager) QueryDistinctExtraField(q *sqlchemy.SQuery, field string) (*sqlchemy.SQuery, error) {
  104. switch field {
  105. case "tenant":
  106. tenantCacheQuery := TenantCacheManager.getTable() // GetTenantQuery("name", "id").Distinct().SubQuery()
  107. q.AppendField(tenantCacheQuery.Field("name", "tenant")).Distinct()
  108. q = q.Join(tenantCacheQuery, sqlchemy.Equals(q.Field("tenant_id"), tenantCacheQuery.Field("id")))
  109. return q, nil
  110. }
  111. q, err := manager.SDomainizedResourceBaseManager.QueryDistinctExtraField(q, field)
  112. if err == nil {
  113. return q, nil
  114. }
  115. return q, httperrors.ErrNotFound
  116. }
  117. func (manager *SProjectizedResourceBaseManager) ListItemFilter(
  118. ctx context.Context,
  119. q *sqlchemy.SQuery,
  120. userCred mcclient.TokenCredential,
  121. query apis.ProjectizedResourceListInput,
  122. ) (*sqlchemy.SQuery, error) {
  123. var err error
  124. q, err = manager.SDomainizedResourceBaseManager.ListItemFilter(ctx, q, userCred, query.DomainizedResourceListInput)
  125. if err != nil {
  126. return nil, errors.Wrap(err, "SDomainizedResourceBaseManager.ListItemFilter")
  127. }
  128. if len(query.ProjectIds) > 0 {
  129. // make sure ids are not utf8 string
  130. idList := stringutils2.RemoveUtf8Strings(query.ProjectIds)
  131. tenants := TenantCacheManager.GetTenantQuery().SubQuery()
  132. subq := tenants.Query(tenants.Field("id")).Filter(sqlchemy.OR(
  133. sqlchemy.In(tenants.Field("id"), idList),
  134. sqlchemy.In(tenants.Field("name"), query.ProjectIds),
  135. )).SubQuery()
  136. q = q.In("tenant_id", subq)
  137. }
  138. tagFilters := tagutils.STagFilters{}
  139. /*if len(query.ProjectOrganizations) > 0 {
  140. orgFilters, err := FetchOrganizationTags(ctx, query.ProjectOrganizations, identityapi.OrgTypeProject)
  141. if err != nil {
  142. return nil, errors.Wrap(err, "FetchOrganizationTags")
  143. }
  144. tagFilters.AddFilters(orgFilters)
  145. }*/
  146. if !query.ProjectTags.IsEmpty() {
  147. tagFilters.AddFilters(query.ProjectTags)
  148. }
  149. if !query.NoProjectTags.IsEmpty() {
  150. tagFilters.AddNoFilters(query.NoProjectTags)
  151. }
  152. q = ObjectIdQueryWithTagFilters(ctx, q, "tenant_id", "project", tagFilters)
  153. return q, nil
  154. }
  155. func (manager *SProjectizedResourceBaseManager) OrderByExtraFields(
  156. ctx context.Context,
  157. q *sqlchemy.SQuery,
  158. userCred mcclient.TokenCredential,
  159. query apis.ProjectizedResourceListInput,
  160. ) (*sqlchemy.SQuery, error) {
  161. orders := []string{query.OrderByProject, query.OrderByDomain}
  162. if NeedOrderQuery(orders) {
  163. subq := TenantCacheManager.GetTenantQuery("id", "name", "domain").SubQuery()
  164. q = q.LeftJoin(subq, sqlchemy.Equals(q.Field("tenant_id"), subq.Field("id")))
  165. q = OrderByFields(q, orders, []sqlchemy.IQueryField{subq.Field("name"), subq.Field("domain")})
  166. }
  167. return q, nil
  168. }
  169. func (manager *SProjectizedResourceBaseManager) FetchCustomizeColumns(
  170. ctx context.Context,
  171. userCred mcclient.TokenCredential,
  172. query jsonutils.JSONObject,
  173. objs []interface{},
  174. fields stringutils2.SSortedStrings,
  175. isList bool,
  176. ) []apis.ProjectizedResourceInfo {
  177. ret := make([]apis.ProjectizedResourceInfo, len(objs))
  178. resIds := make([]string, len(objs))
  179. if len(fields) == 0 || fields.Contains("project_domain") || fields.Contains("tenant") {
  180. projectIds := stringutils2.SSortedStrings{}
  181. for i := range objs {
  182. var base *SProjectizedResourceBase
  183. reflectutils.FindAnonymouStructPointer(objs[i], &base)
  184. if base != nil && len(base.ProjectId) > 0 {
  185. resIds[i] = getObjectIdstr("project", base.ProjectId)
  186. projectIds = stringutils2.Append(projectIds, base.ProjectId)
  187. }
  188. }
  189. projects := DefaultProjectsFetcher(ctx, projectIds, false)
  190. if projects != nil {
  191. for i := range objs {
  192. var base *SProjectizedResourceBase
  193. reflectutils.FindAnonymouStructPointer(objs[i], &base)
  194. if base != nil && len(base.ProjectId) > 0 {
  195. if proj, ok := projects[base.ProjectId]; ok {
  196. if len(fields) == 0 || fields.Contains("project_domain") {
  197. ret[i].ProjectDomain = proj.Domain
  198. }
  199. if len(fields) == 0 || fields.Contains("tenant") {
  200. ret[i].Project = proj.Name
  201. }
  202. }
  203. }
  204. }
  205. }
  206. }
  207. if fields == nil || fields.Contains("__meta__") {
  208. q := Metadata.Query("id", "key", "value")
  209. metaKeyValues := make(map[string][]SMetadata)
  210. err := FetchQueryObjectsByIds(q, "id", resIds, &metaKeyValues)
  211. if err != nil {
  212. log.Errorf("FetchQueryObjectsByIds metadata fail %s", err)
  213. return ret
  214. }
  215. for i := range objs {
  216. if metaList, ok := metaKeyValues[resIds[i]]; ok {
  217. ret[i].ProjectMetadata = map[string]string{}
  218. for _, meta := range metaList {
  219. ret[i].ProjectMetadata[meta.Key] = meta.Value
  220. }
  221. }
  222. }
  223. }
  224. domainRows := manager.SDomainizedResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList)
  225. for i := range ret {
  226. ret[i].DomainizedResourceInfo = domainRows[i]
  227. }
  228. return ret
  229. }
  230. func fetchProjects(ctx context.Context, projectIds []string, isDomain bool) map[string]STenant {
  231. deadline := time.Now().UTC().Add(-consts.GetTenantCacheExpireSeconds())
  232. q := TenantCacheManager.Query().In("id", projectIds).GT("last_check", deadline)
  233. if isDomain {
  234. q = q.Equals("domain_id", identityapi.KeystoneDomainRoot)
  235. } else {
  236. q = q.NotEquals("domain_id", identityapi.KeystoneDomainRoot)
  237. }
  238. projects := make([]STenant, 0)
  239. err := FetchModelObjects(TenantCacheManager, q, &projects)
  240. if err != nil {
  241. return nil
  242. }
  243. ret := make(map[string]STenant)
  244. for i := range projects {
  245. ret[projects[i].Id] = projects[i]
  246. }
  247. for _, pid := range projectIds {
  248. if len(pid) == 0 {
  249. continue
  250. }
  251. if _, ok := ret[pid]; !ok {
  252. // not found
  253. var t *STenant
  254. if isDomain {
  255. t, _ = TenantCacheManager.fetchDomainFromKeystone(ctx, pid)
  256. } else {
  257. t, _ = TenantCacheManager.fetchTenantFromKeystone(ctx, pid, "")
  258. }
  259. if t != nil {
  260. ret[t.Id] = *t
  261. }
  262. }
  263. }
  264. return ret
  265. }
  266. func ValidateProjectizedResourceInput(ctx context.Context, input apis.ProjectizedResourceCreateInput) (*STenant, apis.ProjectizedResourceInput, error) {
  267. tenant, err := DefaultProjectFetcher(ctx, input.ProjectId, input.ProjectDomainId)
  268. if err != nil {
  269. if errors.Cause(err) == sql.ErrNoRows {
  270. return nil, input.ProjectizedResourceInput, httperrors.NewResourceNotFoundError2("project", input.ProjectId)
  271. } else {
  272. return nil, input.ProjectizedResourceInput, errors.Wrap(err, "TenantCacheManager.FetchTenantByIdOrName")
  273. }
  274. }
  275. input.ProjectId = tenant.GetId()
  276. return tenant, input.ProjectizedResourceInput, nil
  277. }
  278. func (manager *SProjectizedResourceBaseManager) ListItemExportKeys(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, keys stringutils2.SSortedStrings) (*sqlchemy.SQuery, error) {
  279. var err error
  280. q, err = manager.SDomainizedResourceBaseManager.ListItemExportKeys(ctx, q, userCred, keys)
  281. if err != nil {
  282. return nil, errors.Wrap(err, "SDomainizedResourceBaseManager.ListItemExportKeys")
  283. }
  284. if keys.Contains("tenant") {
  285. projectsQ := DefaultProjectQuery().SubQuery()
  286. q = q.LeftJoin(projectsQ, sqlchemy.Equals(q.Field("tenant_id"), projectsQ.Field("id")))
  287. q = q.AppendField(projectsQ.Field("name", "tenant"))
  288. }
  289. return q, nil
  290. }