Example #1
0
def manage_leased_machine(machine_lease):
    """Manages a leased machine.

  Args:
    machine_lease: MachineLease instance with client_request_id, hostname,
      lease_expiration_ts set.
  """
    assert machine_lease.client_request_id, machine_lease.key
    assert machine_lease.hostname, machine_lease.key
    assert machine_lease.lease_expiration_ts, machine_lease.key

    ensure_bot_info_exists(machine_lease)

    # Handle an expired lease.
    if machine_lease.lease_expiration_ts <= utils.utcnow():
        logging.info('MachineLease expired: %s', machine_lease.key)
        clear_lease_request(machine_lease.key, machine_lease.client_request_id)
        bot_management.get_info_key(machine_lease.hostname).delete()
        return

    # Handle an active lease with a termination task scheduled.
    # TODO(smut): Check if the bot got terminated by some other termination task.
    if machine_lease.termination_task:
        handle_termination_task(machine_lease)
        return

    # Handle a lease ready for early release.
    if machine_lease.early_release_secs:
        handle_early_release(machine_lease)
        return
Example #2
0
    def test_has_capacity_BotEvent(self):
        # Disable the memcache code path to confirm the DB based behavior.
        self.mock(task_queues, 'probably_has_capacity', lambda *_: None)

        d = {u'pool': [u'default'], u'os': [u'Ubuntu-16.04']}
        botid = 'id1'
        _bot_event(event_type='request_sleep',
                   dimensions={
                       'id': [botid],
                       'pool': ['default'],
                       'os': ['Ubuntu', 'Ubuntu-16.04']
                   })
        self.assertEqual(True, bot_management.has_capacity(d))

        or_dimensions = {
            u'pool': [u'default'],
            u'os': [u'Ubuntu-14.04|Ubuntu-16.04'],
        }

        # Delete the BotInfo, so the bot will disappear.
        bot_management.get_info_key(botid).delete()
        # The capacity is still found due to a recent BotEvent with this dimension.
        self.assertEqual(True, bot_management.has_capacity(d))
        self.assertEqual(True, bot_management.has_capacity(or_dimensions))

        self.mock_now(self.now, config.settings().bot_death_timeout_secs - 1)
        self.assertEqual(True, bot_management.has_capacity(d))
        self.assertEqual(True, bot_management.has_capacity(or_dimensions))

        self.mock_now(self.now, config.settings().bot_death_timeout_secs)
        self.assertEqual(False, bot_management.has_capacity(d))
        self.assertEqual(False, bot_management.has_capacity(or_dimensions))
Example #3
0
    def get(self, request):
        """Returns information about a known bot.

    This includes its state and dimensions, and if it is currently running a
    task.
    """
        logging.debug('%s', request)
        bot_id = request.bot_id
        bot = bot_management.get_info_key(bot_id).get()
        deleted = False
        if not bot:
            # If there is not BotInfo, look if there are BotEvent child of this
            # entity. If this is the case, it means the bot was deleted but it's
            # useful to show information about it to the user even if the bot was
            # deleted.
            events = bot_management.get_events_query(bot_id, True).fetch(1)
            if not events:
                raise endpoints.NotFoundException('%s not found.' % bot_id)
            bot = bot_management.BotInfo(
                key=bot_management.get_info_key(bot_id),
                dimensions_flat=task_queues.dimensions_to_flat(
                    events[0].dimensions),
                state=events[0].state,
                external_ip=events[0].external_ip,
                authenticated_as=events[0].authenticated_as,
                version=events[0].version,
                quarantined=events[0].quarantined,
                maintenance_msg=events[0].maintenance_msg,
                task_id=events[0].task_id,
                last_seen_ts=events[0].ts)
            deleted = True

        return message_conversion.bot_info_to_rpc(bot, deleted=deleted)
Example #4
0
def cleanup_bot(machine_lease):
    """Cleans up entities after a bot is removed."""
    bot_root_key = bot_management.get_root_key(machine_lease.hostname)
    # The bot is being removed, remove it from the task queues.
    task_queues.cleanup_after_bot(bot_root_key)
    bot_management.get_info_key(machine_lease.hostname).delete()
    clear_lease_request(machine_lease.key, machine_lease.client_request_id)
Example #5
0
def ensure_bot_info_exists(machine_lease):
    """Ensures a BotInfo entity exists and has Machine Provider-related fields.

  Args:
    machine_lease: MachineLease instance.
  """
    if machine_lease.bot_id == machine_lease.hostname:
        return
    bot_info = bot_management.get_info_key(machine_lease.hostname).get()
    if not (bot_info and bot_info.lease_id and bot_info.lease_expiration_ts
            and bot_info.machine_type):
        logging.info(
            'Creating BotEvent\nKey: %s\nHostname: %s\nBotInfo: %s',
            machine_lease.key,
            machine_lease.hostname,
            bot_info,
        )
        bot_management.bot_event(
            event_type='bot_leased',
            bot_id=machine_lease.hostname,
            external_ip=None,
            authenticated_as=None,
            dimensions=None,
            state=None,
            version=None,
            quarantined=False,
            maintenance_msg=None,
            task_id='',
            task_name=None,
            lease_id=machine_lease.lease_id,
            lease_expiration_ts=machine_lease.lease_expiration_ts,
            machine_type=machine_lease.machine_type.id(),
            machine_lease=machine_lease.key.id(),
        )
        # Occasionally bot_management.bot_event fails to store the BotInfo so
        # verify presence of Machine Provider fields. See https://crbug.com/681224.
        bot_info = bot_management.get_info_key(machine_lease.hostname).get()
        if not (bot_info and bot_info.lease_id and bot_info.lease_expiration_ts
                and bot_info.machine_type and bot_info.machine_lease):
            # If associate_bot_id isn't called, cron will try again later.
            logging.error(
                'Failed to put BotInfo\nKey: %s\nHostname: %s\nBotInfo: %s',
                machine_lease.key,
                machine_lease.hostname,
                bot_info,
            )
            return
        logging.info(
            'Put BotInfo\nKey: %s\nHostname: %s\nBotInfo: %s',
            machine_lease.key,
            machine_lease.hostname,
            bot_info,
        )
    associate_bot_id(machine_lease.key, machine_lease.hostname)
Example #6
0
    def delete(self, request):
        """Deletes the bot corresponding to a provided bot_id.

    At that point, the bot will not appears in the list of bots but it is still
    possible to get information about the bot with its bot id is known, as
    historical data is not deleted.

    It is meant to remove from the DB the presence of a bot that was retired,
    e.g. the VM was shut down already. Use 'terminate' instead of the bot is
    still alive.
    """
        logging.debug('%s', request)
        bot_info_key = bot_management.get_info_key(request.bot_id)
        bot_info = get_or_raise(
            bot_info_key)  # raises 404 if there is no such bot
        cleaned = False
        if bot_info.machine_lease:
            ml = lease_management.MachineLease.get_by_id(
                bot_info.machine_lease)
            if lease_management.release(ml):
                lease_management.cleanup_bot(ml)
                cleaned = True
        if not cleaned:
            # BotRoot is parent to BotInfo. It is important to note that the bot is
            # not there anymore, so it is not a member of any task queue.
            task_queues.cleanup_after_bot(bot_info_key.parent())
        bot_info_key.delete()
        return swarming_rpcs.DeletedResponse(deleted=True)
Example #7
0
  def test_bot_event_poll_sleep(self):
    now = datetime.datetime(2010, 1, 2, 3, 4, 5, 6)
    self.mock_now(now)
    bot_management.bot_event(
        event_type='request_sleep', bot_id='id1', external_ip='8.8.4.4',
        dimensions={'id': ['id1'], 'foo': ['bar']}, state={'ram': 65},
        version=hashlib.sha1().hexdigest(), quarantined=True, task_id=None,
        task_name=None)

    # Assert that BotInfo was updated too.
    expected = {
      'dimensions': {u'foo': [u'bar'], u'id': [u'id1']},
      'external_ip': u'8.8.4.4',
      'first_seen_ts': now,
      'id': 'id1',
      'last_seen_ts': now,
      'quarantined': True,
      'state': {u'ram': 65},
      'task_id': None,
      'task_name': None,
      'version': u'da39a3ee5e6b4b0d3255bfef95601890afd80709',
    }
    bot_info = bot_management.get_info_key('id1').get()
    self.assertEqual(expected, bot_info.to_dict())
    self.assertEqual(False, bot_info.is_busy)

    # No BotEvent is registered for 'poll'.
    self.assertEqual([], bot_management.get_events_query('id1', True).fetch())
    def test_bot_event_busy(self):
        _bot_event(event_type='bot_connected')
        _bot_event(event_type='request_task', task_id='12311', task_name='yo')
        expected = _gen_bot_info(composite=[
            bot_management.BotInfo.NOT_IN_MAINTENANCE,
            bot_management.BotInfo.ALIVE,
            bot_management.BotInfo.HEALTHY,
            bot_management.BotInfo.BUSY,
        ],
                                 task_id=u'12311',
                                 task_name=u'yo')
        bot_info = bot_management.get_info_key('id1').get()
        self.assertEqual(expected, bot_info.to_dict())

        expected = [
            _gen_bot_event(event_type=u'request_task', task_id=u'12311'),
            _gen_bot_event(event_type=u'bot_connected'),
        ]
        self.assertEqual(expected, [
            e.to_dict() for e in bot_management.get_events_query('id1', True)
        ])

        self.assertEqual(['bot_connected', 5, 'request_task', 5],
                         memcache.get('id1:2010-01-02T03:04',
                                      namespace='BotEvents'))
Example #9
0
    def terminate(self, request):
        """Asks a bot to terminate itself gracefully.

    The bot will stay in the DB, use 'delete' to remove it from the DB
    afterward. This request returns a pseudo-taskid that can be waited for to
    wait for the bot to turn down.

    This command is particularly useful when a privileged user needs to safely
    debug a machine specific issue. The user can trigger a terminate for one of
    the bot exhibiting the issue, wait for the pseudo-task to run then access
    the machine with the guarantee that the bot is not running anymore.
    """
        # TODO(maruel): Disallow a terminate task when there's one currently
        # pending or if the bot is considered 'dead', e.g. no contact since 10
        # minutes.
        logging.debug('%s', request)
        bot_id = unicode(request.bot_id)
        bot_key = bot_management.get_info_key(bot_id)
        get_or_raise(bot_key)  # raises 404 if there is no such bot
        try:
            # Craft a special priority 0 task to tell the bot to shutdown.
            request = task_request.create_termination_task(
                bot_id, wait_for_capacity=True)
        except (datastore_errors.BadValueError, TypeError, ValueError) as e:
            raise endpoints.BadRequestException(e.message)

        result_summary = task_scheduler.schedule_request(request,
                                                         secret_bytes=None)
        return swarming_rpcs.TerminateResponse(
            task_id=task_pack.pack_result_summary_key(result_summary.key))
    def test_bot_event(self):
        # connected.
        d = {
            u'id': [u'id1'],
            u'os': [u'Ubuntu', u'Ubuntu-16.04'],
            u'pool': [u'default'],
        }
        bot_management.bot_event(event_type='bot_connected',
                                 bot_id='id1',
                                 external_ip='8.8.4.4',
                                 authenticated_as='bot:id1.domain',
                                 dimensions=d,
                                 state={'ram': 65},
                                 version=_VERSION,
                                 quarantined=False,
                                 maintenance_msg=None,
                                 task_id=None,
                                 task_name=None)

        expected = _gen_bot_info()
        self.assertEqual(expected,
                         bot_management.get_info_key('id1').get().to_dict())

        self.assertEqual(['bot_connected', 5],
                         memcache.get('id1:2010-01-02T03:04',
                                      namespace='BotEvents'))
Example #11
0
 def get(self, bot_id):
   logging.error('Unexpected old client')
   bot = bot_management.get_info_key(bot_id).get()
   if not bot:
     self.abort_with_error(404, error='Bot not found')
   now = utils.utcnow()
   self.send_response(utils.to_json_encodable(bot.to_dict_with_now(now)))
Example #12
0
    def test_bot_event_busy(self):
        bot_management.bot_event(event_type='request_task',
                                 bot_id='id1',
                                 external_ip='8.8.4.4',
                                 authenticated_as='bot:id1.domain',
                                 dimensions={
                                     'id': ['id1'],
                                     'foo': ['bar']
                                 },
                                 state={'ram': 65},
                                 version=hashlib.sha256().hexdigest(),
                                 quarantined=False,
                                 maintenance_msg=None,
                                 task_id='12311',
                                 task_name='yo')

        expected = self._gen_bot_info(composite=[
            bot_management.BotInfo.NOT_IN_MAINTENANCE,
            bot_management.BotInfo.ALIVE,
            bot_management.BotInfo.NOT_MACHINE_PROVIDER,
            bot_management.BotInfo.HEALTHY,
            bot_management.BotInfo.BUSY,
        ],
                                      task_id=u'12311',
                                      task_name=u'yo')
        bot_info = bot_management.get_info_key('id1').get()
        self.assertEqual(expected, bot_info.to_dict())

        expected = [
            self._gen_bot_event(event_type=u'request_task', task_id=u'12311'),
        ]
        self.assertEqual(expected, [
            e.to_dict() for e in bot_management.get_events_query('id1', True)
        ])
Example #13
0
    def check_bot_code_access(self, bot_id, generate_token):
        """Raises AuthorizationError if caller is not authorized to access bot code.

    Four variants here:
      1. A valid bootstrap token is passed as '?tok=...' parameter.
      2. An user, allowed to do a bootstrap, is using their credentials.
      3. An IP whitelisted machine is making this call.
      4. A bot (with given bot_id) is using it's own machine credentials.

    In later three cases we optionally generate and return a new bootstrap
    token, that can be used to authorize /bot_code calls.
    """
        existing_token = self.request.get('tok')
        if existing_token:
            payload = bot_code.validate_bootstrap_token(existing_token)
            if payload is None:
                raise auth.AuthorizationError('Invalid bootstrap token')
            logging.debug('Using bootstrap token %r', payload)
            return existing_token

        machine_type = None
        if bot_id:
            bot_info = bot_management.get_info_key(bot_id).get()
            if bot_info:
                machine_type = bot_info.machine_type

        # TODO(vadimsh): Remove is_ip_whitelisted_machine check once all bots are
        # using auth for bootstrap and updating.
        if (not acl.can_create_bot() and not acl.is_ip_whitelisted_machine()
                and not (bot_id and bot_auth.is_authenticated_bot(
                    bot_id, machine_type))):
            raise auth.AuthorizationError('Not allowed to access the bot code')

        return bot_code.generate_bootstrap_token() if generate_token else None
Example #14
0
 def get(self, bot_id):
     logging.error('Unexpected old client')
     bot = bot_management.get_info_key(bot_id).get()
     if not bot:
         self.abort_with_error(404, error='Bot not found')
     now = utils.utcnow()
     self.send_response(utils.to_json_encodable(bot.to_dict_with_now(now)))
  def test_bot_event_poll_sleep(self):
    now = datetime.datetime(2010, 1, 2, 3, 4, 5, 6)
    self.mock_now(now)
    bot_management.bot_event(
        event_type='request_sleep', bot_id='id1', external_ip='8.8.4.4',
        dimensions={'id': ['id1'], 'foo': ['bar']}, state={'ram': 65},
        version=hashlib.sha1().hexdigest(), quarantined=True, task_id=None,
        task_name=None)

    # Assert that BotInfo was updated too.
    expected = {
      'dimensions': {u'foo': [u'bar'], u'id': [u'id1']},
      'external_ip': u'8.8.4.4',
      'first_seen_ts': now,
      'id': 'id1',
      'last_seen_ts': now,
      'quarantined': True,
      'state': {u'ram': 65},
      'task_id': None,
      'task_name': None,
      'version': u'da39a3ee5e6b4b0d3255bfef95601890afd80709',
    }
    bot_info = bot_management.get_info_key('id1').get()
    self.assertEqual(expected, bot_info.to_dict())
    self.assertEqual(False, bot_info.is_busy)

    # No BotEvent is registered for 'poll'.
    self.assertEqual([], bot_management.get_events_query('id1', True).fetch())
Example #16
0
    def test_bot_event_poll_sleep(self):
        bot_management.bot_event(event_type='request_sleep',
                                 bot_id='id1',
                                 external_ip='8.8.4.4',
                                 authenticated_as='bot:id1.domain',
                                 dimensions={
                                     'id': ['id1'],
                                     'foo': ['bar']
                                 },
                                 state={'ram': 65},
                                 version=hashlib.sha256().hexdigest(),
                                 quarantined=True,
                                 maintenance_msg=None,
                                 task_id=None,
                                 task_name=None)

        # Assert that BotInfo was updated too.
        expected = self._gen_bot_info(composite=[
            bot_management.BotInfo.NOT_IN_MAINTENANCE,
            bot_management.BotInfo.ALIVE,
            bot_management.BotInfo.NOT_MACHINE_PROVIDER,
            bot_management.BotInfo.QUARANTINED,
            bot_management.BotInfo.IDLE,
        ],
                                      quarantined=True)
        bot_info = bot_management.get_info_key('id1').get()
        self.assertEqual(expected, bot_info.to_dict())

        # No BotEvent is registered for 'poll'.
        self.assertEqual([],
                         bot_management.get_events_query('id1', True).fetch())
Example #17
0
    def test_bot_event_poll_sleep(self):
        _bot_event(event_type='request_sleep')

        # Assert that BotInfo was updated too.
        expected = _gen_bot_info(composite=[
            bot_management.BotInfo.NOT_IN_MAINTENANCE,
            bot_management.BotInfo.ALIVE,
            bot_management.BotInfo.HEALTHY,
            bot_management.BotInfo.IDLE,
        ])

        bot_info = bot_management.get_info_key('id1').get()
        self.assertEqual(expected, bot_info.to_dict())

        # BotEvent is registered for poll when BotInfo creates
        expected_event = _gen_bot_event(event_type=u'request_sleep')
        bot_events = bot_management.get_events_query('id1', True)
        self.assertEqual([expected_event], [e.to_dict() for e in bot_events])

        # flush bot events
        ndb.delete_multi(e.key for e in bot_events)

        # BotEvent is not registered for poll when no dimensions change
        _bot_event(event_type='request_sleep')
        self.assertEqual([],
                         bot_management.get_events_query('id1', True).fetch())

        # BotEvent is registered for poll when dimensions change
        dims = {u'foo': [u'bar']}
        _bot_event(event_type='request_sleep', dimensions=dims)
        expected_event['dimensions'] = dims
        bot_events = bot_management.get_events_query('id1', True).fetch()
        self.assertEqual([expected_event], [e.to_dict() for e in bot_events])
Example #18
0
 def delete(self, bot_id):
   # Only delete BotInfo, not BotRoot, BotEvent nor BotSettings.
   bot_key = bot_management.get_info_key(bot_id)
   found = False
   if bot_key.get():
     bot_key.delete()
     found = True
   self.send_response({'deleted': bool(found)})
Example #19
0
 def delete(self, bot_id):
   # Only delete BotInfo, not BotRoot, BotEvent nor BotSettings.
   bot_key = bot_management.get_info_key(bot_id)
   found = False
   if bot_key.get():
     bot_key.delete()
     found = True
   self.send_response({'deleted': bool(found)})
Example #20
0
def clean_up_bots():
    """Cleans up expired leases."""
    # Maximum number of in-flight ndb.Futures.
    MAX_IN_FLIGHT = 50

    bot_ids = []
    deleted = {}
    for machine_type in MachineType.query(
            MachineType.num_pending_deletion > 0):
        logging.info('Deleting bots: %s',
                     ', '.join(sorted(machine_type.pending_deletion)))
        bot_ids.extend(machine_type.pending_deletion)
        deleted[machine_type.key] = machine_type.pending_deletion

    # Generate a few asynchronous requests at a time in order to
    # prevent having too many in-flight ndb.Futures at a time.
    futures = []
    while bot_ids:
        num_futures = len(futures)
        if num_futures < MAX_IN_FLIGHT:
            keys = [
                bot_management.get_info_key(bot_id)
                for bot_id in bot_ids[:MAX_IN_FLIGHT - num_futures]
            ]
            bot_ids = bot_ids[MAX_IN_FLIGHT - num_futures:]
            futures.extend(ndb.delete_multi_async(keys))

        ndb.Future.wait_any(futures)
        futures = [future for future in futures if not future.done()]

    if futures:
        ndb.Future.wait_all(futures)

    # There should be relatively few MachineType entitites, so
    # just process them sequentially.
    # TODO(smut): Parallelize this.
    for machine_key, hostnames in deleted.iteritems():
        successfully_deleted = []
        for hostname in hostnames:
            if bot_management.get_info_key(hostname).get():
                logging.error('Failed to delete BotInfo: %s', hostname)
            else:
                successfully_deleted.append(hostname)
        logging.info('Deleted bots: %s',
                     ', '.join(sorted(successfully_deleted)))
        _clear_bots_pending_deletion(machine_key, hostnames)
Example #21
0
    def test_bot_event_busy(self):
        now = datetime.datetime(2010, 1, 2, 3, 4, 5, 6)
        self.mock_now(now)
        bot_management.bot_event(event_type='request_task',
                                 bot_id='id1',
                                 external_ip='8.8.4.4',
                                 dimensions={
                                     'id': ['id1'],
                                     'foo': ['bar']
                                 },
                                 state={'ram': 65},
                                 version=hashlib.sha1().hexdigest(),
                                 quarantined=False,
                                 task_id='12311',
                                 task_name='yo')

        expected = {
            'dimensions': {
                u'foo': [u'bar'],
                u'id': [u'id1']
            },
            'external_ip': u'8.8.4.4',
            'first_seen_ts': now,
            'id': 'id1',
            'last_seen_ts': now,
            'quarantined': False,
            'state': {
                u'ram': 65
            },
            'task_id': u'12311',
            'task_name': u'yo',
            'version': u'da39a3ee5e6b4b0d3255bfef95601890afd80709',
        }
        bot_info = bot_management.get_info_key('id1').get()
        self.assertEqual(expected, bot_info.to_dict())
        self.assertEqual(True, bot_info.is_busy)

        expected = [
            {
                'dimensions': {
                    u'foo': [u'bar'],
                    u'id': [u'id1']
                },
                'event_type': u'request_task',
                'external_ip': u'8.8.4.4',
                'message': None,
                'quarantined': False,
                'state': {
                    u'ram': 65
                },
                'task_id': u'12311',
                'ts': now,
                'version': u'da39a3ee5e6b4b0d3255bfef95601890afd80709',
            },
        ]
        self.assertEqual(
            expected,
            [e.to_dict() for e in bot_management.get_events_query('id1')])
Example #22
0
  def get(self, request):
    """Returns information about a known bot.

    This includes its state and dimensions, and if it is currently running a
    task.
    """
    logging.info('%s', request)
    bot = get_or_raise(bot_management.get_info_key(request.bot_id))
    return message_conversion.bot_info_to_rpc(bot, utils.utcnow())
Example #23
0
  def get(self, request):
    """Returns information about a known bot.

    This includes its state and dimensions, and if it is currently running a
    task.
    """
    logging.info('%s', request)
    bot = get_or_raise(bot_management.get_info_key(request.bot_id))
    return message_conversion.bot_info_to_rpc(bot, utils.utcnow())
Example #24
0
def handle_termination_task(machine_lease):
    """Checks the state of the termination task, releasing the lease if completed.

  Args:
    machine_lease: MachineLease instance.
  """
    assert machine_lease.termination_task

    task_result_summary = task_pack.unpack_result_summary_key(
        machine_lease.termination_task).get()
    if task_result_summary.state == task_result.State.COMPLETED:
        # There is a race condition where the bot reports the termination task as
        # completed but hasn't exited yet. The last thing it does before exiting
        # is post a bot_shutdown event. Check for the presence of a bot_shutdown
        # event which occurred after the termination task was completed.
        shutdown_ts = last_shutdown_ts(machine_lease.hostname)
        if not shutdown_ts or shutdown_ts < task_result_summary.completed_ts:
            logging.info(
                'Machine terminated but not yet shut down:\nKey: %s',
                machine_lease.key,
            )
            return

        response = machine_provider.release_machine(
            machine_lease.client_request_id)
        if response.get('error'):
            error = machine_provider.LeaseReleaseRequestError.lookup_by_name(
                response['error'])
            if error not in (
                    machine_provider.LeaseReleaseRequestError.
                    ALREADY_RECLAIMED,
                    machine_provider.LeaseReleaseRequestError.NOT_FOUND,
            ):
                logging.error(
                    'Lease release failed\nKey: %s\nRequest ID: %s\nError: %s',
                    machine_lease.key,
                    response['client_request_id'],
                    response['error'],
                )
                return
        logging.info('MachineLease released: %s', machine_lease.key)
        clear_lease_request(machine_lease.key, machine_lease.client_request_id)
        bot_management.get_info_key(machine_lease.hostname).delete()
Example #25
0
    def get(self, bot_id):
        # pagination is currently for tasks, not events.
        limit = int(self.request.get('limit', 100))
        cursor = datastore_query.Cursor(urlsafe=self.request.get('cursor'))
        bot_future = bot_management.get_info_key(bot_id).get_async()
        run_results, cursor, more = task_result.TaskRunResult.query(
            task_result.TaskRunResult.bot_id == bot_id).order(
                -task_result.TaskRunResult.started_ts).fetch_page(
                    limit, start_cursor=cursor)

        events_future = bot_management.get_events_query(bot_id).fetch_async(
            100)

        now = utils.utcnow()
        bot = bot_future.get_result()
        # Calculate the time this bot was idle.
        idle_time = datetime.timedelta()
        run_time = datetime.timedelta()
        if run_results:
            run_time = run_results[0].duration_now(now) or datetime.timedelta()
            if not cursor and run_results[0].state != task_result.State.RUNNING:
                # Add idle time since last task completed. Do not do this when a cursor
                # is used since it's not representative.
                idle_time = now - run_results[0].ended_ts
            for index in xrange(1, len(run_results)):
                # .started_ts will always be set by definition but .ended_ts may be None
                # if the task was abandoned. We can't count idle time since the bot may
                # have been busy running *another task*.
                # TODO(maruel): One option is to add a third value "broken_time".
                # Looking at timestamps specifically could help too, e.g. comparing
                # ended_ts of this task vs the next one to see if the bot was assigned
                # two tasks simultaneously.
                if run_results[index].ended_ts:
                    idle_time += (run_results[index - 1].started_ts -
                                  run_results[index].ended_ts)
                    duration = run_results[index].duration
                    if duration:
                        run_time += duration

        params = {
            'bot': bot,
            'bot_id': bot_id,
            'current_version': bot_code.get_bot_version(self.request.host_url),
            'cursor': cursor.urlsafe() if cursor and more else None,
            'events': events_future.get_result(),
            'idle_time': idle_time,
            'is_admin': acl.is_admin(),
            'limit': limit,
            'now': now,
            'run_results': run_results,
            'run_time': run_time,
            'xsrf_token': self.generate_xsrf_token(),
        }
        self.response.write(
            template.render('swarming/restricted_bot.html', params))
def clean_up_bots():
  """Cleans up expired leases."""
  # Maximum number of in-flight ndb.Futures.
  MAX_IN_FLIGHT = 50

  bot_ids = []
  deleted = {}
  for machine_type in MachineType.query(MachineType.num_pending_deletion > 0):
    logging.info(
        'Deleting bots: %s', ', '.join(sorted(machine_type.pending_deletion)))
    bot_ids.extend(machine_type.pending_deletion)
    deleted[machine_type.key] = machine_type.pending_deletion

  # Generate a few asynchronous requests at a time in order to
  # prevent having too many in-flight ndb.Futures at a time.
  futures = []
  while bot_ids:
    num_futures = len(futures)
    if num_futures < MAX_IN_FLIGHT:
      keys = [bot_management.get_info_key(bot_id)
              for bot_id in bot_ids[:MAX_IN_FLIGHT - num_futures]]
      bot_ids = bot_ids[MAX_IN_FLIGHT - num_futures:]
      futures.extend(ndb.delete_multi_async(keys))

    ndb.Future.wait_any(futures)
    futures = [future for future in futures if not future.done()]

  if futures:
    ndb.Future.wait_all(futures)

  # There should be relatively few MachineType entitites, so
  # just process them sequentially.
  # TODO(smut): Parallelize this.
  for machine_key, hostnames in deleted.iteritems():
    successfully_deleted = []
    for hostname in hostnames:
      if bot_management.get_info_key(hostname).get():
        logging.error('Failed to delete BotInfo: %s', hostname)
      else:
        successfully_deleted.append(hostname)
    logging.info('Deleted bots: %s', ', '.join(sorted(successfully_deleted)))
    _clear_bots_pending_deletion(machine_key, hostnames)
Example #27
0
  def get(self, bot_id):
    # pagination is currently for tasks, not events.
    limit = int(self.request.get('limit', 100))
    cursor = datastore_query.Cursor(urlsafe=self.request.get('cursor'))
    bot_future = bot_management.get_info_key(bot_id).get_async()
    run_results, cursor, more = task_result.TaskRunResult.query(
        task_result.TaskRunResult.bot_id == bot_id).order(
            -task_result.TaskRunResult.started_ts).fetch_page(
                limit, start_cursor=cursor)

    events_future = bot_management.get_events_query(bot_id).fetch_async(100)

    now = utils.utcnow()
    bot = bot_future.get_result()
    # Calculate the time this bot was idle.
    idle_time = datetime.timedelta()
    run_time = datetime.timedelta()
    if run_results:
      run_time = run_results[0].duration_now(now) or datetime.timedelta()
      if not cursor and run_results[0].state != task_result.State.RUNNING:
        # Add idle time since last task completed. Do not do this when a cursor
        # is used since it's not representative.
        idle_time = now - run_results[0].ended_ts
      for index in xrange(1, len(run_results)):
        # .started_ts will always be set by definition but .ended_ts may be None
        # if the task was abandoned. We can't count idle time since the bot may
        # have been busy running *another task*.
        # TODO(maruel): One option is to add a third value "broken_time".
        # Looking at timestamps specifically could help too, e.g. comparing
        # ended_ts of this task vs the next one to see if the bot was assigned
        # two tasks simultaneously.
        if run_results[index].ended_ts:
          idle_time += (
              run_results[index-1].started_ts - run_results[index].ended_ts)
          duration = run_results[index].duration
          if duration:
            run_time += duration

    params = {
      'bot': bot,
      'bot_id': bot_id,
      'current_version': bot_code.get_bot_version(self.request.host_url),
      'cursor': cursor.urlsafe() if cursor and more else None,
      'events': events_future.get_result(),
      'idle_time': idle_time,
      'is_admin': acl.is_admin(),
      'limit': limit,
      'now': now,
      'run_results': run_results,
      'run_time': run_time,
      'xsrf_token': self.generate_xsrf_token(),
    }
    self.response.write(
        template.render('swarming/restricted_bot.html', params))
Example #28
0
  def delete(self, request):
    """Deletes the bot corresponding to a provided bot_id.

    At that point, the bot will not appears in the list of bots but it is still
    possible to get information about the bot with its bot id is known, as
    historical data is not deleted.

    It is meant to remove from the DB the presence of a bot that was retired,
    e.g. the VM was shut down already. Use 'terminate' instead of the bot is
    still alive.
    """
    logging.info('%s', request)
    bot_key = bot_management.get_info_key(request.bot_id)
    get_or_raise(bot_key)  # raises 404 if there is no such bot
    bot_key.delete()
    return swarming_rpcs.DeletedResponse(deleted=True)
Example #29
0
  def delete(self, request):
    """Deletes the bot corresponding to a provided bot_id.

    At that point, the bot will not appears in the list of bots but it is still
    possible to get information about the bot with its bot id is known, as
    historical data is not deleted.

    It is meant to remove from the DB the presence of a bot that was retired,
    e.g. the VM was shut down already. Use 'terminate' instead of the bot is
    still alive.
    """
    logging.info('%s', request)
    bot_key = bot_management.get_info_key(request.bot_id)
    get_or_raise(bot_key)  # raises 404 if there is no such bot
    bot_key.delete()
    return swarming_rpcs.DeletedResponse(deleted=True)
Example #30
0
    def test_bot_event(self):
        # connected.
        d = {
            u'id': [u'id1'],
            u'os': [u'Ubuntu', u'Ubuntu-16.04'],
            u'pool': [u'default'],
        }
        event = 'request_sleep'
        _bot_event(event_type=event, bot_id='id1', dimensions=d)

        expected = _gen_bot_info()
        self.assertEqual(expected,
                         bot_management.get_info_key('id1').get().to_dict())

        self.assertEqual([event, 5],
                         memcache.get('id1:2010-01-02T03:04',
                                      namespace='BotEvents'))
    def test_bot_event_poll_sleep(self):
        _bot_event(event_type='request_sleep', quarantined=True)

        # Assert that BotInfo was updated too.
        expected = _gen_bot_info(composite=[
            bot_management.BotInfo.NOT_IN_MAINTENANCE,
            bot_management.BotInfo.ALIVE,
            bot_management.BotInfo.QUARANTINED,
            bot_management.BotInfo.IDLE,
        ],
                                 quarantined=True)
        bot_info = bot_management.get_info_key('id1').get()
        self.assertEqual(expected, bot_info.to_dict())

        # No BotEvent is registered for 'poll'.
        self.assertEqual([],
                         bot_management.get_events_query('id1', True).fetch())
Example #32
0
    def post(self, task_id=None):
        request = self.parse_body()
        bot_id = request.get('id')
        task_id = request.get('task_id', '')
        message = request.get('message', 'unknown')

        machine_type = None
        bot_info = bot_management.get_info_key(bot_id).get()
        if bot_info:
            machine_type = bot_info.machine_type

        # Make sure bot self-reported ID matches the authentication token. Raises
        # auth.AuthorizationError if not.
        bot_auth.validate_bot_id_and_fetch_config(bot_id, machine_type)

        bot_management.bot_event(
            event_type='task_error',
            bot_id=bot_id,
            external_ip=self.request.remote_addr,
            authenticated_as=auth.get_peer_identity().to_bytes(),
            dimensions=None,
            state=None,
            version=None,
            quarantined=None,
            maintenance_msg=None,
            task_id=task_id,
            task_name=None,
            message=message)
        line = ('Bot: https://%s/restricted/bot/%s\n'
                'Task failed: https://%s/user/task/%s\n'
                '%s') % (app_identity.get_default_version_hostname(), bot_id,
                         app_identity.get_default_version_hostname(), task_id,
                         message)
        ereporter2.log_request(self.request, source='bot', message=line)

        msg = log_unexpected_keys(self.EXPECTED_KEYS, request, self.request,
                                  'bot', 'keys')
        if msg:
            self.abort_with_error(400, error=msg)

        msg = task_scheduler.bot_kill_task(
            task_pack.unpack_run_result_key(task_id), bot_id)
        if msg:
            logging.error(msg)
            self.abort_with_error(400, error=msg)
        self.send_response({})
Example #33
0
    def terminate(self, request):
        """Asks a bot to terminate itself gracefully.

    The bot will stay in the DB, use 'delete' to remove it from the DB
    afterward. This request returns a pseudo-taskid that can be waited for to
    wait for the bot to turn down.

    This command is particularly useful when a privileged user needs to safely
    debug a machine specific issue. The user can trigger a terminate for one of
    the bot exhibiting the issue, wait for the pseudo-task to run then access
    the machine with the guarantee that the bot is not running anymore.
    """
        # TODO(maruel): Disallow a terminate task when there's one currently
        # pending or if the bot is considered 'dead', e.g. no contact since 10
        # minutes.
        logging.info('%s', request)
        bot_key = bot_management.get_info_key(request.bot_id)
        get_or_raise(bot_key)  # raises 404 if there is no such bot
        try:
            # Craft a special priority 0 task to tell the bot to shutdown.
            properties = task_request.TaskProperties(
                dimensions={u'id': request.bot_id},
                execution_timeout_secs=0,
                grace_period_secs=0,
                io_timeout_secs=0)
            now = utils.utcnow()
            request = task_request.TaskRequest(
                created_ts=now,
                expiration_ts=now + datetime.timedelta(days=1),
                name='Terminate %s' % request.bot_id,
                priority=0,
                properties=properties,
                tags=['terminate:1'],
                user=auth.get_current_identity().to_bytes())
            assert request.properties.is_terminate
            posted_request = task_request.make_request(request,
                                                       acl.is_bot_or_admin())
        except (datastore_errors.BadValueError, TypeError, ValueError) as e:
            raise endpoints.BadRequestException(e.message)

        result_summary = task_scheduler.schedule_request(posted_request)
        return swarming_rpcs.TerminateResponse(
            task_id=task_pack.pack_result_summary_key(result_summary.key))
Example #34
0
    def test_bot_event_update_dimensions(self):
        bot_id = 'id1'
        bot_info_key = bot_management.get_info_key(bot_id)

        # bot dimensions generated without injected bot_config.py.
        dimensions_invalid = {
            'id': ['id1'],
            'os': ['Ubuntu'],
            'pool': ['default']
        }

        # 'bot_connected' event creates BotInfo only with id and pool dimensions.
        _bot_event(bot_id=bot_id,
                   event_type='bot_connected',
                   dimensions=dimensions_invalid)
        self.assertEqual(bot_info_key.get().dimensions_flat,
                         [u'id:id1', u'pool:default'])

        # 'bot_hook_log' event does not register dimensions other than id and pool.
        _bot_event(bot_id=bot_id,
                   event_type='bot_hook_log',
                   dimensions=dimensions_invalid)
        self.assertEqual(bot_info_key.get().dimensions_flat,
                         [u'id:id1', u'pool:default'])

        # 'request_sleep' registers given dimensions to BotInfo.
        _bot_event(bot_id=bot_id,
                   event_type='request_sleep',
                   dimensions={
                       'id': ['id1'],
                       'os': ['Android'],
                       'pool': ['default']
                   })
        self.assertEqual(bot_info_key.get().dimensions_flat,
                         [u'id:id1', u'os:Android', u'pool:default'])

        # 'bot_connected' doesn't update dimensions since bot_config isn't injected.
        _bot_event(bot_id=bot_id,
                   event_type='bot_connected',
                   dimensions=dimensions_invalid)
        self.assertEqual(bot_info_key.get().dimensions_flat,
                         [u'id:id1', u'os:Android', u'pool:default'])
  def terminate(self, request):
    """Asks a bot to terminate itself gracefully.

    The bot will stay in the DB, use 'delete' to remove it from the DB
    afterward. This request returns a pseudo-taskid that can be waited for to
    wait for the bot to turn down.

    This command is particularly useful when a privileged user needs to safely
    debug a machine specific issue. The user can trigger a terminate for one of
    the bot exhibiting the issue, wait for the pseudo-task to run then access
    the machine with the guarantee that the bot is not running anymore.
    """
    # TODO(maruel): Disallow a terminate task when there's one currently
    # pending or if the bot is considered 'dead', e.g. no contact since 10
    # minutes.
    logging.info('%s', request)
    bot_key = bot_management.get_info_key(request.bot_id)
    get_or_raise(bot_key)  # raises 404 if there is no such bot
    try:
      # Craft a special priority 0 task to tell the bot to shutdown.
      properties = task_request.TaskProperties(
          dimensions={u'id': request.bot_id},
          execution_timeout_secs=0,
          grace_period_secs=0,
          io_timeout_secs=0)
      now = utils.utcnow()
      request = task_request.TaskRequest(
          created_ts=now,
          expiration_ts=now + datetime.timedelta(days=1),
          name='Terminate %s' % request.bot_id,
          priority=0,
          properties=properties,
          tags=['terminate:1'],
          user=auth.get_current_identity().to_bytes())
      assert request.properties.is_terminate
      posted_request = task_request.make_request(request, acl.is_bot_or_admin())
    except (datastore_errors.BadValueError, TypeError, ValueError) as e:
      raise endpoints.BadRequestException(e.message)

    result_summary = task_scheduler.schedule_request(posted_request)
    return swarming_rpcs.TerminateResponse(
        task_id=task_pack.pack_result_summary_key(result_summary.key))
Example #36
0
    def test_bot_event(self):
        # connected.
        bot_management.bot_event(event_type='bot_connected',
                                 bot_id='id1',
                                 external_ip='8.8.4.4',
                                 authenticated_as='bot:id1.domain',
                                 dimensions={
                                     'id': ['id1'],
                                     'foo': ['bar']
                                 },
                                 state={'ram': 65},
                                 version=hashlib.sha256().hexdigest(),
                                 quarantined=False,
                                 maintenance_msg=None,
                                 task_id=None,
                                 task_name=None)

        expected = self._gen_bot_info()
        self.assertEqual(expected,
                         bot_management.get_info_key('id1').get().to_dict())
Example #37
0
    def test_bot_event_busy(self):
        _bot_event(event_type='request_task', task_id='12311', task_name='yo')
        expected = _gen_bot_info(composite=[
            bot_management.BotInfo.NOT_IN_MAINTENANCE,
            bot_management.BotInfo.ALIVE,
            bot_management.BotInfo.NOT_MACHINE_PROVIDER,
            bot_management.BotInfo.HEALTHY,
            bot_management.BotInfo.BUSY,
        ],
                                 task_id=u'12311',
                                 task_name=u'yo')
        bot_info = bot_management.get_info_key('id1').get()
        self.assertEqual(expected, bot_info.to_dict())

        expected = [
            _gen_bot_event(event_type=u'request_task', task_id=u'12311'),
        ]
        self.assertEqual(expected, [
            e.to_dict() for e in bot_management.get_events_query('id1', True)
        ])
  def test_bot_event_busy(self):
    now = datetime.datetime(2010, 1, 2, 3, 4, 5, 6)
    self.mock_now(now)
    bot_management.bot_event(
        event_type='request_task', bot_id='id1', external_ip='8.8.4.4',
        dimensions={'id': ['id1'], 'foo': ['bar']}, state={'ram': 65},
        version=hashlib.sha1().hexdigest(), quarantined=False, task_id='12311',
        task_name='yo')

    expected = {
      'dimensions': {u'foo': [u'bar'], u'id': [u'id1']},
      'external_ip': u'8.8.4.4',
      'first_seen_ts': now,
      'id': 'id1',
      'last_seen_ts': now,
      'quarantined': False,
      'state': {u'ram': 65},
      'task_id': u'12311',
      'task_name': u'yo',
      'version': u'da39a3ee5e6b4b0d3255bfef95601890afd80709',
    }
    bot_info = bot_management.get_info_key('id1').get()
    self.assertEqual(expected, bot_info.to_dict())
    self.assertEqual(True, bot_info.is_busy)

    expected = [
      {
        'dimensions': {u'foo': [u'bar'], u'id': [u'id1']},
        'event_type': u'request_task',
        'external_ip': u'8.8.4.4',
        'message': None,
        'quarantined': False,
        'state': {u'ram': 65},
        'task_id': u'12311',
        'ts': now,
        'version': u'da39a3ee5e6b4b0d3255bfef95601890afd80709',
      },
    ]
    self.assertEqual(
        expected,
        [e.to_dict() for e in bot_management.get_events_query('id1', True)])
Example #39
0
    def test_bot_event(self):
        # connected.
        now = datetime.datetime(2010, 1, 2, 3, 4, 5, 6)
        self.mock_now(now)
        bot_management.bot_event(event_type='bot_connected',
                                 bot_id='id1',
                                 external_ip='8.8.4.4',
                                 authenticated_as='bot:id1.domain',
                                 dimensions={
                                     'id': ['id1'],
                                     'foo': ['bar']
                                 },
                                 state={'ram': 65},
                                 version=hashlib.sha1().hexdigest(),
                                 quarantined=False,
                                 task_id=None,
                                 task_name=None)

        expected = {
            'authenticated_as': 'bot:id1.domain',
            'dimensions': {
                u'foo': [u'bar'],
                u'id': [u'id1']
            },
            'external_ip': u'8.8.4.4',
            'first_seen_ts': now,
            'id': 'id1',
            'last_seen_ts': now,
            'lease_id': None,
            'lease_expiration_ts': None,
            'quarantined': False,
            'state': {
                u'ram': 65
            },
            'task_id': None,
            'task_name': None,
            'version': u'da39a3ee5e6b4b0d3255bfef95601890afd80709',
        }
        self.assertEqual(expected,
                         bot_management.get_info_key('id1').get().to_dict())
  def test_bot_event(self):
    # connected.
    now = datetime.datetime(2010, 1, 2, 3, 4, 5, 6)
    self.mock_now(now)
    bot_management.bot_event(
        event_type='bot_connected', bot_id='id1', external_ip='8.8.4.4',
        dimensions={'id': ['id1'], 'foo': ['bar']}, state={'ram': 65},
        version=hashlib.sha1().hexdigest(), quarantined=False, task_id=None,
        task_name=None)

    expected = {
      'dimensions': {u'foo': [u'bar'], u'id': [u'id1']},
      'external_ip': u'8.8.4.4',
      'first_seen_ts': now,
      'id': 'id1',
      'last_seen_ts': now,
      'quarantined': False,
      'state': {u'ram': 65},
      'task_id': None,
      'task_name': None,
      'version': u'da39a3ee5e6b4b0d3255bfef95601890afd80709',
    }
    self.assertEqual(
        expected, bot_management.get_info_key('id1').get().to_dict())
Example #41
0
  def get(self, bot_id):
    # pagination is currently for tasks, not events.
    limit = int(self.request.get('limit', 100))
    cursor = datastore_query.Cursor(urlsafe=self.request.get('cursor'))
    run_results_future = task_result.TaskRunResult.query(
        task_result.TaskRunResult.bot_id == bot_id).order(
            -task_result.TaskRunResult.started_ts).fetch_page_async(
                limit, start_cursor=cursor)
    bot_future = bot_management.get_info_key(bot_id).get_async()
    events_future = bot_management.get_events_query(
        bot_id, True).fetch_async(100)

    now = utils.utcnow()

    # Calculate the time this bot was idle.
    idle_time = datetime.timedelta()
    run_time = datetime.timedelta()
    run_results, cursor, more = run_results_future.get_result()
    if run_results:
      run_time = run_results[0].duration_now(now) or datetime.timedelta()
      if not cursor and run_results[0].state != task_result.State.RUNNING:
        # Add idle time since last task completed. Do not do this when a cursor
        # is used since it's not representative.
        idle_time = now - run_results[0].ended_ts
      for index in xrange(1, len(run_results)):
        # .started_ts will always be set by definition but .ended_ts may be None
        # if the task was abandoned. We can't count idle time since the bot may
        # have been busy running *another task*.
        # TODO(maruel): One option is to add a third value "broken_time".
        # Looking at timestamps specifically could help too, e.g. comparing
        # ended_ts of this task vs the next one to see if the bot was assigned
        # two tasks simultaneously.
        if run_results[index].ended_ts:
          idle_time += (
              run_results[index-1].started_ts - run_results[index].ended_ts)
          # We are taking the whole time the bot was doing work, not just the
          # duration associated with the task.
          duration = run_results[index].duration_total
          if duration:
            run_time += duration

    events = events_future.get_result()
    bot = bot_future.get_result()
    if not bot and events:
      # If there is not BotInfo, look if there are BotEvent child of this
      # entity. If this is the case, it means the bot was deleted but it's
      # useful to show information about it to the user even if the bot was
      # deleted. For example, it could be an auto-scaled bot.
      bot = bot_management.BotInfo(
          key=bot_management.get_info_key(bot_id),
          dimensions=events[0].dimensions,
          state=events[0].state,
          external_ip=events[0].external_ip,
          version=events[0].version,
          quarantined=events[0].quarantined,
          task_id=events[0].task_id,
          last_seen_ts=events[0].ts)

    params = {
      'bot': bot,
      'bot_id': bot_id,
      'current_version': bot_code.get_bot_version(self.request.host_url),
      'cursor': cursor.urlsafe() if cursor and more else None,
      'events': events,
      'idle_time': idle_time,
      'is_admin': acl.is_admin(),
      'limit': limit,
      'now': now,
      'run_results': run_results,
      'run_time': run_time,
      'xsrf_token': self.generate_xsrf_token(),
    }
    self.response.write(
        template.render('swarming/restricted_bot.html', params))
Example #42
0
    def get(self, task_id):
        try:
            key = task_pack.unpack_result_summary_key(task_id)
            request_key = task_pack.result_summary_key_to_request_key(key)
        except ValueError:
            try:
                key = task_pack.unpack_run_result_key(task_id)
                request_key = task_pack.result_summary_key_to_request_key(
                    task_pack.run_result_key_to_result_summary_key(key)
                )
            except (NotImplementedError, ValueError):
                self.abort(404, "Invalid key format.")

        # 'result' can be either a TaskRunResult or TaskResultSummary.
        result_future = key.get_async()
        request_future = request_key.get_async()
        result = result_future.get_result()
        if not result:
            self.abort(404, "Invalid key.")

        if not acl.is_privileged_user():
            self.abort(403, "Implement access control based on the user")

        request = request_future.get_result()
        parent_task_future = None
        if request.parent_task_id:
            parent_key = task_pack.unpack_run_result_key(request.parent_task_id)
            parent_task_future = parent_key.get_async()
        children_tasks_futures = [task_pack.unpack_result_summary_key(c).get_async() for c in result.children_task_ids]

        bot_id = result.bot_id
        following_task_future = None
        previous_task_future = None
        if result.started_ts:
            # Use a shortcut name because it becomes unwieldy otherwise.
            cls = task_result.TaskRunResult

            # Note that the links will be to the TaskRunResult, not to
            # TaskResultSummary.
            following_task_future = (
                cls.query(cls.bot_id == bot_id, cls.started_ts > result.started_ts).order(cls.started_ts).get_async()
            )
            previous_task_future = (
                cls.query(cls.bot_id == bot_id, cls.started_ts < result.started_ts).order(-cls.started_ts).get_async()
            )

        bot_future = bot_management.get_info_key(bot_id).get_async() if bot_id else None

        following_task = None
        if following_task_future:
            following_task = following_task_future.get_result()

        previous_task = None
        if previous_task_future:
            previous_task = previous_task_future.get_result()

        parent_task = None
        if parent_task_future:
            parent_task = parent_task_future.get_result()
        children_tasks = [c.get_result() for c in children_tasks_futures]

        params = {
            "bot": bot_future.get_result() if bot_future else None,
            "children_tasks": children_tasks,
            "is_admin": acl.is_admin(),
            "is_gae_admin": users.is_current_user_admin(),
            "is_privileged_user": acl.is_privileged_user(),
            "following_task": following_task,
            "full_appid": os.environ["APPLICATION_ID"],
            "host_url": self.request.host_url,
            "is_running": result.state == task_result.State.RUNNING,
            "now": utils.utcnow(),
            "parent_task": parent_task,
            "previous_task": previous_task,
            "request": request,
            "task": result,
            "xsrf_token": self.generate_xsrf_token(),
        }
        self.response.write(template.render("swarming/user_task.html", params))
 def test_get_info_key(self):
   self.assertEqual(
       ndb.Key(bot_management.BotRoot, 'foo', bot_management.BotInfo, 'info'),
       bot_management.get_info_key('foo'))
Example #44
0
  def get(self, task_id):
    try:
      key = task_pack.unpack_result_summary_key(task_id)
      request_key = task_pack.result_summary_key_to_request_key(key)
    except ValueError:
      try:
        key = task_pack.unpack_run_result_key(task_id)
        request_key = task_pack.result_summary_key_to_request_key(
            task_pack.run_result_key_to_result_summary_key(key))
      except (NotImplementedError, ValueError):
        self.abort(404, 'Invalid key format.')

    # 'result' can be either a TaskRunResult or TaskResultSummary.
    result_future = key.get_async()
    request_future = request_key.get_async()
    result = result_future.get_result()
    if not result:
      self.abort(404, 'Invalid key.')

    if not acl.is_privileged_user():
      self.abort(403, 'Implement access control based on the user')

    request = request_future.get_result()
    parent_task_future = None
    if request.parent_task_id:
      parent_key = task_pack.unpack_run_result_key(request.parent_task_id)
      parent_task_future = parent_key.get_async()
    children_tasks_futures = [
      task_pack.unpack_result_summary_key(c).get_async()
      for c in result.children_task_ids
    ]

    bot_id = result.bot_id
    following_task_future = None
    previous_task_future = None
    if result.started_ts:
      # Use a shortcut name because it becomes unwieldy otherwise.
      cls = task_result.TaskRunResult

      # Note that the links will be to the TaskRunResult, not to
      # TaskResultSummary.
      following_task_future = cls.query(
          cls.bot_id == bot_id,
          cls.started_ts > result.started_ts,
          ).order(cls.started_ts).get_async()
      previous_task_future = cls.query(
          cls.bot_id == bot_id,
          cls.started_ts < result.started_ts,
          ).order(-cls.started_ts).get_async()

    bot_future = (
        bot_management.get_info_key(bot_id).get_async() if bot_id else None)

    following_task = None
    if following_task_future:
      following_task = following_task_future.get_result()

    previous_task = None
    if previous_task_future:
      previous_task = previous_task_future.get_result()

    parent_task = None
    if parent_task_future:
      parent_task = parent_task_future.get_result()
    children_tasks = [c.get_result() for c in children_tasks_futures]

    params = {
      'bot': bot_future.get_result() if bot_future else None,
      'children_tasks': children_tasks,
      'is_admin': acl.is_admin(),
      'is_gae_admin': users.is_current_user_admin(),
      'is_privileged_user': acl.is_privileged_user(),
      'following_task': following_task,
      'full_appid': os.environ['APPLICATION_ID'],
      'host_url': self.request.host_url,
      'is_running': result.state == task_result.State.RUNNING,
      'now': utils.utcnow(),
      'parent_task': parent_task,
      'previous_task': previous_task,
      'request': request,
      'task': result,
      'xsrf_token': self.generate_xsrf_token(),
    }
    self.response.write(template.render('swarming/user_task.html', params))
Example #45
0
 def post(self, bot_id):
   bot_key = bot_management.get_info_key(bot_id)
   if bot_key.get():
     bot_key.delete()
   self.redirect('/restricted/bots')
Example #46
0
 def get(self, request):
   """Provides BotInfo corresponding to a provided bot_id."""
   bot = get_or_raise(bot_management.get_info_key(request.bot_id))
   entity_dict = bot.to_dict_with_now(utils.utcnow())
   return message_conversion.bot_info_from_dict(entity_dict)
Example #47
0
 def delete(self, request):
   """Deletes the bot corresponding to a provided bot_id."""
   bot_key = bot_management.get_info_key(request.bot_id)
   get_or_raise(bot_key)  # raises 404 if there is no such bot
   bot_key.delete()
   return swarming_rpcs.DeletedResponse(deleted=True)