docstr: target: test scale in cluster and querynode < replica
  method: 1.Deploy cluster with 3 querynodes
          2.Create and insert data, flush
          3.Load collection with 2 replica number
          4.Scale in querynode from 3 to 1 and query
          5.Scale out querynode from 1 back to 3
  expected: Verify search successfully after scale out
docstr_tok: ["target",":","test","scale","in","cluster","and","querynode","<","replica","method",":","1.Deploy","cluster","with","3","querynodes","2.Create","and","insert","data",",","flush","3.Load","collection","with","2","replica","number","4.Scale","in","querynode","from","3","to","1","and","query","5.Scale","out","querynode","from","1","back","to","3","expected",":","Verify","search","successfully","after","scale","out"]
code: def test_scale_in_query_node_less_than_replicas(self):
      """
      target: test scale in cluster and querynode < replica
      method: 1.Deploy cluster with 3 querynodes
              2.Create and insert data, flush
              3.Load collection with 2 replica number
              4.Scale in querynode from 3 to 1 and query
              5.Scale out querynode from 1 back to 3
      expected: Verify search successfully after scale out
      """
      release_name = "scale-in-query"
      image_tag = get_latest_tag()
      image = f"{constants.IMAGE_REPOSITORY}:{image_tag}"
      query_config = {
          "metadata.namespace": constants.NAMESPACE,
          "metadata.name": release_name,
          "spec.mode": "cluster",
          "spec.components.image": image,
          "spec.components.proxy.serviceType": "LoadBalancer",
          "spec.components.queryNode.replicas": 2,
          "spec.config.common.retentionDuration": 60,
      }
      mic = MilvusOperator()
      mic.install(query_config)
      if mic.wait_for_healthy(
          release_name, constants.NAMESPACE, timeout=1800
      ):
          host = mic.endpoint(release_name, constants.NAMESPACE).split(
              ":"
          )[0]
      else:
          raise MilvusException(message=f"Milvus healthy timeout 1800s")
      try:
          # prepare collection
          connections.connect("scale-in", host=host, port=19530)
          utility_w = ApiUtilityWrapper()
          collection_w = ApiCollectionWrapper()
          collection_w.init_collection(
              name=cf.gen_unique_str("scale_in"),
              schema=cf.gen_default_collection_schema(),
              using="scale-in",
          )
          collection_w.insert(cf.gen_default_dataframe_data())
          assert collection_w.num_entities == ct.default_nb
  
          # load multi replicas and search success
          collection_w.load(replica_number=2)
          search_res, is_succ = collection_w.search(
              cf.gen_vectors(1, ct.default_dim),
              ct.default_float_vec_field_name,
              ct.default_search_params,
              ct.default_limit,
          )
          assert len(search_res[0].ids) == ct.default_limit
          log.info("Search successfully after load with 2 replicas")
          log.debug(collection_w.get_replicas()[0])
          log.debug(
              utility_w.get_query_segment_info(
                  collection_w.name, using="scale-in"
              )
          )
  
          # scale in querynode from 2 to 1, less than replica number
          log.debug("Scale in querynode from 2 to 1")
          mic.upgrade(
              release_name,
              {"spec.components.queryNode.replicas": 1},
              constants.NAMESPACE,
          )
          mic.wait_for_healthy(release_name, constants.NAMESPACE)
          wait_pods_ready(
              constants.NAMESPACE,
              f"app.kubernetes.io/instance={release_name}",
          )
  
          # search and not assure success
          collection_w.search(
              cf.gen_vectors(1, ct.default_dim),
              ct.default_float_vec_field_name,
              ct.default_search_params,
              ct.default_limit,
              check_task=CheckTasks.check_nothing,
          )
          log.debug(
              collection_w.get_replicas(
                  check_task=CheckTasks.check_nothing
              )[0]
          )
  
          # scale querynode from 1 back to 2
          mic.upgrade(
              release_name,
              {"spec.components.queryNode.replicas": 2},
              constants.NAMESPACE,
          )
          mic.wait_for_healthy(release_name, constants.NAMESPACE)
          wait_pods_ready(
              constants.NAMESPACE,
              f"app.kubernetes.io/instance={release_name}",
          )
  
          # verify search success
          collection_w.search(
              cf.gen_vectors(1, ct.default_dim),
              ct.default_float_vec_field_name,
              ct.default_search_params,
              ct.default_limit,
          )
          # Verify replica info is correct
          replicas = collection_w.get_replicas()[0]
          assert len(replicas.groups) == 2
          for group in replicas.groups:
              assert len(group.group_nodes) == 1
          # Verify loaded segment info is correct
          seg_info = utility_w.get_query_segment_info(
              collection_w.name, using="scale-in"
          )[0]
          num_entities = 0
          for seg in seg_info:
              assert len(seg.nodeIds) == 2
              num_entities += seg.num_rows
          assert num_entities == ct.default_nb
  
      except Exception as e:
          raise Exception(str(e))
  
      finally:
          label = f"app.kubernetes.io/instance={release_name}"
          log.info("Start to export milvus pod logs")
          read_pod_log(
              namespace=constants.NAMESPACE,
              label_selector=label,
              release_name=release_name,
          )
          mic.uninstall(release_name, namespace=constants.NAMESPACE)
  
imports: import random
  import threading
  import time
  import pytest
  from base.collection_wrapper import ApiCollectionWrapper
  from base.utility_wrapper import ApiUtilityWrapper
  from common.common_type import CaseLabel, CheckTasks
  from common.milvus_sys import MilvusSys
  from customize.milvus_operator import MilvusOperator
  from common import common_func
  from common import common_type
  from scale import constants, scale_common
  from pymilvus import Index, connections, MilvusException
  from utils.util_log import test_log
  from utils.util_k8s import wait_pods_ready, read_pod_log
  from utils.util_pymilvus import get_latest_tag
  from utils.wrapper import counter
label_desc: Use image node_id 3 for calling the TestQueryNodeScale obj's underlying member method code with example usage: obj.test_scale_in_query_node_less_than_replicas() without return types