docstr: target: test scale queryNode
  method: 1.deploy milvus cluster with 1 queryNode
          2.prepare work (connect, create, insert, index and load)
          3.continuously search (daemon thread)
          4.expand queryNode from 2 to 5
          5.continuously insert new data (daemon thread)
          6.shrink queryNode from 5 to 3
  expected: Verify milvus remains healthy and search successfully during scale
docstr_tok: ["target",":","test","scale","queryNode","method",":","1.deploy","milvus","cluster","with","1","queryNode","2.prepare","work","(","connect",",","create",",","insert",",","index","and","load",")","3.continuously","search","(","daemon","thread",")","4.expand","queryNode","from","2","to","5","5.continuously","insert","new","data","(","daemon","thread",")","6.shrink","queryNode","from","5","to","3","expected",":","Verify","milvus","remains","healthy","and","search","successfully","during","scale"]
code: def test_scale_query_node(self, host):
      """
      target: test scale queryNode
      method: 1.deploy milvus cluster with 1 queryNode
              2.prepare work (connect, create, insert, index and load)
              3.continuously search (daemon thread)
              4.expand queryNode from 2 to 5
              5.continuously insert new data (daemon thread)
              6.shrink queryNode from 5 to 3
      expected: Verify milvus remains healthy and search successfully during scale
      """
      release_name = "scale-query"
      image_tag = get_latest_tag()
      image = f"{constants.IMAGE_REPOSITORY}:{image_tag}"
      query_config = {
          "metadata.namespace": constants.NAMESPACE,
          "spec.mode": "cluster",
          "metadata.name": release_name,
          "spec.components.image": image,
          "spec.components.proxy.serviceType": "LoadBalancer",
          "spec.components.queryNode.replicas": 1,
          "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:
          # connect
          connections.add_connection(
              default={"host": host, "port": 19530}
          )
          connections.connect(alias="default")
  
          # create
          c_name = cf.gen_unique_str("scale_query")
          # c_name = 'scale_query_DymS7kI4'
          collection_w = ApiCollectionWrapper()
          utility_w = ApiUtilityWrapper()
          collection_w.init_collection(
              name=c_name, schema=cf.gen_default_collection_schema()
          )
  
          # insert two segments
          for i in range(30):
              df = cf.gen_default_dataframe_data(nb)
              collection_w.insert(df)
              log.debug(collection_w.num_entities)
  
          # create index
          collection_w.create_index(
              ct.default_float_vec_field_name,
              default_index_params,
              timeout=60,
          )
          assert collection_w.has_index()[0]
          assert collection_w.index()[0] == Index(
              collection_w.collection,
              ct.default_float_vec_field_name,
              default_index_params,
          )
  
          # load
          collection_w.load()
  
          # scale queryNode to 5
          mic.upgrade(
              release_name,
              {"spec.components.queryNode.replicas": 5},
              constants.NAMESPACE,
          )
  
          @counter
          def do_search():
              """do search"""
              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,
                  check_task=CheckTasks.check_nothing,
              )
              assert len(search_res) == 1
              return search_res, is_succ
  
          def loop_search():
              """continuously search"""
              while True:
                  do_search()
  
          threading.Thread(
              target=loop_search, args=(), daemon=True
          ).start()
  
          # wait new QN running, continuously insert
          mic.wait_for_healthy(release_name, constants.NAMESPACE)
          wait_pods_ready(
              constants.NAMESPACE,
              f"app.kubernetes.io/instance={release_name}",
          )
  
          # verify load balance
          verify_load_balance(c_name, host=host)
  
          @counter
          def do_insert():
              """do insert"""
              return collection_w.insert(
                  cf.gen_default_dataframe_data(1000),
                  check_task=CheckTasks.check_nothing,
              )
  
          def loop_insert():
              """loop insert"""
              while True:
                  do_insert()
  
          threading.Thread(
              target=loop_insert, args=(), daemon=True
          ).start()
  
          log.debug(collection_w.num_entities)
          time.sleep(20)
          log.debug("Expand querynode test finished")
  
          mic.upgrade(
              release_name,
              {"spec.components.queryNode.replicas": 3},
              constants.NAMESPACE,
          )
          mic.wait_for_healthy(release_name, constants.NAMESPACE)
          wait_pods_ready(
              constants.NAMESPACE,
              f"app.kubernetes.io/instance={release_name}",
          )
  
          log.debug(collection_w.num_entities)
          time.sleep(60)
          scale_common.check_succ_rate(do_search)
          scale_common.check_succ_rate(do_insert)
          log.debug("Shrink querynode test finished")
  
      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 1 for calling the TestQueryNodeScale obj's underlying member method code with example usage: obj.test_scale_query_node(host) and returns: search_res,  is_succ, collection_w