def testDeleteRequest(self): """Check that we can efficiently destroy a single flow request.""" session_id = rdfvalue.SessionID(flow_name="test3") request = rdf_flow_runner.RequestState( id=1, client_id=test_lib.TEST_CLIENT_ID, next_state="TestState", session_id=session_id) with queue_manager.QueueManager(token=self.token) as manager: manager.QueueRequest(request) manager.QueueResponse( rdf_flows.GrrMessage(session_id=session_id, request_id=1, response_id=1)) # Check the request and responses are there. all_requests = list(manager.FetchRequestsAndResponses(session_id)) self.assertEqual(len(all_requests), 1) self.assertEqual(all_requests[0][0], request) with queue_manager.QueueManager(token=self.token) as manager: manager.DeleteRequest(request) all_requests = list(manager.FetchRequestsAndResponses(session_id)) self.assertEqual(len(all_requests), 0)
def _StopLegacy(self, reason=None): super(GenericHunt, self).Stop(reason=reason) started_flows = grr_collections.RDFUrnCollection( self.started_flows_collection_urn) num_terminated_flows = 0 self.Log("Hunt stop. Terminating all the started flows.") # Delete hunt flows states. for flows_batch in collection.Batch(started_flows, self.__class__.STOP_BATCH_SIZE): with queue_manager.QueueManager(token=self.token) as manager: manager.MultiDestroyFlowStates(flows_batch) with data_store.DB.GetMutationPool() as mutation_pool: for f in flows_batch: flow.GRRFlow.MarkForTermination( f, reason="Parent hunt stopped.", mutation_pool=mutation_pool) num_terminated_flows += len(flows_batch) # Delete hunt's requests and responses to ensure no more # processing is going to occur. with queue_manager.QueueManager(token=self.token) as manager: manager.DestroyFlowStates(self.session_id) self.Log("%d flows terminated.", num_terminated_flows)
def testUsesFrozenTimestampWhenDeletingAndFetchingNotifications(self): # When used in "with" statement QueueManager uses the frozen timestamp # when fetching and deleting data. Test that if we have 2 managers # created at different times, they will behave correctly when dealing # with notifications for the same session ids. I.e. older queue_manager # will only "see" it's own notification and younger queue_manager will # "see" both. with queue_manager.QueueManager(token=self.token) as manager1: manager1.QueueNotification( session_id=rdfvalue.SessionID( base="aff4:/hunts", queue=queues.HUNTS, flow_name="123456")) manager1.Flush() self._current_mock_time += 10 with queue_manager.QueueManager(token=self.token) as manager2: manager2.QueueNotification( session_id=rdfvalue.SessionID( base="aff4:/hunts", queue=queues.HUNTS, flow_name="123456")) manager2.Flush() self.assertEqual( len(manager1.GetNotificationsForAllShards(queues.HUNTS)), 1) self.assertEqual( len(manager2.GetNotificationsForAllShards(queues.HUNTS)), 1) manager1.DeleteNotification( rdfvalue.SessionID( base="aff4:/hunts", queue=queues.HUNTS, flow_name="123456")) self.assertEqual( len(manager1.GetNotificationsForAllShards(queues.HUNTS)), 0) self.assertEqual( len(manager2.GetNotificationsForAllShards(queues.HUNTS)), 1)
def DrainTaskSchedulerQueueForClient(self, client, max_count=None): """Drains the client's Task Scheduler queue. 1) Get all messages in the client queue. 2) Sort these into a set of session_ids. 3) Use data_store.DB.ResolvePrefix() to query all requests. 4) Delete all responses for retransmitted messages (if needed). Args: client: The ClientURN object specifying this client. max_count: The maximum number of messages we will issue for the client. If not given, uses self.max_queue_size . Returns: The tasks respresenting the messages returned. If we can not send them, we can reschedule them for later. """ if max_count is None: max_count = self.max_queue_size if max_count <= 0: return [] client = rdf_client.ClientURN(client) start_time = time.time() # Drain the queue for this client new_tasks = queue_manager.QueueManager(token=self.token).QueryAndOwn( queue=client.Queue(), limit=max_count, lease_seconds=self.message_expiry_time) initial_ttl = rdf_flows.GrrMessage().task_ttl check_before_sending = [] result = [] for task in new_tasks: if task.task_ttl < initial_ttl - 1: # This message has been leased before. check_before_sending.append(task) else: result.append(task) if check_before_sending: with queue_manager.QueueManager(token=self.token) as manager: status_found = manager.MultiCheckStatus(check_before_sending) # All messages that don't have a status yet should be sent again. for task in check_before_sending: if task not in status_found: result.append(task) else: manager.DeQueueClientRequest(task) stats.STATS.IncrementCounter("grr_messages_sent", len(result)) if result: logging.debug("Drained %d messages for %s in %s seconds.", len(result), client, time.time() - start_time) return result
def testNotificationRequeueing(self): with test_lib.ConfigOverrider({"Worker.queue_shards": 1}): session_id = rdfvalue.SessionID(base="aff4:/testflows", queue=queues.HUNTS, flow_name="123") with test_lib.FakeTime(1000): # Schedule a notification. with queue_manager.QueueManager(token=self.token) as manager: manager.QueueNotification(session_id=session_id) with test_lib.FakeTime(1100): with queue_manager.QueueManager(token=self.token) as manager: notifications = manager.GetNotifications(queues.HUNTS) self.assertEqual(len(notifications), 1) # This notification was first queued and last queued at time 1000. notification = notifications[0] self.assertEqual( notification.timestamp.AsSecondsSinceEpoch(), 1000) self.assertEqual( notification.first_queued.AsSecondsSinceEpoch(), 1000) # Now requeue the same notification. manager.DeleteNotification(session_id) manager.QueueNotification(notification) with test_lib.FakeTime(1200): with queue_manager.QueueManager(token=self.token) as manager: notifications = manager.GetNotifications(queues.HUNTS) self.assertEqual(len(notifications), 1) notification = notifications[0] # Now the last queue time is 1100, the first queue time is still 1000. self.assertEqual( notification.timestamp.AsSecondsSinceEpoch(), 1100) self.assertEqual( notification.first_queued.AsSecondsSinceEpoch(), 1000) # Again requeue the same notification. manager.DeleteNotification(session_id) manager.QueueNotification(notification) expired = 1000 + queue_manager.QueueManager.notification_expiry_time with test_lib.FakeTime(expired): with queue_manager.QueueManager(token=self.token) as manager: notifications = manager.GetNotifications(queues.HUNTS) self.assertEqual(len(notifications), 1) # Again requeue the notification, this time it should be dropped. manager.DeleteNotification(session_id) manager.QueueNotification(notifications[0]) with queue_manager.QueueManager(token=self.token) as manager: notifications = manager.GetNotifications(queues.HUNTS) self.assertEqual(len(notifications), 0)
def testDestroyFlowStates(self): """Check that we can efficiently destroy the flow's request queues.""" session_id = rdfvalue.SessionID(flow_name="test2") request = rdf_flow_runner.RequestState( id=1, client_id=test_lib.TEST_CLIENT_ID, next_state="TestState", session_id=session_id) with queue_manager.QueueManager(token=self.token) as manager: manager.QueueRequest(request) manager.QueueResponse( rdf_flows.GrrMessage(request_id=1, response_id=1, session_id=session_id)) # Check the request and responses are there. all_requests = list(manager.FetchRequestsAndResponses(session_id)) self.assertEqual(len(all_requests), 1) self.assertEqual(all_requests[0][0], request) # Read the response directly. responses = data_store.DB.ReadResponsesForRequestId(session_id, 1) self.assertEqual(len(responses), 1) response = responses[0] self.assertEqual(response.request_id, 1) self.assertEqual(response.response_id, 1) self.assertEqual(response.session_id, session_id) with queue_manager.QueueManager(token=self.token) as manager: manager.DestroyFlowStates(session_id) all_requests = list(manager.FetchRequestsAndResponses(session_id)) self.assertEqual(len(all_requests), 0) # Check that the response is gone. responses = data_store.DB.ReadResponsesForRequestId(session_id, 1) self.assertEqual(len(responses), 0) # Ensure the rows are gone from the data store. Some data stores # don't store the queues in that way but there is no harm in # checking. self.assertEqual( data_store.DB.ResolveRow(session_id.Add("state/request:00000001")), []) self.assertEqual(data_store.DB.ResolveRow(session_id.Add("state")), [])
def Run(self): client_id = self.SetupClient(0) with test_lib.FakeTime(42): flow_urn = flow.StartAFF4Flow( flow_name=processes.ListProcesses.__name__, client_id=client_id, token=self.token) test_process = client_test_lib.MockWindowsProcess( name="test_process") with utils.Stubber(psutil, "Process", lambda: test_process): mock = flow_test_lib.MockClient(client_id, None, token=self.token) while mock.Next(): pass replace = {flow_urn.Basename(): "W:ABCDEF"} manager = queue_manager.QueueManager(token=self.token) requests_responses = manager.FetchRequestsAndResponses(flow_urn) for request, responses in requests_responses: replace[str(request.request.task_id)] = "42" for response in responses: replace[str(response.task_id)] = "42" self.Check("ListFlowRequests", args=flow_plugin.ApiListFlowRequestsArgs( client_id=client_id.Basename(), flow_id=flow_urn.Basename()), replace=replace)
def ExpireRules(self): """Removes any rules with an expiration date in the past.""" rules = self.Get(self.Schema.RULES) new_rules = self.Schema.RULES() now = time.time() * 1e6 expired_session_ids = set() for rule in rules: if rule.expires > now: new_rules.Append(rule) else: for action in rule.actions: if action.hunt_id: expired_session_ids.add(action.hunt_id) if expired_session_ids: with data_store.DB.GetMutationPool() as pool: # Notify the worker to mark this hunt as terminated. manager = queue_manager.QueueManager(token=self.token) manager.MultiNotifyQueue([ rdf_flows.GrrNotification(session_id=session_id) for session_id in expired_session_ids ], mutation_pool=pool) if len(new_rules) < len(rules): self.Set(self.Schema.RULES, new_rules) self.Flush()
def Next(self): """Grab tasks for us from the server's queue.""" with queue_manager.QueueManager(token=self.token) as manager: request_tasks = manager.QueryAndOwn( self.client_id.Queue(), limit=1, lease_seconds=10000) request_tasks.extend(self._mock_task_queue) self._mock_task_queue[:] = [] # Clear the referenced list. for message in request_tasks: try: responses = self.client_mock.HandleMessage(message) logging.info("Called client action %s generating %s responses", message.name, len(responses) + 1) except Exception as e: # pylint: disable=broad-except logging.exception("Error %s occurred in client", e) responses = [ self.client_mock.GenerateStatusMessage( message, 1, status="GENERIC_ERROR") ] # Now insert those on the flow state queue for response in responses: self.PushToStateQueue(manager, response) # Additionally schedule a task for the worker manager.QueueNotification(session_id=message.session_id) return len(request_tasks)
def testReceiveMessageListFleetspeak(self): fsd = fs_frontend_tool.GRRFSServer() grr_client_nr = 0xab grr_client_id_urn = self.SetupClient(grr_client_nr) flow_obj = self.FlowSetup(flow_test_lib.FlowOrderTest.__name__, grr_client_id_urn) num_msgs = 9 session_id = flow_obj.session_id messages = [ rdf_flows.GrrMessage(request_id=1, response_id=i, session_id=session_id, payload=rdfvalue.RDFInteger(i)) for i in range(1, num_msgs + 1) ] fs_client_id = b"\x10\x00\x00\x00\x00\x00\x00\xab" # fs_client_id should be equivalent to grr_client_id_urn self.assertEqual( fs_client_id, fleetspeak_utils.GRRIDToFleetspeakID(grr_client_id_urn.Basename())) message_list = rdf_flows.PackedMessageList() communicator.Communicator.EncodeMessageList( rdf_flows.MessageList(job=messages), message_list) fs_message = fs_common_pb2.Message(message_type="MessageList", source=fs_common_pb2.Address( client_id=fs_client_id, service_name=FS_SERVICE_NAME)) fs_message.data.Pack(message_list.AsPrimitiveProto()) fsd.Process(fs_message, None) # Make sure the task is still on the client queue manager = queue_manager.QueueManager(token=self.token) tasks_on_client_queue = manager.Query(grr_client_id_urn, 100) self.assertEqual(len(tasks_on_client_queue), 1) want_messages = [message.Copy() for message in messages] for want_message in want_messages: # This is filled in by the frontend as soon as it gets the message. want_message.auth_state = ( rdf_flows.GrrMessage.AuthorizationState.AUTHENTICATED) want_message.source = grr_client_id_urn stored_messages = data_store.DB.ReadResponsesForRequestId( session_id, 1) self.assertEqual(len(stored_messages), len(want_messages)) stored_messages.sort(key=lambda m: m.response_id) # Check that messages were stored correctly for stored_message, want_message in itertools.izip( stored_messages, want_messages): stored_message.timestamp = None self.assertRDFValuesEqual(stored_message, want_message)
def Handle(self, args, token=None): flow_urn = args.flow_id.ResolveClientFlowURN(args.client_id, token=token) # Check if this flow really exists. try: aff4.FACTORY.Open(flow_urn, aff4_type=flow.GRRFlow, mode="r", token=token) except aff4.InstantiationError: raise FlowNotFoundError() result = ApiListFlowRequestsResult() manager = queue_manager.QueueManager(token=token) requests_responses = manager.FetchRequestsAndResponses(flow_urn) stop = None if args.count: stop = args.offset + args.count for request, responses in itertools.islice(requests_responses, args.offset, stop): if request.id == 0: continue # TODO(amoser): The request_id field should be an int. api_request = ApiFlowRequest( request_id=str(request.id), request_state=request) if responses: api_request.responses = responses result.items.append(api_request) return result
def _HandleAFF4(self, args, token=None): manager = queue_manager.QueueManager(token=token) result = ApiListClientActionRequestsResult() # Passing "limit" argument explicitly, as Query returns just 1 request # by default. for task in manager.Query(args.client_id.ToClientURN(), limit=self.__class__.REQUESTS_NUM_LIMIT): request = ApiClientActionRequest(leased_until=task.leased_until, session_id=task.session_id, client_action=task.name) if args.fetch_responses: res = [] for r in data_store.DB.ReadResponsesForRequestId( task.session_id, task.request_id): # Clear out some internal fields. r.task_id = None r.auth_state = None r.name = None res.append(r) request.responses = res result.items.append(request) return result
def testInspect(self): """Test the inspect UI.""" client_urn = self.SetupClient(0) client_id = client_urn.Basename() self.RequestAndGrantClientApproval(client_id) if data_store.RelationalDBEnabled(): flow_id = flow.StartFlow( client_id=client_id, flow_cls=flow_discovery.Interrogate) status = rdf_flow_objects.FlowStatus( client_id=client_id, flow_id=flow_id, request_id=1, response_id=2) data_store.REL_DB.WriteFlowResponses([status]) else: session_id = flow.StartAFF4Flow( client_id=client_urn, flow_name=flow_discovery.Interrogate.__name__, token=self.token) status = rdf_flows.GrrMessage( request_id=1, response_id=2, session_id=session_id, type=rdf_flows.GrrMessage.Type.STATUS, auth_state=rdf_flows.GrrMessage.AuthorizationState.AUTHENTICATED) with queue_manager.QueueManager(token=self.token) as manager: manager.QueueResponse(status) self.Open("/#/clients/%s/debug-requests" % client_id) # Check that the we can see both requests and responses. self.WaitUntil(self.IsTextPresent, "GetPlatformInfo") self.WaitUntil(self.IsTextPresent, "GetConfig") self.WaitUntil(self.IsTextPresent, "EnumerateInterfaces") self.WaitUntil(self.IsTextPresent, "STATUS")
def testNoNotificationRescheduling(self): """Test that no notifications are rescheduled when a flow raises.""" with test_lib.FakeTime(10000): flow_obj = self.FlowSetup("RaisingTestFlow") session_id = flow_obj.session_id flow_obj.Close() # Send the flow some messages. self.SendResponse(session_id, "Hello1", request_id=1) self.SendResponse(session_id, "Hello2", request_id=2) self.SendResponse(session_id, "Hello3", request_id=3) worker_obj = self._TestWorker() # Process all messages. with test_lib.SuppressLogs(): worker_obj.RunOnce() worker_obj.thread_pool.Join() delay = flow_runner.FlowRunner.notification_retry_interval with test_lib.FakeTime(10000 + 100 + delay): manager = queue_manager.QueueManager(token=self.token) self.assertFalse( manager.GetNotificationsForAllShards(session_id.Queue()))
def FinalizeProcessCompletedRequests(self, notification): # Delete kill notification as the flow got processed and is not # stuck. with queue_manager.QueueManager(token=self.token) as manager: manager.DeleteNotification( self.session_id, start=self.context.kill_timestamp, end=self.context.kill_timestamp) self.context.kill_timestamp = None # If a flow raises in one state, the remaining states will not # be processed. This is indistinguishable from an incomplete # state due to missing responses / status so we need to check # here if the flow is still running before rescheduling. if (self.IsRunning() and notification.last_status and (self.context.next_processed_request <= notification.last_status)): logging.debug("Had to reschedule a notification: %s", notification) # We have received a notification for a specific request but # could not process that request. This might be a race # condition in the data store so we reschedule the # notification in the future. delay = self.notification_retry_interval notification.ttl -= 1 if notification.ttl: manager.QueueNotification( notification, timestamp=notification.timestamp + delay)
def testNotificationsAreDeletedFromAllShards(self): manager = queue_manager.QueueManager(token=self.token) manager.QueueNotification(session_id=rdfvalue.SessionID( base="aff4:/hunts", queue=queues.HUNTS, flow_name="42")) manager.Flush() manager.QueueNotification(session_id=rdfvalue.SessionID( base="aff4:/hunts", queue=queues.HUNTS, flow_name="43")) manager.Flush() # There should be two notifications in two different shards. shards_with_data = 0 for _ in range(manager.num_notification_shards): shard_sessions = manager.GetNotifications(queues.HUNTS) if shard_sessions: shards_with_data += 1 self.assertEqual(len(shard_sessions), 1) self.assertEqual(shards_with_data, 2) # This should still work, as we delete notifications from all shards. manager.DeleteNotification( rdfvalue.SessionID(base="aff4:/hunts", queue=queues.HUNTS, flow_name="43")) manager.DeleteNotification( rdfvalue.SessionID(base="aff4:/hunts", queue=queues.HUNTS, flow_name="42")) for _ in range(manager.num_notification_shards): shard_sessions = manager.GetNotifications(queues.HUNTS) self.assertFalse(shard_sessions)
def testDelete(self): """Test that we can delete tasks.""" test_queue = rdfvalue.RDFURN("fooDelete") task = rdf_flows.GrrMessage(queue=test_queue, session_id="aff4:/Test", generate_task_id=True) with data_store.DB.GetMutationPool() as pool: manager = queue_manager.QueueManager(token=self.token) manager.Schedule([task], pool) # Get a lease on the task tasks = manager.QueryAndOwn(test_queue, lease_seconds=100, limit=100) self.assertEqual(len(tasks), 1) self.assertEqual(tasks[0].session_id, "aff4:/Test") # Now delete the task with data_store.DB.GetMutationPool() as pool: manager.Delete(test_queue, tasks, mutation_pool=pool) # Task is now deleted. tasks = manager.QueryAndOwn(test_queue, lease_seconds=100, limit=100) self.assertEqual(len(tasks), 0) # If we try to get another lease on it we should fail - even after # expiry time. self._current_mock_time += 1000 tasks = manager.QueryAndOwn(test_queue, lease_seconds=100, limit=100) self.assertEqual(len(tasks), 0)
def testCountsActualNumberOfCompletedResponsesWhenApplyingTheLimit(self): session_id = rdfvalue.SessionID(flow_name="test") # Now queue more requests and responses: with queue_manager.QueueManager(token=self.token) as manager: # Start with request 1 - leave request 1 un-responded to. for request_id in range(5): request = rdf_flow_runner.RequestState( id=request_id, client_id=test_lib.TEST_CLIENT_ID, next_state="TestState", session_id=session_id) manager.QueueRequest(request) # Don't queue any actual responses, just a status message with a # fake response_id. manager.QueueResponse( rdf_flows.GrrMessage( session_id=session_id, request_id=request_id, response_id=1000, type=rdf_flows.GrrMessage.Type.STATUS)) # Check that even though status message for every request indicates 1000 # responses, only the actual response count is used to apply the limit # when FetchCompletedResponses is called. completed_response = list( manager.FetchCompletedResponses(session_id, limit=5)) self.assertEqual(len(completed_response), 5) for i, (request, responses) in enumerate(completed_response): self.assertEqual(request.id, i) # Responses contain just the status message. self.assertEqual(len(responses), 1)
def testSchedule(self): """Test the ability to schedule a task.""" test_queue = rdfvalue.RDFURN("fooSchedule") task = rdf_flows.GrrMessage(queue=test_queue, task_ttl=5, session_id="aff4:/Test", generate_task_id=True) manager = queue_manager.QueueManager(token=self.token) with data_store.DB.GetMutationPool() as pool: manager.Schedule([task], pool) self.assertGreater(task.task_id, 0) self.assertGreater(task.task_id & 0xffffffff, 0) self.assertEqual( (int(self._current_mock_time * 1000) & 0xffffffff) << 32, task.task_id & 0x1fffffff00000000) self.assertEqual(task.task_ttl, 5) stored_tasks = data_store.DB.QueueQueryTasks(test_queue, limit=100000) self.assertEqual(len(stored_tasks), 1) stored_task = stored_tasks[0] self.assertGreater(stored_task.leased_until, 0) stored_task.leased_until = None self.assertRDFValuesEqual(stored_task, task) # Get a lease on the task tasks = manager.QueryAndOwn(test_queue, lease_seconds=100, limit=100) self.assertEqual(len(tasks), 1) self.assertEqual(tasks[0].task_ttl, 4) self.assertEqual(tasks[0].session_id, "aff4:/Test") # If we try to get another lease on it we should fail self._current_mock_time += 10 tasks = manager.QueryAndOwn(test_queue, lease_seconds=100, limit=100) self.assertEqual(len(tasks), 0) # However after 100 seconds this should work again self._current_mock_time += 110 tasks = manager.QueryAndOwn(test_queue, lease_seconds=100, limit=100) self.assertEqual(len(tasks), 1) self.assertEqual(tasks[0].task_ttl, 3) # Check now that after a few retransmits we drop the message for i in range(2, 0, -1): self._current_mock_time += 110 tasks = manager.QueryAndOwn(test_queue, lease_seconds=100) self.assertEqual(len(tasks), 1) self.assertEqual(tasks[0].task_ttl, i) # The task is now gone self._current_mock_time += 110 tasks = manager.QueryAndOwn(test_queue, lease_seconds=100) self.assertEqual(len(tasks), 0)
def testTaskRetransmissionsAreCorrectlyAccounted(self): test_queue = rdfvalue.RDFURN("fooSchedule") task = rdf_flows.GrrMessage(queue=test_queue, task_ttl=5, session_id="aff4:/Test", generate_task_id=True) manager = queue_manager.QueueManager(token=self.token) with data_store.DB.GetMutationPool() as pool: manager.Schedule([task], pool) # Get a lease on the task tasks = manager.QueryAndOwn(test_queue, lease_seconds=100, limit=100) self.assertEqual(len(tasks), 1) self.assertEqual(tasks[0].task_ttl, 4) self.assertEqual( stats.STATS.GetMetricValue("grr_task_retransmission_count"), self.retransmission_metric_value) # Get a lease on the task 100 seconds later self._current_mock_time += 110 tasks = manager.QueryAndOwn(test_queue, lease_seconds=100, limit=100) self.assertEqual(len(tasks), 1) self.assertEqual(tasks[0].task_ttl, 3) self.assertEqual( stats.STATS.GetMetricValue("grr_task_retransmission_count"), self.retransmission_metric_value + 1)
def CheckNotificationsDisappear(self, session_id): worker_obj = self._TestWorker() manager = queue_manager.QueueManager(token=self.token) notification = rdf_flows.GrrNotification(session_id=session_id) with data_store.DB.GetMutationPool() as pool: manager.NotifyQueue(notification, mutation_pool=pool) notifications = manager.GetNotifications(queues.FLOWS) # Check the notification is there. With multiple worker queue shards we can # get other notifications such as for audit event listeners, so we need to # filter out ours. notifications = [ x for x in notifications if x.session_id == session_id ] self.assertLen(notifications, 1) # Process all messages worker_obj.RunOnce() worker_obj.thread_pool.Join() notifications = manager.GetNotifications(queues.FLOWS) notifications = [ x for x in notifications if x.session_id == session_id ] # Check the notification is now gone. self.assertEmpty(notifications)
def testKillNotificationsScheduledForFlows(self): worker_obj = self._TestWorker() initial_time = rdfvalue.RDFDatetime.FromSecondsSinceEpoch(100) try: with test_lib.FakeTime(initial_time.AsSecondsSinceEpoch()): flow.StartAFF4Flow(flow_name=WorkerStuckableTestFlow.__name__, client_id=self.client_id, token=self.token, sync=False) # Process all messages worker_obj.RunOnce() # Wait until worker thread starts processing the flow. WorkerStuckableTestFlow.WaitUntilWorkerStartsProcessing() # Assert that there are no stuck notifications in the worker's # queue. with queue_manager.QueueManager(token=self.token) as manager: for queue in worker_obj.queues: notifications = manager.GetNotifications(queue) for n in notifications: self.assertFalse(n.in_progress) finally: # Release the semaphore so that worker thread unblocks and finishes # processing the flow. WorkerStuckableTestFlow.StopFlow() WorkerStuckableTestFlow.LetWorkerFinishProcessing() worker_obj.thread_pool.Join()
def testNoKillNotificationsScheduledForHunts(self): worker_obj = self._TestWorker() initial_time = rdfvalue.RDFDatetime.FromSecondsSinceEpoch(100) try: with test_lib.FakeTime(initial_time.AsSecondsSinceEpoch()): with implementation.StartHunt( hunt_name=WorkerStuckableHunt.__name__, client_rate=0, token=self.token) as hunt: hunt.GetRunner().Start() implementation.GRRHunt.StartClients(hunt.session_id, [self.client_id]) # Process all messages while worker_obj.RunOnce(): pass # Wait until worker thread starts processing the flow. WorkerStuckableHunt.WaitUntilWorkerStartsProcessing() # Assert that there are no stuck notifications in the worker's queue. with queue_manager.QueueManager(token=self.token) as manager: for queue in worker_obj.queues: notifications = manager.GetNotifications(queue) for n in notifications: self.assertFalse(n.in_progress) finally: # Release the semaphore so that worker thread unblocks and finishes # processing the flow. WorkerStuckableHunt.LetWorkerFinishProcessing() worker_obj.thread_pool.Join()
def Next(self): """Very simple emulator of the worker. We wake each flow in turn and run it. Returns: total number of flows still alive. Raises: RuntimeError: if the flow terminates with an error. """ with queue_manager.QueueManager(token=self.token) as manager: run_sessions = [] for queue in self.queues: notifications_available = manager.GetNotificationsForAllShards(queue) # Run all the flows until they are finished # Only sample one session at the time to force serialization of flows # after each state run. for notification in notifications_available[:1]: session_id = notification.session_id manager.DeleteNotification(session_id, end=notification.timestamp) run_sessions.append(session_id) # Handle well known flows here. flow_name = session_id.FlowName() if flow_name in self.well_known_flows: well_known_flow = self.well_known_flows[flow_name] with well_known_flow: responses = well_known_flow.FetchAndRemoveRequestsAndResponses( well_known_flow.well_known_session_id) well_known_flow.ProcessResponses(responses, self.pool) continue with aff4.FACTORY.OpenWithLock( session_id, token=self.token, blocking=False) as flow_obj: # Run it runner = flow_obj.GetRunner() cpu_used = runner.context.client_resources.cpu_usage user_cpu = self.cpu_user.next() system_cpu = self.cpu_system.next() network_bytes = self.network_bytes.next() cpu_used.user_cpu_time += user_cpu cpu_used.system_cpu_time += system_cpu runner.context.network_bytes_sent += network_bytes runner.ProcessCompletedRequests(notification, self.pool) if (self.check_flow_errors and isinstance(flow_obj, flow.GRRFlow) and runner.context.state == rdf_flow_runner.FlowContext.State.ERROR ): logging.exception("Flow terminated in state %s with an error: %s", runner.context.current_state, runner.context.backtrace) raise RuntimeError(runner.context.backtrace) return run_sessions
def testNotFirstShardNameHasIndexSuffix(self): manager = queue_manager.QueueManager(token=self.token) while True: shard = manager.GetNotificationShard(queues.HUNTS) if (manager.notification_shard_counters[str(queues.HUNTS)] % manager.num_notification_shards) == 1: break self.assertEqual(shard, queues.HUNTS.Add("1"))
def testFirstShardNameIsEqualToTheQueue(self): manager = queue_manager.QueueManager(token=self.token) while True: shard = manager.GetNotificationShard(queues.HUNTS) if (manager.notification_shard_counters[str(queues.HUNTS)] % manager.num_notification_shards) == 0: break self.assertEqual(shard, queues.HUNTS)
def HandleMessageBundles(self, request_comms, response_comms): """Processes a queue of messages as passed from the client. We basically dispatch all the GrrMessages in the queue to the task scheduler for backend processing. We then retrieve from the TS the messages destined for this client. Args: request_comms: A ClientCommunication rdfvalue with messages sent by the client. source should be set to the client CN. response_comms: A ClientCommunication rdfvalue of jobs destined to this client. Returns: tuple of (source, message_count) where message_count is the number of messages received from the client with common name source. """ messages, source, timestamp = self._communicator.DecodeMessages( request_comms) now = time.time() if messages: # Receive messages in line. self.ReceiveMessages(source, messages) # We send the client a maximum of self.max_queue_size messages required_count = max(0, self.max_queue_size - request_comms.queue_size) tasks = [] message_list = rdf_flows.MessageList() # Only give the client messages if we are able to receive them in a # reasonable time. if time.time() - now < 10: tasks = self.DrainTaskSchedulerQueueForClient( source, required_count) message_list.job = tasks # Encode the message_list in the response_comms using the same API version # the client used. try: self._communicator.EncodeMessages( message_list, response_comms, destination=source, timestamp=timestamp, api_version=request_comms.api_version) except communicator.UnknownClientCert: # We can not encode messages to the client yet because we do not have the # client certificate - return them to the queue so we can try again later. with data_store.DB.GetMutationPool() as pool: queue_manager.QueueManager(token=self.token).Schedule( tasks, pool) raise return source, len(messages)
def DeleteRuns(self, job, age=None, token=None): """Deletes flows initiated by the job that are older than specified.""" if age is None: raise ValueError("age can't be None") child_flows = list(job.ListChildren(age=age)) with queue_manager.QueueManager(token=token) as queuemanager: queuemanager.MultiDestroyFlowStates(child_flows) aff4.FACTORY.MultiDelete(child_flows, token=token)
def __init__(self, flow_obj, parent_runner=None, runner_args=None, token=None): """Constructor for the Flow Runner. Args: flow_obj: The flow object this runner will run states for. parent_runner: The parent runner of this runner. runner_args: A FlowRunnerArgs() instance containing initial values. If not specified, we use the runner_args from the flow_obj. token: An instance of access_control.ACLToken security token. """ self.token = token or flow_obj.token self.parent_runner = parent_runner # If we have a parent runner, we use its queue manager. if parent_runner is not None: self.queue_manager = parent_runner.queue_manager else: # Otherwise we use a new queue manager. self.queue_manager = queue_manager.QueueManager(token=self.token) self.queue_manager.FreezeTimestamp() self.queued_replies = [] self.outbound_lock = threading.Lock() self.flow_obj = flow_obj # Initialize from a new runner args proto. if runner_args is not None: self.runner_args = runner_args self.session_id = self.GetNewSessionID() self.flow_obj.urn = self.session_id # Flow state does not have a valid context, we need to create one. self.context = self.InitializeContext(runner_args) self.flow_obj.context = self.context self.context.session_id = self.session_id else: # Retrieve args from the flow object's context. The flow object is # responsible for storing our context, although they do not generally # access it directly. self.context = self.flow_obj.context self.runner_args = self.flow_obj.runner_args # Populate the flow object's urn with the session id. self.flow_obj.urn = self.session_id = self.context.session_id # Sent replies are cached so that they can be processed by output plugins # when the flow is saved. self.sent_replies = []
def DeleteOldRuns(self, job, cutoff_timestamp=None, token=None): """Deletes flows initiated by the job that are older than specified.""" if cutoff_timestamp is None: raise ValueError("cutoff_timestamp can't be None") child_flows = list(job.ListChildren(age=cutoff_timestamp)) with queue_manager.QueueManager(token=token) as queuemanager: queuemanager.MultiDestroyFlowStates(child_flows) aff4.FACTORY.MultiDelete(child_flows, token=token) return len(child_flows)