rolecache.go 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286
  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. "fmt"
  19. "runtime/debug"
  20. "time"
  21. "yunion.io/x/jsonutils"
  22. "yunion.io/x/log"
  23. "yunion.io/x/pkg/errors"
  24. "yunion.io/x/pkg/util/httputils"
  25. "yunion.io/x/sqlchemy"
  26. "yunion.io/x/onecloud/pkg/cloudcommon/consts"
  27. "yunion.io/x/onecloud/pkg/cloudcommon/db/lockman"
  28. "yunion.io/x/onecloud/pkg/mcclient/auth"
  29. "yunion.io/x/onecloud/pkg/mcclient/informer"
  30. modules "yunion.io/x/onecloud/pkg/mcclient/modules/identity"
  31. "yunion.io/x/onecloud/pkg/util/ctx"
  32. "yunion.io/x/onecloud/pkg/util/stringutils2"
  33. )
  34. // TODO: 1. Access to a non-existent role will still trigger synchronization.
  35. // 2. The failure of the watch mechanism makes the cache miss an event,
  36. // then there is outdated data in the cache before the expiration
  37. // time comes or the event about this data occurs again.
  38. var (
  39. DefaultRoleFetcher func(ctx context.Context, id string) (*SRole, error)
  40. )
  41. type SRoleCacheManager struct {
  42. SKeystoneCacheObjectManager
  43. watching bool
  44. }
  45. type SRole struct {
  46. SKeystoneCacheObject
  47. }
  48. func (role *SRole) GetModelManager() IModelManager {
  49. return RoleCacheManager
  50. }
  51. var RoleCacheManager *SRoleCacheManager
  52. func init() {
  53. RoleCacheManager = &SRoleCacheManager{
  54. NewKeystoneCacheObjectManager(SRole{}, "roles_cache_tbl", "role_cache", "role_caches"), false}
  55. // log.Debugf("initialize role cache manager %s", RoleCacheManager.KeywordPlural())
  56. RoleCacheManager.SetVirtualObject(RoleCacheManager)
  57. DefaultRoleFetcher = RoleCacheManager.FetchRoleByIdOrName
  58. }
  59. func (manager *SRoleCacheManager) FetchRoleByIdOrName(ctx context.Context, idStr string) (*SRole, error) {
  60. return manager.fetchRole(ctx, idStr, false, func(q *sqlchemy.SQuery) *sqlchemy.SQuery {
  61. if stringutils2.IsUtf8(idStr) {
  62. return q.Equals("name", idStr)
  63. } else {
  64. return q.Filter(sqlchemy.OR(
  65. sqlchemy.Equals(q.Field("id"), idStr),
  66. sqlchemy.Equals(q.Field("name"), idStr),
  67. ))
  68. }
  69. })
  70. }
  71. func (manager *SRoleCacheManager) FetchRoleById(ctx context.Context, idStr string) (*SRole, error) {
  72. return manager.fetchRole(ctx, idStr, false, func(q *sqlchemy.SQuery) *sqlchemy.SQuery {
  73. return q.Filter(sqlchemy.Equals(q.Field("id"), idStr))
  74. })
  75. }
  76. func (manager *SRoleCacheManager) FetchRoleByName(ctx context.Context, idStr string) (*SRole, error) {
  77. return manager.fetchRole(ctx, idStr, false, func(q *sqlchemy.SQuery) *sqlchemy.SQuery {
  78. return q.Filter(sqlchemy.Equals(q.Field("name"), idStr))
  79. })
  80. }
  81. func (manager *SRoleCacheManager) fetchRole(ctx context.Context, idStr string, noExpireCheck bool, filter func(*sqlchemy.SQuery) *sqlchemy.SQuery) (*SRole, error) {
  82. q := manager.Query()
  83. q = filter(q)
  84. tobj, err := NewModelObject(manager)
  85. if err != nil {
  86. return nil, errors.Wrap(err, "NewModelObject")
  87. }
  88. err = q.First(tobj)
  89. if err != nil && err != sql.ErrNoRows {
  90. return nil, errors.Wrap(err, "query")
  91. } else if tobj != nil {
  92. role := tobj.(*SRole)
  93. if noExpireCheck || !role.IsExpired() {
  94. return role, nil
  95. }
  96. }
  97. return manager.FetchRoleFromKeystone(ctx, idStr)
  98. }
  99. func (manager *SRoleCacheManager) FetchRoleFromKeystone(ctx context.Context, idStr string) (*SRole, error) {
  100. if len(idStr) == 0 {
  101. log.Debugf("fetch empty role!!!!\n%s", debug.Stack())
  102. return nil, fmt.Errorf("Empty idStr")
  103. }
  104. // It's to query the full list of roles(contains other domain's ones and system ones)
  105. query := jsonutils.NewDict()
  106. query.Set("scope", jsonutils.NewString("system"))
  107. query.Set("system", jsonutils.JSONTrue)
  108. query.Set("pending_delete", jsonutils.NewString("all"))
  109. s := auth.GetAdminSession(ctx, consts.GetRegion())
  110. role, err := modules.RolesV3.GetById(s, idStr, query)
  111. if err != nil {
  112. if je, ok := err.(*httputils.JSONClientError); ok && je.Code == 404 {
  113. role, err = modules.RolesV3.GetByName(s, idStr, query)
  114. if je, ok := err.(*httputils.JSONClientError); ok && je.Code == 404 {
  115. return nil, sql.ErrNoRows
  116. }
  117. }
  118. if err != nil {
  119. log.Errorf("fetch role %s fail %s", idStr, err)
  120. return nil, errors.Wrap(err, "modules.RolesV3.Get")
  121. }
  122. }
  123. id, _ := role.GetString("id")
  124. name, _ := role.GetString("name")
  125. domainId, _ := role.GetString("domain_id")
  126. domainName, _ := role.GetString("project_domain")
  127. return manager.Save(ctx, id, name, domainId, domainName)
  128. }
  129. func (manager *SRoleCacheManager) Save(ctx context.Context, idStr string, name string, domainId string, domain string) (*SRole, error) {
  130. lockman.LockRawObject(ctx, manager.KeywordPlural(), idStr)
  131. defer lockman.ReleaseRawObject(ctx, manager.KeywordPlural(), idStr)
  132. objo, err := manager.FetchById(idStr)
  133. if err != nil && err != sql.ErrNoRows {
  134. log.Errorf("FetchRolebyId fail %s", err)
  135. return nil, err
  136. }
  137. if err == nil {
  138. obj := objo.(*SRole)
  139. _, err = Update(obj, func() error {
  140. obj.Id = idStr
  141. obj.Name = name
  142. obj.Domain = domain
  143. obj.DomainId = domainId
  144. obj.LastCheck = time.Now().UTC()
  145. return nil
  146. })
  147. if err != nil {
  148. return nil, err
  149. } else {
  150. return obj, nil
  151. }
  152. } else {
  153. objm, err := NewModelObject(manager)
  154. obj := objm.(*SRole)
  155. obj.Id = idStr
  156. obj.Name = name
  157. obj.Domain = domain
  158. obj.DomainId = domainId
  159. obj.LastCheck = time.Now().UTC()
  160. err = manager.TableSpec().InsertOrUpdate(ctx, obj)
  161. if err != nil {
  162. return nil, err
  163. } else {
  164. return obj, nil
  165. }
  166. }
  167. }
  168. func (manager *SRoleCacheManager) OnAdd(obj *jsonutils.JSONDict) {
  169. id, err := obj.GetString("id")
  170. if err != nil {
  171. log.Errorf("unable to get Id: %v", err)
  172. return
  173. }
  174. ctx := ctx.CtxWithTime()
  175. lockman.LockRawObject(ctx, manager.KeywordPlural(), id)
  176. defer lockman.ReleaseRawObject(ctx, manager.KeywordPlural(), id)
  177. role := new(SRole)
  178. role.Id = id
  179. role.Name, _ = obj.GetString("name")
  180. role.DomainId, _ = obj.GetString("domain_id")
  181. role.LastCheck = time.Now()
  182. err = manager.TableSpec().Insert(ctx, role)
  183. if err != nil {
  184. log.Errorf("unable to insert: %v", err)
  185. }
  186. }
  187. func (manager *SRoleCacheManager) OnUpdate(oldObj, newObj *jsonutils.JSONDict) {
  188. id, err := oldObj.GetString("id")
  189. if err != nil {
  190. log.Errorf("unable to get Id: %v", err)
  191. return
  192. }
  193. ctx := ctx.CtxWithTime()
  194. role, err := manager.fetchRole(ctx, id, true, nil)
  195. if err != nil {
  196. log.Errorf("unable to fetch Role from db: %v", err)
  197. return
  198. }
  199. name, _ := newObj.GetString("name")
  200. domainId, _ := newObj.GetString("domain_id")
  201. if role.Name == name && role.DomainId == domainId {
  202. return
  203. }
  204. lockman.LockRawObject(ctx, manager.KeywordPlural(), id)
  205. defer lockman.ReleaseRawObject(ctx, manager.KeywordPlural(), id)
  206. _, err = Update(role, func() error {
  207. role.Name = name
  208. role.DomainId = domainId
  209. role.LastCheck = time.Now()
  210. return nil
  211. })
  212. if err != nil {
  213. log.Errorf("unable to update: %v", err)
  214. }
  215. }
  216. func (manager *SRoleCacheManager) OnDelete(obj *jsonutils.JSONDict) {
  217. id, err := obj.GetString("id")
  218. if err != nil {
  219. log.Errorf("unable to get Id: %v", err)
  220. return
  221. }
  222. ctx := ctx.CtxWithTime()
  223. role, err := manager.fetchRole(ctx, id, true, nil)
  224. if err != nil {
  225. log.Errorf("unable to fetch Role from db: %v", err)
  226. return
  227. }
  228. lockman.LockRawObject(ctx, manager.KeywordPlural(), id)
  229. defer lockman.ReleaseRawObject(ctx, manager.KeywordPlural(), id)
  230. _, err = Update(role, func() error {
  231. role.Deleted = false
  232. return nil
  233. })
  234. if err != nil {
  235. log.Errorf("unable to delete role: %v", err)
  236. }
  237. }
  238. func (manager *SRoleCacheManager) StartWatchRoleInKeystone() error {
  239. if manager.watching {
  240. return nil
  241. }
  242. ctx := ctx.CtxWithTime()
  243. s := auth.GetAdminSession(ctx, "")
  244. watchMan, err := informer.NewWatchManagerBySession(s)
  245. if err != nil {
  246. return err
  247. }
  248. resMan := &modules.RolesV3
  249. return watchMan.For(resMan).AddEventHandler(ctx, manager)
  250. }
  251. func (r *SRole) IsExpired() bool {
  252. if r.LastCheck.IsZero() {
  253. return true
  254. }
  255. now := time.Now().UTC()
  256. if r.LastCheck.Add(consts.GetRoleCacheExpireHours()).Before(now) {
  257. return true
  258. }
  259. return false
  260. }