| OLD | NEW |
| 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 management, e.g. list of known bots and their state. | 5 """Swarming bot management, e.g. list of known bots and their state. |
| 6 | 6 |
| 7 +---------+ | 7 +---------+ |
| 8 |BotRoot | | 8 |BotRoot | |
| 9 |id=bot_id| | 9 |id=bot_id| |
| 10 +---------+ | 10 +---------+ |
| (...skipping 10 matching lines...) Expand all Loading... |
| 21 happening for the bot. | 21 happening for the bot. |
| 22 - BotInfo is a 'dump-only' entity used for UI, it permits quickly show the | 22 - BotInfo is a 'dump-only' entity used for UI, it permits quickly show the |
| 23 state of every bots in an single query. It is basically a cache of the last | 23 state of every bots in an single query. It is basically a cache of the last |
| 24 BotEvent and additionally updated on poll. It doesn't need to be updated in a | 24 BotEvent and additionally updated on poll. It doesn't need to be updated in a |
| 25 transaction. | 25 transaction. |
| 26 - BotSettings contains bot-specific settings. It must be updated in a | 26 - BotSettings contains bot-specific settings. It must be updated in a |
| 27 transaction and contains admin-provided settings, contrary to the other | 27 transaction and contains admin-provided settings, contrary to the other |
| 28 entities which are generated from data provided by the bot itself. | 28 entities which are generated from data provided by the bot itself. |
| 29 """ | 29 """ |
| 30 | 30 |
| 31 import datetime |
| 31 import hashlib | 32 import hashlib |
| 32 | 33 |
| 33 from google.appengine.ext import ndb | 34 from google.appengine.ext import ndb |
| 34 | 35 |
| 35 from components import datastore_utils | 36 from components import datastore_utils |
| 36 from components import utils | 37 from components import utils |
| 37 from server import config | 38 from server import config |
| 38 from server import task_pack | 39 from server import task_pack |
| 39 | 40 |
| 40 | 41 |
| (...skipping 219 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 260 if order: | 261 if order: |
| 261 query = query.order(BotEvent.key) | 262 query = query.order(BotEvent.key) |
| 262 return query | 263 return query |
| 263 | 264 |
| 264 | 265 |
| 265 def get_settings_key(bot_id): | 266 def get_settings_key(bot_id): |
| 266 """Returns the BotSettings ndb.Key for a known bot.""" | 267 """Returns the BotSettings ndb.Key for a known bot.""" |
| 267 return ndb.Key(BotSettings, 'settings', parent=get_root_key(bot_id)) | 268 return ndb.Key(BotSettings, 'settings', parent=get_root_key(bot_id)) |
| 268 | 269 |
| 269 | 270 |
| 271 def filter_dimensions(q, dimensions): |
| 272 """Filters a ndb.Query for BotInfo based on dimensions in the request.""" |
| 273 for d in dimensions: |
| 274 parts = d.split(':', 1) |
| 275 if len(parts) != 2 or any(i.strip() != i or not i for i in parts): |
| 276 raise ValueError('Invalid dimensions') |
| 277 q = q.filter(BotInfo.dimensions_flat == d) |
| 278 return q |
| 279 |
| 280 |
| 281 def filter_availability(q, quarantined, is_dead, now): |
| 282 """Filters a ndb.Query for BotInfo based on quarantined/is_dead.""" |
| 283 if quarantined is not None: |
| 284 q = q.filter(BotInfo.quarantined == quarantined) |
| 285 |
| 286 dt = datetime.timedelta(seconds=config.settings().bot_death_timeout_secs) |
| 287 timeout = now - dt |
| 288 if is_dead: |
| 289 q = q.filter(BotInfo.last_seen_ts < timeout) |
| 290 elif is_dead is not None: |
| 291 q = q.filter(BotInfo.last_seen_ts > timeout) |
| 292 return q |
| 293 |
| 294 |
| 270 def bot_event( | 295 def bot_event( |
| 271 event_type, bot_id, external_ip, authenticated_as, dimensions, state, | 296 event_type, bot_id, external_ip, authenticated_as, dimensions, state, |
| 272 version, quarantined, task_id, task_name, **kwargs): | 297 version, quarantined, task_id, task_name, **kwargs): |
| 273 """Records when a bot has queried for work. | 298 """Records when a bot has queried for work. |
| 274 | 299 |
| 275 Arguments: | 300 Arguments: |
| 276 - event: event type. | 301 - event: event type. |
| 277 - bot_id: bot id. | 302 - bot_id: bot id. |
| 278 - external_ip: IP address as seen by the HTTP handler. | 303 - external_ip: IP address as seen by the HTTP handler. |
| 279 - authenticated_as: bot identity as seen by the HTTP handler. | 304 - authenticated_as: bot identity as seen by the HTTP handler. |
| (...skipping 88 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 368 Returns: | 393 Returns: |
| 369 Tuple (True to restart, text message explaining the reason). | 394 Tuple (True to restart, text message explaining the reason). |
| 370 """ | 395 """ |
| 371 # Periodically reboot bots to workaround OS level leaks (especially on Win). | 396 # Periodically reboot bots to workaround OS level leaks (especially on Win). |
| 372 running_time = state.get('running_time', 0) | 397 running_time = state.get('running_time', 0) |
| 373 assert isinstance(running_time, (int, float)) | 398 assert isinstance(running_time, (int, float)) |
| 374 period = get_bot_reboot_period(bot_id, state) | 399 period = get_bot_reboot_period(bot_id, state) |
| 375 if period and running_time > period: | 400 if period and running_time > period: |
| 376 return True, 'Periodic reboot: running longer than %ds' % period | 401 return True, 'Periodic reboot: running longer than %ds' % period |
| 377 return False, '' | 402 return False, '' |
| OLD | NEW |