|
5 | 5 |
|
6 | 6 | import logging |
7 | 7 | import random |
| 8 | +import resource |
8 | 9 |
|
9 | 10 | from virttest import env_process |
10 | 11 | from virttest import error_context |
|
14 | 15 | LOG = logging.getLogger("avocado.test") |
15 | 16 |
|
16 | 17 |
|
| 18 | +def _set_host_nofile_limit(test, params): |
| 19 | + """Set host nofile soft limit for a large number of guest cases and return original limits.""" |
| 20 | + target_nofile = int(params.get_numeric("host_nofile_limit", 65536)) |
| 21 | + original_limits = resource.getrlimit(resource.RLIMIT_NOFILE) |
| 22 | + soft_limit, hard_limit = original_limits |
| 23 | + |
| 24 | + if hard_limit == resource.RLIM_INFINITY: |
| 25 | + new_soft_limit = target_nofile |
| 26 | + else: |
| 27 | + new_soft_limit = min(target_nofile, hard_limit) |
| 28 | + |
| 29 | + if soft_limit >= new_soft_limit: |
| 30 | + return original_limits |
| 31 | + |
| 32 | + resource.setrlimit(resource.RLIMIT_NOFILE, (new_soft_limit, hard_limit)) |
| 33 | + if new_soft_limit < target_nofile: |
| 34 | + test.log.warning( |
| 35 | + "host nofile hard limit is %s, case soft limit set to %s instead of %s", |
| 36 | + hard_limit, |
| 37 | + new_soft_limit, |
| 38 | + target_nofile, |
| 39 | + ) |
| 40 | + else: |
| 41 | + test.log.info("host nofile soft limit set to %s for this case", new_soft_limit) |
| 42 | + |
| 43 | + return original_limits |
| 44 | + |
| 45 | + |
| 46 | +def _prepare_auto_calc_guest_count(params): |
| 47 | + """Generate vm list for auto_calc_guest_count mode.""" |
| 48 | + guest_mem = int(params.get_numeric("mem", params.get_numeric("start_mem", 1024))) |
| 49 | + guest_count_mem = guest_mem + int( |
| 50 | + params.get_numeric("guest_count_mem_overhead", 0) |
| 51 | + ) |
| 52 | + host_usable_mem = int(utils_misc.get_usable_memory_size()) |
| 53 | + guest_num = host_usable_mem // guest_count_mem |
| 54 | + max_guest_count = params.get("max_guest_count") |
| 55 | + if max_guest_count: |
| 56 | + guest_num = min(guest_num, int(max_guest_count)) |
| 57 | + vm_names = ["vm%s" % idx for idx in range(1, guest_num + 1)] |
| 58 | + params["vms"] = " ".join(vm_names) |
| 59 | + return guest_count_mem, host_usable_mem, vm_names |
| 60 | + |
| 61 | + |
17 | 62 | def _calc_default_mem_series(params, vm_names): |
18 | 63 | """ |
19 | 64 | Calculate a default memory series based on the generator type. |
20 | 65 |
|
21 | 66 | Supported generators: |
22 | 67 | - linear: increments by mem_step from start_mem to memory_limit |
23 | 68 | - random_32g_window: random samples within sliding 32G windows |
| 69 | + Note: auto_calc_guest_count preparation is handled outside this function. |
24 | 70 | """ |
25 | 71 | # Supported: "linear" (default fallback) or "random_32g_window" (used by current cfg) |
26 | 72 | mem_generator = params.get("mem_generator", "linear") |
@@ -136,43 +182,61 @@ def run(test, params, env): |
136 | 182 |
|
137 | 183 | timeout = int(params.get_numeric("login_timeout", 240)) |
138 | 184 |
|
139 | | - vm_names = params.objects("vms") |
140 | | - if not vm_names: |
141 | | - test.cancel("No VMs configured for multi_vms_multi_boot") |
142 | | - |
143 | | - iteration_plan = _resolve_iteration_plan(params, vm_names) |
144 | | - |
145 | | - if not iteration_plan: |
146 | | - test.cancel("No valid iterations resolved for multi_vms_multi_boot") |
147 | | - |
148 | | - test.log.info("Total iterations: %s, VMs per iteration: %s", |
149 | | - len(iteration_plan), len(vm_names)) |
150 | | - |
151 | | - for iteration, vm_param_overrides in enumerate(iteration_plan, start=1): |
152 | | - started_vms = [] |
153 | | - try: |
154 | | - override_desc = ", ".join( |
155 | | - "%s(mem=%s)" % (vm_name, vm_param_overrides.get(vm_name, {}).get("mem", "default")) |
156 | | - for vm_name in vm_names |
157 | | - ) |
158 | | - error_context.context( |
159 | | - "Iteration %s/%s: %s" |
160 | | - % (iteration, len(iteration_plan), override_desc), |
161 | | - test.log.info, |
162 | | - ) |
163 | | - |
164 | | - for vm_name in vm_names: |
165 | | - vm_params = params.object_params(vm_name) |
166 | | - vm_params["start_vm"] = "yes" |
167 | | - for key, value in vm_param_overrides.get(vm_name, {}).items(): |
168 | | - vm_params[key] = value |
169 | | - env_process.preprocess_vm(test, vm_params, env, vm_name) |
170 | | - started_vms.append(env.get_vm(vm_name)) |
171 | | - |
172 | | - for vm in started_vms: |
173 | | - vm.verify_alive() |
174 | | - session = vm.wait_for_login(timeout=timeout) |
175 | | - session.close() |
176 | | - finally: |
177 | | - for vm in started_vms: |
178 | | - vm.destroy(gracefully=False) |
| 185 | + auto_calc_guest_count = params.get("auto_calc_guest_count", "no") == "yes" |
| 186 | + original_nofile_limits = None |
| 187 | + try: |
| 188 | + if auto_calc_guest_count: |
| 189 | + original_nofile_limits = _set_host_nofile_limit(test, params) |
| 190 | + |
| 191 | + vm_names = params.objects("vms") |
| 192 | + # If auto_calc_guest_count is disabled, vms must be explicitly configured |
| 193 | + if not vm_names and not auto_calc_guest_count: |
| 194 | + test.cancel("No VMs configured for multi_vms_multi_boot") |
| 195 | + # Validate auto_calc_guest_count requirements |
| 196 | + if auto_calc_guest_count: |
| 197 | + guest_count_mem, host_usable_mem, vm_names = _prepare_auto_calc_guest_count(params) |
| 198 | + if host_usable_mem < guest_count_mem: |
| 199 | + test.fail( |
| 200 | + "Not enough usable host memory for auto_calc_guest_count. " |
| 201 | + "required=%dM usable=%dM" % (guest_count_mem, host_usable_mem) |
| 202 | + ) |
| 203 | + |
| 204 | + iteration_plan = _resolve_iteration_plan(params, vm_names) |
| 205 | + |
| 206 | + if not iteration_plan: |
| 207 | + test.cancel("No valid iterations resolved for multi_vms_multi_boot") |
| 208 | + |
| 209 | + test.log.info("Total iterations: %s, VMs per iteration: %s", |
| 210 | + len(iteration_plan), len(vm_names)) |
| 211 | + |
| 212 | + for iteration, vm_param_overrides in enumerate(iteration_plan, start=1): |
| 213 | + started_vms = [] |
| 214 | + try: |
| 215 | + override_desc = ", ".join( |
| 216 | + "%s(mem=%s)" % (vm_name, vm_param_overrides.get(vm_name, {}).get("mem", "default")) |
| 217 | + for vm_name in vm_names |
| 218 | + ) |
| 219 | + error_context.context( |
| 220 | + "Iteration %s/%s: %s" |
| 221 | + % (iteration, len(iteration_plan), override_desc), |
| 222 | + test.log.info, |
| 223 | + ) |
| 224 | + |
| 225 | + for vm_name in vm_names: |
| 226 | + vm_params = params.object_params(vm_name) |
| 227 | + vm_params["start_vm"] = "yes" |
| 228 | + for key, value in vm_param_overrides.get(vm_name, {}).items(): |
| 229 | + vm_params[key] = value |
| 230 | + env_process.preprocess_vm(test, vm_params, env, vm_name) |
| 231 | + started_vms.append(env.get_vm(vm_name)) |
| 232 | + |
| 233 | + for vm in started_vms: |
| 234 | + vm.verify_alive() |
| 235 | + session = vm.wait_for_login(timeout=timeout) |
| 236 | + session.close() |
| 237 | + finally: |
| 238 | + for vm in started_vms: |
| 239 | + vm.destroy(gracefully=False) |
| 240 | + finally: |
| 241 | + if original_nofile_limits is not None: |
| 242 | + resource.setrlimit(resource.RLIMIT_NOFILE, original_nofile_limits) |
0 commit comments