| OLD | NEW |
| 1 # Copyright 2015 The LUCI Authors. All rights reserved. | 1 # Copyright 2015 The LUCI Authors. All rights reserved. |
| 2 # Use of this source code is governed under the Apache License, Version 2.0 | 2 # Use of this source code is governed under the Apache License, Version 2.0 |
| 3 # that can be found in the LICENSE file. | 3 # that can be found in the LICENSE file. |
| 4 | 4 |
| 5 """Provides info about projects (service tenants).""" | 5 """Provides info about projects (service tenants).""" |
| 6 | 6 |
| 7 import logging | 7 import logging |
| 8 | 8 |
| 9 from google.appengine.api import memcache | 9 from google.appengine.api import memcache |
| 10 from google.appengine.ext import ndb | 10 from google.appengine.ext import ndb |
| (...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 70 | 70 |
| 71 | 71 |
| 72 def get_project(id): | 72 def get_project(id): |
| 73 """Returns a project by id.""" | 73 """Returns a project by id.""" |
| 74 for p in get_projects(): | 74 for p in get_projects(): |
| 75 if p.id == id: | 75 if p.id == id: |
| 76 return p | 76 return p |
| 77 return None | 77 return None |
| 78 | 78 |
| 79 | 79 |
| 80 def get_repos(project_ids): | 80 @ndb.tasklet |
| 81 def get_repos_async(project_ids): |
| 81 """Returns a mapping {project_id: (repo_type, repo_url)}. | 82 """Returns a mapping {project_id: (repo_type, repo_url)}. |
| 82 | 83 |
| 83 All projects must exist. | 84 All projects must exist. |
| 84 """ | 85 """ |
| 85 assert isinstance(project_ids, list) | 86 assert isinstance(project_ids, list) |
| 86 keys = [ndb.Key(ProjectImportInfo, pid) for pid in project_ids] | 87 infos = yield ndb.get_multi_async( |
| 87 return { | 88 ndb.Key(ProjectImportInfo, pid) for pid in project_ids) |
| 89 raise ndb.Return({ |
| 88 pid: (info.repo_type, info.repo_url) if info else (None, None) | 90 pid: (info.repo_type, info.repo_url) if info else (None, None) |
| 89 for pid, info in zip(project_ids, ndb.get_multi(keys)) | 91 for pid, info in zip(project_ids, infos) |
| 90 } | 92 }) |
| 91 | 93 |
| 92 | 94 |
| 93 def get_metadata(project_ids): | 95 @ndb.tasklet |
| 96 def get_metadata_async(project_ids): |
| 94 """Returns a mapping {project_id: metadata}. | 97 """Returns a mapping {project_id: metadata}. |
| 95 | 98 |
| 96 If a project does not exist, the metadata is None. | 99 If a project does not exist, the metadata is None. |
| 97 | 100 |
| 98 The project metadata stored in project.cfg files in each project. | 101 The project metadata stored in project.cfg files in each project. |
| 99 """ | 102 """ |
| 103 PROJECT_DOES_NOT_EXIST_SENTINEL = (0,) |
| 100 cache_ns = 'projects.get_metadata' | 104 cache_ns = 'projects.get_metadata' |
| 101 cache_map = memcache.get_multi(project_ids, namespace=cache_ns) | 105 ctx = ndb.get_context() |
| 106 # ctx.memcache_get is auto-batching. Internally it makes get_multi RPC. |
| 107 cache_futs = { |
| 108 pid: ctx.memcache_get(pid, namespace=cache_ns) |
| 109 for pid in project_ids |
| 110 } |
| 111 yield cache_futs.values() |
| 102 result = {} | 112 result = {} |
| 103 missing = [] | 113 missing = [] |
| 104 for pid in project_ids: | 114 for pid in project_ids: |
| 105 if pid in cache_map: | 115 binary = cache_futs[pid].get_result() |
| 116 if binary is not None: |
| 106 # cache hit | 117 # cache hit |
| 107 binary = cache_map[pid] | 118 if binary is PROJECT_DOES_NOT_EXIST_SENTINEL: |
| 108 if binary is None: | |
| 109 # project does not exist | |
| 110 result[pid] = None | 119 result[pid] = None |
| 111 else: | 120 else: |
| 112 cfg = project_config_pb2.ProjectCfg() | 121 cfg = project_config_pb2.ProjectCfg() |
| 113 cfg.ParseFromString(binary) | 122 cfg.ParseFromString(binary) |
| 114 result[pid] = cfg | 123 result[pid] = cfg |
| 115 else: | 124 else: |
| 116 # cache miss | 125 # cache miss |
| 117 missing.append(pid) | 126 missing.append(pid) |
| 118 | 127 |
| 119 if missing: | 128 if missing: |
| 120 fetched = _get_project_configs( | 129 fetched = yield _get_project_configs_async( |
| 121 missing, common.PROJECT_METADATA_FILENAME, | 130 missing, common.PROJECT_METADATA_FILENAME, |
| 122 project_config_pb2.ProjectCfg) | 131 project_config_pb2.ProjectCfg) |
| 123 result.update(fetched) # at this point result must have all project ids | 132 result.update(fetched) # at this point result must have all project ids |
| 124 # Cache metadata for 10 min. In practice, it never changes. | 133 # Cache metadata for 10 min. In practice, it never changes. |
| 125 cache_map = { | 134 # ctx.memcache_set is auto-batching. Internally it makes set_multi RPC. |
| 126 pid: cfg.SerializeToString() if cfg else None | 135 yield [ |
| 136 ctx.memcache_set( |
| 137 pid, |
| 138 cfg.SerializeToString() if cfg else PROJECT_DOES_NOT_EXIST_SENTINEL, |
| 139 namespace=cache_ns, |
| 140 time=60 * 10) |
| 127 for pid, cfg in fetched.iteritems() | 141 for pid, cfg in fetched.iteritems() |
| 128 } | 142 ] |
| 129 memcache.set_multi(cache_map, namespace=cache_ns, time=60 * 10) | |
| 130 | 143 |
| 131 return result | 144 raise ndb.Return(result) |
| 132 | 145 |
| 133 | 146 |
| 134 def get_refs(project_ids): | 147 def get_refs(project_ids): |
| 135 """Returns a mapping {project_id: list of refs} | 148 """Returns a mapping {project_id: list of refs} |
| 136 | 149 |
| 137 The ref list is None if a project does not exist. | 150 The ref list is None if a project does not exist. |
| 138 | 151 |
| 139 The list of refs stored in refs.cfg of a project. | 152 The list of refs stored in refs.cfg of a project. |
| 140 """ | 153 """ |
| 141 cfgs = _get_project_configs( | 154 cfgs = _get_project_configs_async( |
| 142 project_ids, common.REFS_FILENAME, project_config_pb2.RefsCfg) | 155 project_ids, common.REFS_FILENAME, project_config_pb2.RefsCfg |
| 156 ).get_result() |
| 143 return { | 157 return { |
| 144 pid: None if cfg is None else cfg.refs or DEFAULT_REF_CFG.refs | 158 pid: None if cfg is None else cfg.refs or DEFAULT_REF_CFG.refs |
| 145 for pid, cfg in cfgs.iteritems() | 159 for pid, cfg in cfgs.iteritems() |
| 146 } | 160 } |
| 147 | 161 |
| 148 | 162 |
| 149 def _get_project_configs(project_ids, path, message_factory): | 163 def _get_project_configs_async(project_ids, path, message_factory): |
| 150 """Returns a mapping {project_id: message}. | 164 """Returns a mapping {project_id: message}. |
| 151 | 165 |
| 152 If a project does not exist, the message is None. | 166 If a project does not exist, the message is None. |
| 153 """ | 167 """ |
| 154 assert isinstance(project_ids, list) | 168 assert isinstance(project_ids, list) |
| 155 if not project_ids: | 169 if not project_ids: |
| 156 return {} | 170 return {} |
| 157 prefix = 'projects/' | 171 |
| 158 messages = storage.get_latest_messages_async( | 172 @ndb.tasklet |
| 159 [prefix + pid for pid in _filter_existing(project_ids)], | 173 def get_async(): |
| 160 path, message_factory).get_result() | 174 prefix = 'projects/' |
| 161 return { | 175 messages = yield storage.get_latest_messages_async( |
| 162 # messages may not have a key because we filter project ids by existence | 176 [prefix + pid for pid in _filter_existing(project_ids)], |
| 163 pid: messages.get(prefix + pid) | 177 path, message_factory) |
| 164 for pid in project_ids | 178 raise ndb.Return({ |
| 165 } | 179 # messages may not have a key because we filter project ids by existence |
| 180 pid: messages.get(prefix + pid) |
| 181 for pid in project_ids |
| 182 }) |
| 183 |
| 184 return get_async() |
| 166 | 185 |
| 167 | 186 |
| 168 def _filter_existing(project_ids): | 187 def _filter_existing(project_ids): |
| 169 # TODO(nodir): optimize | 188 # TODO(nodir): optimize |
| 170 assert isinstance(project_ids, list) | 189 assert isinstance(project_ids, list) |
| 171 if not project_ids: | 190 if not project_ids: |
| 172 return project_ids | 191 return project_ids |
| 173 assert all(pid for pid in project_ids) | 192 assert all(pid for pid in project_ids) |
| 174 all_project_ids = set(p.id for p in get_projects()) | 193 all_project_ids = set(p.id for p in get_projects()) |
| 175 return [ | 194 return [ |
| 176 pid for pid in project_ids | 195 pid for pid in project_ids |
| 177 if pid in all_project_ids | 196 if pid in all_project_ids |
| 178 ] | 197 ] |
| OLD | NEW |