organization_nodes.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521
  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 models
  15. import (
  16. "context"
  17. "crypto/sha256"
  18. "database/sql"
  19. "encoding/binary"
  20. "fmt"
  21. "strings"
  22. "yunion.io/x/jsonutils"
  23. "yunion.io/x/log"
  24. "yunion.io/x/pkg/errors"
  25. "yunion.io/x/pkg/util/rbacscope"
  26. "yunion.io/x/pkg/utils"
  27. "yunion.io/x/sqlchemy"
  28. api "yunion.io/x/onecloud/pkg/apis/identity"
  29. "yunion.io/x/onecloud/pkg/cloudcommon/consts"
  30. "yunion.io/x/onecloud/pkg/cloudcommon/db"
  31. "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman"
  32. "yunion.io/x/onecloud/pkg/httperrors"
  33. "yunion.io/x/onecloud/pkg/mcclient"
  34. "yunion.io/x/onecloud/pkg/mcclient/auth"
  35. "yunion.io/x/onecloud/pkg/mcclient/modulebase"
  36. "yunion.io/x/onecloud/pkg/util/stringutils2"
  37. "yunion.io/x/onecloud/pkg/util/tagutils"
  38. )
  39. var _ db.IModelManager = (*SOrganizationNodeManager)(nil)
  40. var _ db.IModel = (*SOrganizationNode)(nil)
  41. type SOrganizationNodeManager struct {
  42. db.SStandaloneResourceBaseManager
  43. db.SPendingDeletedBaseManager
  44. }
  45. var OrganizationNodeManager *SOrganizationNodeManager
  46. func init() {
  47. OrganizationNodeManager = &SOrganizationNodeManager{
  48. SStandaloneResourceBaseManager: db.NewStandaloneResourceBaseManager(
  49. SOrganizationNode{},
  50. "organization_nodes_tbl",
  51. "organization_node",
  52. "organization_nodes",
  53. ),
  54. }
  55. OrganizationNodeManager.SetVirtualObject(OrganizationNodeManager)
  56. OrganizationNodeManager.TableSpec().AddIndex(true, "deleted", "org_id", "full_label", "level")
  57. }
  58. type SOrganizationNode struct {
  59. db.SStandaloneResourceBase `name:""`
  60. db.SPendingDeletedBase
  61. OrgId string `width:"36" charset:"ascii" list:"user" create:"admin_required"`
  62. FullLabel string `width:"180" charset:"utf8" list:"user" create:"admin_required"`
  63. Level int `list:"user" create:"admin_required"`
  64. Weight int `list:"user" create:"admin_required" update:"admin" default:"1"`
  65. }
  66. func (orgNode *SOrganizationNode) GetOrganization() (*SOrganization, error) {
  67. return OrganizationManager.fetchOrganizationById(orgNode.OrgId)
  68. }
  69. func (orgNode *SOrganizationNode) GetChildCount() (int, error) {
  70. q := OrganizationNodeManager.Query().Startswith("full_label", orgNode.FullLabel+api.OrganizationLabelSeparator)
  71. cnt, err := q.CountWithError()
  72. if err != nil {
  73. return 0, errors.Wrap(err, "CountWithError")
  74. }
  75. return cnt, nil
  76. }
  77. func (orgNode *SOrganizationNode) GetDirectChildCount() (int, error) {
  78. q := OrganizationNodeManager.Query().Startswith("full_label", orgNode.FullLabel+api.OrganizationLabelSeparator)
  79. q = q.Equals("level", orgNode.Level+1)
  80. cnt, err := q.CountWithError()
  81. if err != nil {
  82. return 0, errors.Wrap(err, "CountWithError")
  83. }
  84. return cnt, nil
  85. }
  86. func generateId(orgId string, fullLabel string, level int) string {
  87. h := sha256.New()
  88. h.Write([]byte(orgId))
  89. h.Write([]byte(fullLabel))
  90. bs := make([]byte, 4)
  91. binary.LittleEndian.PutUint32(bs, uint32(level))
  92. h.Write(bs)
  93. return fmt.Sprintf("%x", h.Sum(nil))
  94. }
  95. func (manager *SOrganizationNodeManager) ensureNode(ctx context.Context, orgId string, fullLabel string, weight *int, desc string) (*SOrganizationNode, error) {
  96. labels := api.SplitLabel(fullLabel)
  97. level := len(labels)
  98. label := labels[level-1]
  99. id := generateId(orgId, fullLabel, level)
  100. obj, err := manager.FetchById(id)
  101. if err != nil {
  102. if errors.Cause(err) != sql.ErrNoRows {
  103. return nil, errors.Wrap(err, "FetchById")
  104. }
  105. // not exist
  106. if weight == nil {
  107. one := 1
  108. weight = &one
  109. }
  110. } else {
  111. // exist
  112. if weight == nil {
  113. weight = &obj.(*SOrganizationNode).Weight
  114. }
  115. if len(desc) == 0 {
  116. desc = obj.(*SOrganizationNode).Description
  117. }
  118. }
  119. node := &SOrganizationNode{
  120. OrgId: orgId,
  121. FullLabel: fullLabel,
  122. Level: level,
  123. Weight: *weight,
  124. }
  125. node.Description = desc
  126. node.Name = label
  127. node.Id = id
  128. err = manager.TableSpec().InsertOrUpdate(ctx, node)
  129. if err != nil {
  130. return nil, errors.Wrap(err, "InsertOrUpdate")
  131. }
  132. return node, nil
  133. }
  134. func (orgNode *SOrganizationNode) PerformBind(
  135. ctx context.Context,
  136. userCred mcclient.TokenCredential,
  137. query jsonutils.JSONObject,
  138. input api.OrganizationNodePerformBindInput,
  139. ) (jsonutils.JSONObject, error) {
  140. orgNode.startOrganizationBindTask(ctx, userCred, input, true)
  141. return nil, nil
  142. }
  143. func (orgNode *SOrganizationNode) PerformUnbind(
  144. ctx context.Context,
  145. userCred mcclient.TokenCredential,
  146. query jsonutils.JSONObject,
  147. input api.OrganizationNodePerformBindInput,
  148. ) (jsonutils.JSONObject, error) {
  149. orgNode.startOrganizationBindTask(ctx, userCred, input, false)
  150. return nil, nil
  151. }
  152. func (orgNode *SOrganizationNode) startOrganizationBindTask(
  153. ctx context.Context,
  154. userCred mcclient.TokenCredential,
  155. input api.OrganizationNodePerformBindInput,
  156. bind bool,
  157. ) error {
  158. params := jsonutils.NewDict()
  159. params.Set("input", jsonutils.Marshal(input))
  160. params.Set("bind", jsonutils.NewBool(bind))
  161. task, err := taskman.TaskManager.NewTask(ctx, "OrganizationBindTask", orgNode, userCred, params, "", "", nil)
  162. if err != nil {
  163. return err
  164. }
  165. task.ScheduleRun(nil)
  166. return nil
  167. }
  168. func (orgNode *SOrganizationNode) BindTargetIds(ctx context.Context, userCred mcclient.TokenCredential, input api.OrganizationNodePerformBindInput, bind bool) error {
  169. org, err := orgNode.GetOrganization()
  170. if err != nil {
  171. return errors.Wrap(err, "GetOrganization")
  172. }
  173. tags := orgNode.GetTags(org)
  174. if !bind {
  175. for k := range tags {
  176. tags[k] = "None"
  177. }
  178. }
  179. var errs []error
  180. for _, id := range input.TargetId {
  181. err := orgNode.bindTargetId(ctx, userCred, input.ResourceType, id, tags)
  182. if err != nil {
  183. errs = append(errs, err)
  184. }
  185. }
  186. return errors.NewAggregate(errs)
  187. }
  188. func (orgNode *SOrganizationNode) bindTargetId(ctx context.Context, userCred mcclient.TokenCredential, resType string, idstr string, tags map[string]string) error {
  189. org, err := OrganizationManager.fetchOrganizationById(orgNode.OrgId)
  190. if err != nil {
  191. return errors.Wrap(err, "fetchOrganizationById")
  192. }
  193. switch org.Type {
  194. case api.OrgTypeProject:
  195. return orgNode.bindProject(ctx, userCred, idstr, tags)
  196. case api.OrgTypeDomain:
  197. return orgNode.bindDomain(ctx, userCred, idstr, tags)
  198. case api.OrgTypeObject:
  199. return orgNode.bindObject(ctx, userCred, resType, idstr, tags)
  200. }
  201. return errors.Wrapf(errors.ErrNotSupported, "cannot bind to %s %s", resType, idstr)
  202. }
  203. func (orgNode *SOrganizationNode) bindProject(ctx context.Context, userCred mcclient.TokenCredential, idstr string, tags map[string]string) error {
  204. projObj, err := ProjectManager.FetchById(idstr)
  205. if err != nil {
  206. return errors.Wrapf(err, "ProjectManager.FetchById %s", idstr)
  207. }
  208. return projObj.(*SProject).SetOrganizationMetadataAll(ctx, tags, userCred)
  209. }
  210. func (orgNode *SOrganizationNode) bindDomain(ctx context.Context, userCred mcclient.TokenCredential, idstr string, tags map[string]string) error {
  211. domainObj, err := DomainManager.FetchById(idstr)
  212. if err != nil {
  213. return errors.Wrapf(err, "DomainManager.FetchById %s", idstr)
  214. }
  215. return domainObj.(*SDomain).SetOrganizationMetadataAll(ctx, tags, userCred)
  216. }
  217. func (orgNode *SOrganizationNode) bindObject(ctx context.Context, userCred mcclient.TokenCredential, resType, idstr string, tags map[string]string) error {
  218. s := auth.GetSession(ctx, userCred, consts.GetRegion())
  219. module, err := modulebase.GetModule(s, resType)
  220. if err != nil {
  221. return errors.Wrap(err, "GetModule")
  222. }
  223. input := jsonutils.Marshal(tags)
  224. _, err = module.PerformAction(s, idstr, "set-org-metadata", input)
  225. if err != nil {
  226. return errors.Wrapf(err, "PerformAction set-org-metadata %s", idstr)
  227. }
  228. return nil
  229. }
  230. func (orgNode *SOrganizationNode) GetShortDesc(ctx context.Context) *jsonutils.JSONDict {
  231. desc := orgNode.SStandaloneResourceBase.GetShortDesc(ctx)
  232. desc.Set("level", jsonutils.NewInt(int64(orgNode.Level)))
  233. desc.Set("full_label", jsonutils.NewString(orgNode.FullLabel))
  234. return desc
  235. }
  236. func (orgNode *SOrganizationNode) ValidateUpdateData(
  237. ctx context.Context,
  238. userCred mcclient.TokenCredential,
  239. query jsonutils.JSONObject,
  240. input api.OrganizationNodeUpdateInput,
  241. ) (api.OrganizationNodeUpdateInput, error) {
  242. var err error
  243. input.StandaloneResourceBaseUpdateInput, err = orgNode.SStandaloneResourceBase.ValidateUpdateData(ctx, userCred, query, input.StandaloneResourceBaseUpdateInput)
  244. if err != nil {
  245. return input, errors.Wrap(err, "SStandaloneResourceBase.ValidateUpdateData")
  246. }
  247. // not allow to update name
  248. if len(input.Name) > 0 && input.Name != orgNode.Name {
  249. return input, errors.Wrap(httperrors.ErrForbidden, "not allow to update name")
  250. }
  251. return input, nil
  252. }
  253. func tagSetList2Conditions(tagsetList tagutils.TTagSetList, keys []string, q *sqlchemy.SQuery) sqlchemy.ICondition {
  254. for i := range keys {
  255. if !strings.HasPrefix(keys[i], "org:") {
  256. keys[i] = "org:" + keys[i]
  257. }
  258. }
  259. conds := make([]sqlchemy.ICondition, 0)
  260. paths := tagutils.TagSetList2Paths(tagsetList, keys)
  261. for i := range paths {
  262. for j := range paths[i] {
  263. label := api.JoinLabels(paths[i][:j+1]...)
  264. conds = append(conds, sqlchemy.Equals(q.Field("full_label"), label))
  265. if j == len(paths[i])-1 {
  266. labelSlash := label + api.OrganizationLabelSeparator
  267. conds = append(conds, sqlchemy.Startswith(q.Field("full_label"), labelSlash))
  268. }
  269. }
  270. }
  271. if len(conds) > 0 {
  272. return sqlchemy.OR(conds...)
  273. }
  274. return nil
  275. }
  276. // 项目列表
  277. func (manager *SOrganizationNodeManager) ListItemFilter(
  278. ctx context.Context,
  279. q *sqlchemy.SQuery,
  280. userCred mcclient.TokenCredential,
  281. query api.OrganizationNodeListInput,
  282. ) (*sqlchemy.SQuery, error) {
  283. var err error
  284. q, err = manager.SStandaloneResourceBaseManager.ListItemFilter(ctx, q, userCred, query.StandaloneResourceListInput)
  285. if err != nil {
  286. return nil, errors.Wrap(err, "SStandaloneResourceBaseManager.ListItemFilter")
  287. }
  288. if len(query.OrgId) > 0 {
  289. orgObj, err := OrganizationManager.FetchByIdOrName(ctx, userCred, query.OrgId)
  290. if err != nil {
  291. if errors.Cause(err) == sql.ErrNoRows {
  292. return nil, httperrors.NewResourceNotFoundError2(OrganizationManager.Keyword(), query.OrgId)
  293. } else {
  294. return nil, errors.Wrapf(err, "FetchByIdOrName %s", query.OrgId)
  295. }
  296. }
  297. org := orgObj.(*SOrganization)
  298. q = q.Equals("org_id", org.GetId())
  299. var cond sqlchemy.ICondition
  300. switch org.Type {
  301. case api.OrgTypeDomain:
  302. if !query.PolicyDomainTags.IsEmpty() {
  303. cond = tagSetList2Conditions(query.PolicyDomainTags, org.GetKeys(), q)
  304. }
  305. case api.OrgTypeProject:
  306. if !query.PolicyProjectTags.IsEmpty() {
  307. cond = tagSetList2Conditions(query.PolicyProjectTags, org.GetKeys(), q)
  308. }
  309. case api.OrgTypeObject:
  310. if !query.PolicyObjectTags.IsEmpty() {
  311. cond = tagSetList2Conditions(query.PolicyObjectTags, org.GetKeys(), q)
  312. }
  313. }
  314. if cond != nil {
  315. q = q.Filter(cond)
  316. }
  317. }
  318. if len(query.OrgType) > 0 {
  319. orgSubQ := OrganizationManager.Query().SubQuery()
  320. q = q.Join(orgSubQ, sqlchemy.Equals(q.Field("org_id"), orgSubQ.Field("id")))
  321. q = q.Filter(sqlchemy.Equals(orgSubQ.Field("type"), query.OrgType))
  322. }
  323. if query.Level != 0 {
  324. q = q.Equals("level", query.Level)
  325. }
  326. return q, nil
  327. }
  328. func (manager *SOrganizationNodeManager) OrderByExtraFields(
  329. ctx context.Context,
  330. q *sqlchemy.SQuery,
  331. userCred mcclient.TokenCredential,
  332. query api.OrganizationNodeListInput,
  333. ) (*sqlchemy.SQuery, error) {
  334. var err error
  335. q, err = manager.SStandaloneResourceBaseManager.OrderByExtraFields(ctx, q, userCred, query.StandaloneResourceListInput)
  336. if err != nil {
  337. return nil, errors.Wrap(err, "SStandaloneResourceBaseManager.OrderByExtraFields")
  338. }
  339. return q, nil
  340. }
  341. func (manager *SOrganizationNodeManager) QueryDistinctExtraField(q *sqlchemy.SQuery, field string) (*sqlchemy.SQuery, error) {
  342. var err error
  343. q, err = manager.SStandaloneResourceBaseManager.QueryDistinctExtraField(q, field)
  344. if err == nil {
  345. return q, nil
  346. }
  347. return q, httperrors.ErrNotFound
  348. }
  349. func (orgNode *SOrganizationNode) GetTags(org *SOrganization) map[string]string {
  350. tags := make(map[string]string)
  351. keys := api.SplitLabel(org.Keys)
  352. values := api.SplitLabel(orgNode.FullLabel)
  353. for i := 0; i < orgNode.Level; i++ {
  354. k := db.ORGANIZATION_TAG_PREFIX + keys[i]
  355. v := values[i]
  356. tags[k] = v
  357. }
  358. return tags
  359. }
  360. func (orgNode *SOrganizationNode) GetTagSet(org *SOrganization) tagutils.TTagSet {
  361. tags := make(tagutils.TTagSet, 0)
  362. keys := api.SplitLabel(org.Keys)
  363. values := api.SplitLabel(orgNode.FullLabel)
  364. for i := 0; i < orgNode.Level; i++ {
  365. tag := tagutils.STag{
  366. Key: db.ORGANIZATION_TAG_PREFIX + keys[i],
  367. Value: values[i],
  368. }
  369. tags = append(tags, tag)
  370. }
  371. return tags
  372. }
  373. func (manager *SOrganizationNodeManager) FetchCustomizeColumns(
  374. ctx context.Context,
  375. userCred mcclient.TokenCredential,
  376. query jsonutils.JSONObject,
  377. objs []interface{},
  378. fields stringutils2.SSortedStrings,
  379. isList bool,
  380. ) []api.SOrganizationNodeDetails {
  381. rows := make([]api.SOrganizationNodeDetails, len(objs))
  382. orgIds := make([]string, 0)
  383. for i := range rows {
  384. orgNode := objs[i].(*SOrganizationNode)
  385. if !utils.IsInArray(orgNode.OrgId, orgIds) {
  386. orgIds = append(orgIds, orgNode.OrgId)
  387. }
  388. }
  389. orgMaps := make(map[string]SOrganization)
  390. err := db.FetchModelObjectsByIds(OrganizationManager, "id", orgIds, &orgMaps)
  391. if err != nil {
  392. log.Errorf("FetchModelObjectsByIds fail %s", err)
  393. return rows
  394. }
  395. stdRows := manager.SStandaloneResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList)
  396. for i := range rows {
  397. orgNode := objs[i].(*SOrganizationNode)
  398. if org, ok := orgMaps[orgNode.OrgId]; ok {
  399. rows[i] = api.SOrganizationNodeDetails{
  400. SOrganizationNode: api.SOrganizationNode{
  401. OrgId: orgNode.OrgId,
  402. FullLabel: orgNode.FullLabel,
  403. Level: orgNode.Level,
  404. Weight: orgNode.Weight,
  405. },
  406. StandaloneResourceDetails: stdRows[i],
  407. Organization: org.Name,
  408. Tags: orgNode.GetTagSet(&org),
  409. Type: org.Type,
  410. }
  411. rows[i].Id = orgNode.Id
  412. }
  413. }
  414. return rows
  415. }
  416. func (manager *SOrganizationNodeManager) FilterBySystemAttributes(q *sqlchemy.SQuery, userCred mcclient.TokenCredential, query jsonutils.JSONObject, scope rbacscope.TRbacScope) *sqlchemy.SQuery {
  417. q = manager.SStandaloneResourceBaseManager.FilterBySystemAttributes(q, userCred, query, scope)
  418. q = manager.SPendingDeletedBaseManager.FilterBySystemAttributes(manager.GetIStandaloneModelManager(), q, userCred, query, scope)
  419. return q
  420. }
  421. func (orgNode *SOrganizationNode) ValidateDeleteCondition(ctx context.Context, info *api.ProjectDetails) error {
  422. childCnt, err := orgNode.GetDirectChildCount()
  423. if err != nil {
  424. return errors.Wrap(err, "GetDirectChildCount")
  425. }
  426. if childCnt > 0 {
  427. return errors.Wrapf(httperrors.ErrNotEmpty, "childnodes %d", childCnt)
  428. }
  429. return orgNode.SStandaloneResourceBase.ValidateDeleteCondition(ctx, nil)
  430. }
  431. // fake delete
  432. func (orgNode *SOrganizationNode) Delete(ctx context.Context, userCred mcclient.TokenCredential) error {
  433. if !orgNode.PendingDeleted {
  434. err := orgNode.SPendingDeletedBase.MarkPendingDelete(orgNode.GetIStandaloneModel(), ctx, userCred, "")
  435. if err != nil {
  436. return errors.Wrap(err, "MarkPendingDelete")
  437. }
  438. }
  439. return nil
  440. }
  441. func (manager *SOrganizationNodeManager) fetchOrgNodesInfo(ctx context.Context, userCred mcclient.TokenCredential, nodeIds []string, isList bool) ([]api.SOrganizationNodeInfo, error) {
  442. q := manager.Query().In("id", nodeIds)
  443. nodes := make([]SOrganizationNode, 0)
  444. err := db.FetchModelObjects(manager, q, &nodes)
  445. if err != nil {
  446. return nil, errors.Wrap(err, "FetchModelObjects")
  447. }
  448. infs := make([]interface{}, 0)
  449. for i := range nodes {
  450. infs = append(infs, &nodes[i])
  451. }
  452. nodeDetails := manager.FetchCustomizeColumns(ctx, userCred, nil, infs, nil, isList)
  453. ret := make([]api.SOrganizationNodeInfo, 0)
  454. for i := range nodeDetails {
  455. node := nodeDetails[i]
  456. ret = append(ret, api.SOrganizationNodeInfo{
  457. Id: node.Id,
  458. FullLabel: node.FullLabel,
  459. OrgId: node.OrgId,
  460. Organization: node.Organization,
  461. Tags: node.Tags,
  462. Type: node.Type,
  463. })
  464. }
  465. return ret, nil
  466. }