class_docstr: Utility to test BatchingNodeProvider.
docstr: Simulates an autoscaler update with multiple terminate and create calls.
  
  Calls non_terminated_nodes, then create/terminate nodes, then post_process.
  
  Args:
      create_node_requests (List[Tuple(str, int)]): List of pairs
          (node type, count). Each pair is used in a create_node call that
          creates count nodes of the node type.
      terminate_nodes_requests (List[Tuple(str, int)]): List of pairs
          (node type, count). Each pair is used in a terminate_nodes call
          that terminates up to count nodes of the node type.
      safe_to_scale_flag (bool): Passed to the node provider to determine  # noqa
          where provider.safe_to_scale() evaluates to True or False.
docstr_tok: ["Simulates","an","autoscaler","update","with","multiple","terminate","and","create","calls",".","Calls","non_terminated_nodes",",","then","create\/terminate","nodes",",","then","post_process",".","Args",":","create_node_requests","(","List","[","Tuple","(","str",",","int",")","]",")",":","List","of","pairs","(","node","type",",","count",")",".","Each","pair","is","used","in","a","create_node","call","that","creates","count","nodes","of","the","node","type",".","terminate_nodes_requests","(","List","[","Tuple","(","str",",","int",")","]",")",":","List","of","pairs","(","node","type",",","count",")",".","Each","pair","is","used","in","a","terminate_nodes","call","that","terminates","up","to","count","nodes","of","the","node","type",".","safe_to_scale_flag","(","bool",")",":","Passed","to","the","node","provider","to","determine","#","noqa","where","provider.safe_to_scale","(",")","evaluates","to","True","or","False","."]
code: def update(
      self,
      create_node_requests,
      terminate_nodes_requests,
      safe_to_scale_flag,
  ):
      """Simulates an autoscaler update with multiple terminate and create calls.
  
      Calls non_terminated_nodes, then create/terminate nodes, then post_process.
  
      Args:
          create_node_requests (List[Tuple(str, int)]): List of pairs
              (node type, count). Each pair is used in a create_node call that
              creates count nodes of the node type.
          terminate_nodes_requests (List[Tuple(str, int)]): List of pairs
              (node type, count). Each pair is used in a terminate_nodes call
              that terminates up to count nodes of the node type.
          safe_to_scale_flag (bool): Passed to the node provider to determine  # noqa
              where provider.safe_to_scale() evaluates to True or False.
      """
      self.node_provider.safe_to_scale_flag = safe_to_scale_flag
  
      # Call non_terminated_nodes to refresh internal caches.
      # Also validate node provider state.
      self.validate_non_terminated_nodes()
  
      # Abort if it's not safe to scale.
      # This behavior is tested in the context of an actual autoscaler update in
      # test_autoscaler:test_safe_to_scale.
      if not self.node_provider.safe_to_scale():
          return
      # Terminate some nodes.
      # Set to track nodes marked for termination during the update.
      to_terminate_this_update = set()
      for node_type, count in terminate_nodes_requests:
          # Each iteration makes a provider.terminate_nodes call.
          # Terminate "count" nodes of the given node_type.
          # If count is greater than the number of remaining nodes of the type,
          # terminate all of the nodes of the type.
          to_terminate_this_request = []
          for node in self.node_provider._node_data_dict:
              if len(to_terminate_this_request) >= count:
                  break
              if (
                  self.node_provider.node_tags(node)[
                      TAG_RAY_USER_NODE_TYPE
                  ]
                  != node_type
              ):
                  continue
              if node in to_terminate_this_update:
                  continue
              to_terminate_this_update.add(node)
              to_terminate_this_request.append(node)
          self.node_provider.terminate_nodes(to_terminate_this_request)
          self.expected_node_counts[node_type] -= len(
              to_terminate_this_request
          )
          # else: the scale request will not be submitted.
  
      # Create some nodes.
      for node_type, count in create_node_requests:
          self.node_provider.create_node(
              node_config={},
              tags={TAG_RAY_USER_NODE_TYPE: node_type},
              count=count,
          )
          self.expected_node_counts[node_type] += count
          # else: the scale request will not be submitted.
  
      # Scale change is needed exactly when there's something to create or terminate.
      assert self.node_provider.scale_change_needed is bool(
          create_node_requests or terminate_nodes_requests
      )
  
      # Submit the scale request.
      self.node_provider.post_process()
      # Expect a scale request to be submitted iff we called create or terminate
      # at least one.
      if create_node_requests or terminate_nodes_requests:
          self.expected_scale_request_submitted_count += 1
  
imports: from copy import copy
  from uuid import uuid4
  import os
  import random
  import sys
  from typing import Any, Dict
  from collections import defaultdict
  import pytest
  from ray.autoscaler.batching_node_provider import BatchingNodeProvider, NodeData, ScaleRequest
  from ray.autoscaler._private.util import NodeID, NodeType
  from ray.autoscaler.tags import STATUS_UP_TO_DATE, TAG_RAY_USER_NODE_TYPE, TAG_RAY_NODE_KIND, TAG_RAY_NODE_STATUS, NODE_KIND_HEAD, NODE_KIND_WORKER
  from ray.autoscaler._private.constants import DISABLE_LAUNCH_CONFIG_CHECK_KEY, DISABLE_NODE_UPDATERS_KEY, FOREGROUND_NODE_LAUNCH_KEY
label_desc: Use image node_id 2 for calling the BatchingNodeProviderTester obj's underlying member method code with example usage: obj.update(create_node_requests, terminate_nodes_requests, safe_to_scale_flag) without return types