Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(104)

Side by Side Diff: appengine/swarming/server/bot_code.py

Issue 2953253003: Replace custom blob gRPC API with ByteStream (Closed)
Patch Set: Rebase to latest Created 3 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 # Copyright 2014 The LUCI Authors. All rights reserved. 1 # Copyright 2014 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 """Swarming bot code. Includes bootstrap and swarming_bot.zip. 5 """Swarming bot code. Includes bootstrap and swarming_bot.zip.
6 6
7 It includes everything that is AppEngine specific. The non-GAE code is in 7 It includes everything that is AppEngine specific. The non-GAE code is in
8 bot_archive.py. 8 bot_archive.py.
9 """ 9 """
10 10
(...skipping 10 matching lines...) Expand all
21 from components import auth 21 from components import auth
22 from components import config 22 from components import config
23 from components import datastore_utils 23 from components import datastore_utils
24 from components import utils 24 from components import utils
25 from server import bot_archive 25 from server import bot_archive
26 from server import config as local_config 26 from server import config as local_config
27 27
28 28
29 ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 29 ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
30 30
31 MAX_MEMCACHED_SIZE_BYTES = 1000000
Vadim Sh. 2017/06/23 18:26:50 how many parts do we get?
aludwin 2017/06/26 17:12:56 Two currently - though in the unit test, we get 11
31 32
32 ### Models. 33 ### Models.
33 34
34 35
35 File = collections.namedtuple('File', ('content', 'who', 'when', 'version')) 36 File = collections.namedtuple('File', ('content', 'who', 'when', 'version'))
36 37
37 38
38 class VersionedFile(ndb.Model): 39 class VersionedFile(ndb.Model):
39 """Versionned entity. 40 """Versionned entity.
40 41
(...skipping 174 matching lines...) Expand 10 before | Expand all | Expand 10 after
215 return version, additionals 216 return version, additionals
216 217
217 218
218 def get_swarming_bot_zip(host): 219 def get_swarming_bot_zip(host):
219 """Returns a zipped file of all the files a bot needs to run. 220 """Returns a zipped file of all the files a bot needs to run.
220 221
221 Returns: 222 Returns:
222 A string representing the zipped file's contents. 223 A string representing the zipped file's contents.
223 """ 224 """
224 version, additionals = get_bot_version(host) 225 version, additionals = get_bot_version(host)
225 content = memcache.get('code-' + version, namespace='bot_code') 226 content = get_cached_swarming_bot_zip(version)
226 if content: 227 if content:
227 logging.debug('memcached bot code %s; %d bytes', version, len(content)) 228 logging.debug('memcached bot code %s; %d bytes', version, len(content))
228 return content 229 return content
229 230
230 # Get the start bot script from the database, if present. Pass an empty 231 # Get the start bot script from the database, if present. Pass an empty
231 # file if the files isn't present. 232 # file if the files isn't present.
232 additionals = additionals or { 233 additionals = additionals or {
233 'config/bot_config.py': get_bot_config().content, 234 'config/bot_config.py': get_bot_config().content,
234 } 235 }
235 bot_dir = os.path.join(ROOT_DIR, 'swarming_bot') 236 bot_dir = os.path.join(ROOT_DIR, 'swarming_bot')
236 content, version = bot_archive.get_swarming_bot_zip( 237 content, version = bot_archive.get_swarming_bot_zip(
237 bot_dir, host, utils.get_app_version(), additionals, 238 bot_dir, host, utils.get_app_version(), additionals,
238 local_config.settings().enable_ts_monitoring) 239 local_config.settings().enable_ts_monitoring)
239 # This is immutable so not no need to set expiration time.
240 memcache.set('code-' + version, content, namespace='bot_code')
241 logging.info('generated bot code %s; %d bytes', version, len(content)) 240 logging.info('generated bot code %s; %d bytes', version, len(content))
241 cache_swarming_bot_zip(version, content)
242 return content 242 return content
243 243
Vadim Sh. 2017/06/23 18:26:50 nit: \n (two blank lines between top-level code un
aludwin 2017/06/26 17:12:56 Done.
244 class MemcacheMissingException(Exception):
245 pass
246
Vadim Sh. 2017/06/23 18:26:50 nit: \n
aludwin 2017/06/26 17:12:56 Done.
247 def get_cached_swarming_bot_zip(version):
248 """Returns the bot contents if its been cached, or None if missing."""
249 try:
250 num_parts = get_cached_bot_entry(version, 'parts')
Vadim Sh. 2017/06/23 18:26:50 'parts' and 'signature' should be part of one memc
aludwin 2017/06/26 17:12:56 Done.
aludwin 2017/06/26 17:12:56 Done.
251 true_sig = get_cached_bot_entry(version, 'signature')
252 content = ''
253 for p in range(0, num_parts):
254 content += get_cached_bot_entry(version, 'content', p)
Vadim Sh. 2017/06/23 18:26:50 please use asynchronous memcache API to fetch part
aludwin 2017/06/26 17:12:56 Done.
255 h = hashlib.sha256()
256 h.update(content)
257 if h.hexdigest() != true_sig:
258 logging.error('bot %s had signature %s instead of expected %s', version,
259 h.hexdigest(), true_sig)
260 return None
261 return content
262 except MemcacheMissingException:
263 return None
264
265
266 def cache_swarming_bot_zip(version, content):
267 """Caches the bot code to memcache."""
268 h = hashlib.sha256()
269 h.update(content)
270 p = 0
271 while len(content) > 0:
Vadim Sh. 2017/06/23 18:26:50 same here, it should be done in parallel
aludwin 2017/06/26 17:12:56 Done.
272 chunk_size = min(MAX_MEMCACHED_SIZE_BYTES, len(content))
273 set_cached_bot_entry(content[0:chunk_size], version, 'content', p)
274 content=content[chunk_size:]
275 p += 1
276 set_cached_bot_entry(h.hexdigest(), version, 'signature')
277 set_cached_bot_entry(p, version, 'parts')
278 logging.info('bot %s with sig %s saved in memcached in %d chunks',
279 version, h.hexdigest(), p)
280
281
282 def get_cached_bot_entry(version, desc, part=None):
283 """Gets a bot entry from memcached.
284
285 Raise MemcacheMissingException if the entry is None.
286 """
287 res = memcache.get(cached_bot_key(version, desc, part), namespace='bot_code')
288 if res is None:
289 raise MemcacheMissingException()
290 return res
291
292
293 def set_cached_bot_entry(content, version, desc, part=None):
294 """Sets a bot entry in memcached."""
295 memcache.set(cached_bot_key(version, desc, part), content,
296 namespace='bot_code')
297
298
299 def cached_bot_key(version, desc, part):
300 """Returns a memcache key for bot entries."""
301 key = 'code-%s-%s' % (version, desc)
302 if part is not None:
303 key = '%s-%d' % (key, part)
304 return key
244 305
Vadim Sh. 2017/06/23 18:26:50 nit: \n
aludwin 2017/06/26 17:12:56 Missed this one but checked in locally for next up
245 ### Bootstrap token. 306 ### Bootstrap token.
246 307
247 308
248 class BootstrapToken(auth.TokenKind): 309 class BootstrapToken(auth.TokenKind):
249 expiration_sec = 3600 310 expiration_sec = 3600
250 secret_key = auth.SecretKey('bot_bootstrap_token') 311 secret_key = auth.SecretKey('bot_bootstrap_token')
251 version = 1 312 version = 1
252 313
253 314
254 def generate_bootstrap_token(): 315 def generate_bootstrap_token():
(...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after
306 367
307 ## Config validators 368 ## Config validators
308 369
309 370
310 @config.validation.self_rule('regex:scripts/.+\\.py') 371 @config.validation.self_rule('regex:scripts/.+\\.py')
311 def _validate_scripts(content, ctx): 372 def _validate_scripts(content, ctx):
312 try: 373 try:
313 ast.parse(content) 374 ast.parse(content)
314 except (SyntaxError, TypeError) as e: 375 except (SyntaxError, TypeError) as e:
315 ctx.error('invalid %s: %s' % (ctx.path, e)) 376 ctx.error('invalid %s: %s' % (ctx.path, e))
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698