def test_or_dimensions_new_tasks(self):
        # Bots are already registered, then new tasks show up
        self.mock_now(datetime.datetime(2020, 1, 2, 3, 4, 5))
        self.assertEqual(
            0,
            _assert_bot(bot_id=u'bot1',
                        dimensions={
                            u'os': [u'v1', u'v2'],
                            u'gpu': [u'nv'],
                        }))
        self.assertEqual(
            0,
            _assert_bot(bot_id=u'bot2',
                        dimensions={
                            u'os': [u'v2'],
                            u'gpu': [u'amd'],
                        }))

        payloads = self._mock_enqueue_task_async_for_rebuild_task_cache()

        request1 = _gen_request(properties=_gen_properties(
            dimensions={
                u'pool': [u'default'],
                u'os': [u'v1|v2'],
                u'gpu': [u'nv|amd'],
            }))
        task_queues.assert_task_async(request1).get_result()
        self.assertEqual(1, len(payloads))
        f = task_queues.rebuild_task_cache_async(payloads[-1])
        self.assertEqual(True, f.get_result())
        payloads.pop()

        # Both bots should be able to handle |request1|
        self.assert_count(2, task_queues.BotDimensions)
        self.assert_count(2, task_queues.BotTaskDimensions)
        self.assert_count(1, task_queues.TaskDimensions)
        self.assertEqual(4, len(task_queues.TaskDimensions.query().get().sets))
        bot1_root_key = bot_management.get_root_key(u'bot1')
        bot2_root_key = bot_management.get_root_key(u'bot2')
        self.assertEqual(1, len(task_queues.get_queues(bot1_root_key)))
        self.assertEqual(1, len(task_queues.get_queues(bot2_root_key)))

        request2 = _gen_request(properties=_gen_properties(
            dimensions={
                u'pool': [u'default'],
                u'os': [u'v1'],
                u'gpu': [u'nv|amd'],
            }))
        task_queues.assert_task_async(request2).get_result()
        self.assertEqual(1, len(payloads))
        f = task_queues.rebuild_task_cache_async(payloads[-1])
        self.assertEqual(True, f.get_result())
        payloads.pop()

        # Only bot1 can handle |request2|
        self.assert_count(3, task_queues.BotTaskDimensions)
        self.assert_count(2, task_queues.TaskDimensions)
        self.assertEqual(2, len(task_queues.get_queues(bot1_root_key)))
        self.assertEqual(1, len(task_queues.get_queues(bot2_root_key)))
 def post(self):
     f = task_queues.rebuild_task_cache_async(self.request.body)
     if not f.get_result():
         # The task likely failed due to DB transaction contention,
         # so we can reply that the service has had too many requests (429).
         # Using a 400-level response also prevents failures here from causing
         # unactionable alerts due to a high rate of 500s.
         self.response.set_status(429, 'Need to retry')
    def test_or_dimensions_same_hash(self):
        self.mock_now(datetime.datetime(2020, 1, 2, 3, 4, 5))
        self.assertEqual(
            0, _assert_bot(bot_id=u'bot1', dimensions={u'os': [u'v1']}))
        self.assertEqual(
            0, _assert_bot(bot_id=u'bot2', dimensions={u'os': [u'v2']}))
        self.assertEqual(
            0, _assert_bot(bot_id=u'bot3', dimensions={u'os': [u'v3']}))

        payloads = self._mock_enqueue_task_async_for_rebuild_task_cache()
        # Both requests should have the same dimension_hash
        request1 = _gen_request(properties=_gen_properties(dimensions={
            u'pool': [u'default'],
            u'os': [u'v1|v2|v3'],
        }))
        request2 = _gen_request(properties=_gen_properties(dimensions={
            u'pool': [u'default'],
            u'os': [u'v3|v2|v1'],
        }))
        task_queues.assert_task_async(request1).get_result()
        task_queues.assert_task_async(request2).get_result()
        self.assertEqual(2, len(payloads))
        while payloads:
            f = task_queues.rebuild_task_cache_async(payloads[-1])
            self.assertEqual(True, f.get_result())
            payloads.pop()

        self.assert_count(3, task_queues.BotDimensions)
        self.assert_count(3, task_queues.BotTaskDimensions)
        self.assert_count(1, task_queues.TaskDimensions)
        self.assertEqual(3, len(task_queues.TaskDimensions.query().get().sets))
        bot1_root_key = bot_management.get_root_key(u'bot1')
        bot2_root_key = bot_management.get_root_key(u'bot2')
        bot3_root_key = bot_management.get_root_key(u'bot3')
        self.assertEqual(1, len(task_queues.get_queues(bot1_root_key)))
        self.assertEqual(1, len(task_queues.get_queues(bot2_root_key)))
        self.assertEqual(1, len(task_queues.get_queues(bot3_root_key)))
 def _enqueue_task_async(self, url, queue_name, payload):
   if queue_name == 'rebuild-task-cache':
     return task_queues.rebuild_task_cache_async(payload)
   self.fail(url)
    def test_rebuild_task_cache_async(self):
        # Assert that expiration works.
        now = datetime.datetime(2010, 1, 2, 3, 4, 5)
        self.mock_now(now)

        # We want _yield_BotTaskDimensions_keys() to return multiple
        # BotTaskDimensions ndb.Key to confirm that the inner loops work. This
        # requires a few bots.
        _assert_bot(bot_id=u'bot1')
        _assert_bot(bot_id=u'bot2')
        _assert_bot(bot_id=u'bot3')
        bot_root_key = bot_management.get_root_key(u'bot1')
        self.assertEqual(0, task_queues.BotTaskDimensions.query().count())
        self.assertEqual(0, task_queues.TaskDimensions.query().count())

        # Intentionally force the code to throttle the number of concurrent RPCs,
        # otherwise the inner loops wouldn't be reached with less than 50 bots, and
        # testing with 50 bots, would make the unit test slow.
        self.mock(task_queues, '_CAP_FUTURES_LIMIT', 1)

        payloads = self._mock_enqueue_task_async_for_rebuild_task_cache()

        # The equivalent of self._assert_task(tasks=1) except that we snapshot the
        # payload.
        # Trigger multiple task queues to go deeper in the code.
        request_1 = _gen_request(properties=_gen_properties(dimensions={
            u'cpu': [u'x86-64'],
            u'pool': [u'default'],
        }))
        task_queues.assert_task_async(request_1).get_result()
        self.assertEqual(1, len(payloads))
        f = task_queues.rebuild_task_cache_async(payloads[-1])
        self.assertEqual(True, f.get_result())
        self.assertEqual(3, task_queues.BotTaskDimensions.query().count())
        self.assertEqual(1, task_queues.TaskDimensions.query().count())
        self.assertEqual(60, request_1.expiration_secs)
        expected = now + task_queues._EXTEND_VALIDITY + datetime.timedelta(
            seconds=request_1.expiration_secs)
        self.assertEqual(
            expected,
            task_queues.TaskDimensions.query().get().valid_until_ts)

        request_2 = _gen_request(properties=_gen_properties(
            dimensions={
                u'os': [u'Ubuntu-16.04'],
                u'pool': [u'default'],
            }))
        task_queues.assert_task_async(request_2).get_result()
        self.assertEqual(2, len(payloads))
        f = task_queues.rebuild_task_cache_async(payloads[-1])
        self.assertEqual(True, f.get_result())
        self.assertEqual(6, task_queues.BotTaskDimensions.query().count())
        self.assertEqual(2, task_queues.TaskDimensions.query().count())
        self.assertEqual([227177418, 1843498234],
                         task_queues.get_queues(bot_root_key))
        memcache.flush_all()
        self.assertEqual([227177418, 1843498234],
                         task_queues.get_queues(bot_root_key))

        # Now expire the two TaskDimensions, one at a time, and rebuild the task
        # queue.
        offset = (task_queues._EXTEND_VALIDITY + datetime.timedelta(
            seconds=request_1.expiration_secs)).total_seconds() + 1
        self.mock_now(now, offset)
        f = task_queues.rebuild_task_cache_async(payloads[0])
        self.assertEqual(True, f.get_result())
        self.assertEqual(6, task_queues.BotTaskDimensions.query().count())
        self.assertEqual(2, task_queues.TaskDimensions.query().count())
        self.assertEqual([227177418, 1843498234],
                         task_queues.get_queues(bot_root_key))
        # Observe the effect of memcache. See comment in get_queues().
        memcache.flush_all()
        self.assertEqual([], task_queues.get_queues(bot_root_key))

        # Re-running still do not delete TaskDimensions because they are kept until
        # _KEEP_DEAD.
        f = task_queues.rebuild_task_cache_async(payloads[1])
        self.assertEqual(True, f.get_result())
        self.assertEqual(6, task_queues.BotTaskDimensions.query().count())
        self.assertEqual(2, task_queues.TaskDimensions.query().count())
        self.assertEqual([], task_queues.get_queues(bot_root_key))

        # Get past _KEEP_DEAD.
        offset = (task_queues._EXTEND_VALIDITY + task_queues._KEEP_DEAD +
                  datetime.timedelta(seconds=request_1.expiration_secs)
                  ).total_seconds() + 1
        self.mock_now(now, offset)
        self.assertEqual([], task_queues.get_queues(bot_root_key))
        f = task_queues.rebuild_task_cache_async(payloads[0])
        self.assertEqual(True, f.get_result())
        self.assertEqual(6, task_queues.BotTaskDimensions.query().count())
        self.assertEqual(1, task_queues.TaskDimensions.query().count())
        self.assertEqual([], task_queues.get_queues(bot_root_key))