1+ #!/usr/bin/env python3
2+ # Copyright 2026 The Kubernetes Authors.
3+ #
4+ # Licensed under the Apache License, Version 2.0 (the "License");
5+ # you may not use this file except in compliance with the License.
6+ # You may obtain a copy of the License at
7+ #
8+ # http://www.apache.org/licenses/LICENSE-2.0
9+ #
10+ # Unless required by applicable law or agreed to in writing, software
11+ # distributed under the License is distributed on an "AS IS" BASIS,
12+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+ # See the License for the specific language governing permissions and
14+ # limitations under the License.
15+
16+ import os
17+ import shutil
18+ import subprocess
19+ import tempfile
20+ import sys
21+
22+ # Add the repository root to sys.path to allow importing from dev.*
23+ _repo_root = os .path .dirname (os .path .dirname (os .path .dirname (os .path .dirname (os .path .abspath (__file__ )))))
24+ if _repo_root not in sys .path :
25+ sys .path .insert (0 , _repo_root )
26+
27+ from dev .ci .shared .runner import TestRunner
28+
29+ def install_clusterloader2 (repo_root ):
30+ """Installs clusterloader2 if not present."""
31+ bin_dir = os .path .join (repo_root , "bin" )
32+ if not os .path .exists (bin_dir ):
33+ os .makedirs (bin_dir )
34+ cl2_path = os .path .join (bin_dir , "clusterloader2" )
35+ if os .path .exists (cl2_path ):
36+ return cl2_path
37+
38+ print ("Installing clusterloader2..." )
39+ with tempfile .TemporaryDirectory () as tmpdirname :
40+ subprocess .check_call (["git" , "clone" , "--depth" , "1" , "https://github.com/kubernetes/perf-tests.git" , tmpdirname ])
41+
42+ # Build from inside clusterloader2 directory as per README so go.mod is found
43+ build_dir = os .path .join (tmpdirname , "clusterloader2" )
44+ cmd = ["go" , "build" , "-o" , cl2_path , "./cmd/clusterloader.go" ]
45+ subprocess .check_call (cmd , cwd = build_dir )
46+ return cl2_path
47+
48+ class LoadTestRunner (TestRunner ):
49+ def __init__ (self ):
50+ super ().__init__ ("load-test" , "Invokes load-test in kind cluster and outputs a junit report in the ARTIFACTS dir" )
51+ self .parser .add_argument (
52+ "--replicas" ,
53+ dest = "replicas" ,
54+ help = "Number of replicas per namespace" ,
55+ type = int ,
56+ default = 5 ,
57+ )
58+ self .parser .add_argument (
59+ "--namespaces" ,
60+ dest = "namespaces" ,
61+ help = "Number of namespaces" ,
62+ type = int ,
63+ default = 1 ,
64+ )
65+ self .parser .add_argument (
66+ "--qps" ,
67+ dest = "qps" ,
68+ help = "QPS for creating objects" ,
69+ type = float ,
70+ default = 10 ,
71+ )
72+ self .parser .add_argument (
73+ "--namespace-prefix" ,
74+ dest = "namespace_prefix" ,
75+ help = "Prefix for namespaces" ,
76+ type = str ,
77+ default = "agent-sandbox" ,
78+ )
79+
80+ def setup_cluster (self , args ):
81+ return super ().setup_cluster (args , extra_push_images_args = ["--controller-only" ])
82+
83+ def run_tests (self , args ):
84+ cl2_path = install_clusterloader2 (self .repo_root )
85+ test_config = "agent-sandbox-load-test.yaml"
86+ kubeconfig = os .path .join (self .repo_root , "bin/KUBECONFIG" )
87+
88+ # Create overrides file with CLI arguments
89+ with tempfile .NamedTemporaryFile (mode = 'w' , delete = False ) as overrides_file :
90+ overrides_file .write (f"CL2_REPLICAS: { args .replicas } \n " )
91+ overrides_file .write (f"CL2_NAMESPACES: { args .namespaces } \n " )
92+ overrides_file .write (f"CL2_QPS: { args .qps } \n " )
93+ overrides_file .write (f"CL2_NAMESPACE_PREFIX: { args .namespace_prefix } \n " )
94+ overrides_path = overrides_file .name
95+
96+ try :
97+ # Run clusterloader2 from the load-test directory so relative paths in config work
98+ report_dir = os .path .join (self .repo_root , "bin" )
99+ cmd = [cl2_path , f"--testconfig={ test_config } " , f"--kubeconfig={ kubeconfig } " , f"--testoverrides={ overrides_path } " , "--provider=kind" , "--v=2" , f"--report-dir={ report_dir } " ]
100+ print (f"Running load test: { ' ' .join (cmd )} " )
101+ result = subprocess .run (cmd , cwd = os .path .join (self .repo_root , "dev/load-test" ))
102+ finally :
103+ if os .path .exists (overrides_path ):
104+ os .remove (overrides_path )
105+ # Cleanup kubeconfig and cl2 path for fresh runs
106+ if os .path .exists (kubeconfig ):
107+ os .remove (kubeconfig )
108+ if os .path .exists (cl2_path ):
109+ os .remove (cl2_path )
110+ return result
111+
112+ def copy_artifacts (self ):
113+ artifact_dir = os .getenv ("ARTIFACTS" )
114+ if artifact_dir :
115+ if os .path .exists (f"{ self .repo_root } /bin/junit.xml" ):
116+ shutil .copy (f"{ self .repo_root } /bin/junit.xml" , f"{ artifact_dir } /junit_load_test.xml" )
117+
118+ if __name__ == "__main__" :
119+ runner = LoadTestRunner ()
120+ runner .main ()
0 commit comments