VirtualBox

source: vbox/trunk/src/VBox/Main/testcase/tstVBoxMultipleVM.cpp

Last change on this file was 106061, checked in by vboxsync, 7 weeks ago

Copyright year updates by scm.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 21.4 KB
Line 
1/** @file
2 * tstVBoxMultipleVM - load test for ClientWatcher.
3 */
4
5/*
6 * Copyright (C) 2006-2024 Oracle and/or its affiliates.
7 *
8 * This file is part of VirtualBox base platform packages, as
9 * available from https://www.virtualbox.org.
10 *
11 * This program is free software; you can redistribute it and/or
12 * modify it under the terms of the GNU General Public License
13 * as published by the Free Software Foundation, in version 3 of the
14 * License.
15 *
16 * This program is distributed in the hope that it will be useful, but
17 * WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 * General Public License for more details.
20 *
21 * You should have received a copy of the GNU General Public License
22 * along with this program; if not, see <https://www.gnu.org/licenses>.
23 *
24 * SPDX-License-Identifier: GPL-3.0-only
25 */
26
27
28/*********************************************************************************************************************************
29* Header Files *
30*********************************************************************************************************************************/
31#include <VBox/com/com.h>
32#include <VBox/com/string.h>
33#include <VBox/com/array.h>
34#include <VBox/com/Guid.h>
35#include <VBox/com/ErrorInfo.h>
36#include <VBox/com/errorprint.h>
37#include <iprt/assert.h>
38#include <iprt/errcore.h>
39#include <VBox/com/VirtualBox.h>
40#include <iprt/stream.h>
41#include <iprt/semaphore.h>
42#include <iprt/thread.h>
43#include <VBox/sup.h>
44
45#include <vector>
46#include <algorithm>
47
48#include <iprt/test.h>
49#include <iprt/time.h>
50#include <iprt/rand.h>
51#include <iprt/getopt.h>
52
53using namespace com;
54
55
56/*********************************************************************************************************************************
57* Structures and Typedefs *
58*********************************************************************************************************************************/
59/* Arguments of test thread */
60struct TestThreadArgs
61{
62 /** number of machines that should be run simultaneousely */
63 uint32_t machinesPackSize;
64 /** percents of VM Stop operation what should be called
65 * without session unlocking */
66 uint32_t percentsUnlok;
67 /** How much time in milliseconds test will be executed */
68 uint64_t cMsExecutionTime;
69 /** How much machines create for the test */
70 uint32_t numberMachines;
71};
72
73
74/*********************************************************************************************************************************
75* Global Variables & defs *
76*********************************************************************************************************************************/
77static RTTEST g_hTest;
78#ifdef RT_ARCH_AMD64
79typedef std::vector<Bstr> TMachinesList;
80static volatile bool g_RunTest = true;
81static RTSEMEVENT g_PingEevent;
82static volatile uint64_t g_Counter = 0;
83static TestThreadArgs g_Args;
84
85
86/** Worker for TST_COM_EXPR(). */
87static HRESULT tstComExpr(HRESULT hrc, const char *pszOperation, int iLine)
88{
89 if (FAILED(hrc))
90 {
91 RTTestFailed(g_hTest, "%s failed on line %u with hrc=%Rhrc\n", pszOperation, iLine, hrc);
92 }
93 return hrc;
94}
95
96
97#define CHECK_ERROR_L(iface, method) \
98 do { \
99 hrc = iface->method; \
100 if (FAILED(hrc)) \
101 RTPrintf("warning: %s->%s failed on line %u with hrc=%Rhrc\n", #iface, #method, __LINE__, hrc);\
102 } while (0)
103
104
105/** Macro that executes the given expression and report any failure.
106 * The expression must return a HRESULT. */
107#define TST_COM_EXPR(expr) tstComExpr(expr, #expr, __LINE__)
108
109
110static int tstStartVM(IVirtualBox *pVBox, ISession *pSession, Bstr machineID, bool fSkipUnlock)
111{
112 HRESULT hrc;
113 ComPtr<IProgress> progress;
114 ComPtr<IMachine> machine;
115 Bstr machineName;
116
117 hrc = TST_COM_EXPR(pVBox->FindMachine(machineID.raw(), machine.asOutParam()));
118 if(SUCCEEDED(hrc))
119 hrc = TST_COM_EXPR(machine->COMGETTER(Name)(machineName.asOutParam()));
120 if(SUCCEEDED(hrc))
121 {
122 hrc = machine->LaunchVMProcess(pSession, Bstr("headless").raw(),
123 ComSafeArrayNullInParam(), progress.asOutParam());
124 }
125 if (SUCCEEDED(hrc) && !progress.isNull())
126 {
127 CHECK_ERROR_L(progress, WaitForCompletion(-1));
128 if (SUCCEEDED(hrc))
129 {
130 BOOL completed = true;
131 CHECK_ERROR_L(progress, COMGETTER(Completed)(&completed));
132 if (SUCCEEDED(hrc))
133 {
134 Assert(completed);
135 LONG iRc;
136 CHECK_ERROR_L(progress, COMGETTER(ResultCode)(&iRc));
137 if (SUCCEEDED(hrc))
138 {
139 if (FAILED(iRc))
140 {
141 ProgressErrorInfo info(progress);
142 RTPrintf("Start VM '%ls' failed. Warning: %ls.\n", machineName.raw(), info.getText().raw());
143 }
144 else
145 RTPrintf("VM '%ls' started.\n", machineName.raw());
146 }
147 }
148 }
149 if (!fSkipUnlock)
150 pSession->UnlockMachine();
151 else
152 RTPrintf("Session unlock skipped.\n");
153 }
154 return hrc;
155}
156
157
158static int tstStopVM(IVirtualBox* pVBox, ISession* pSession, Bstr machineID, bool fSkipUnlock)
159{
160 ComPtr<IMachine> machine;
161 HRESULT hrc = TST_COM_EXPR(pVBox->FindMachine(machineID.raw(), machine.asOutParam()));
162 if (SUCCEEDED(hrc))
163 {
164 Bstr machineName;
165 hrc = TST_COM_EXPR(machine->COMGETTER(Name)(machineName.asOutParam()));
166 if (SUCCEEDED(hrc))
167 {
168 MachineState_T machineState;
169 hrc = TST_COM_EXPR(machine->COMGETTER(State)(&machineState));
170 // check that machine is in running state
171 if ( SUCCEEDED(hrc)
172 && ( machineState == MachineState_Running
173 || machineState == MachineState_Paused))
174 {
175 ComPtr<IConsole> console;
176 ComPtr<IProgress> progress;
177
178 hrc = TST_COM_EXPR(machine->LockMachine(pSession, LockType_Shared));
179 if(SUCCEEDED(hrc))
180 TST_COM_EXPR(pSession->COMGETTER(Console)(console.asOutParam()));
181 if(SUCCEEDED(hrc))
182 hrc = console->PowerDown(progress.asOutParam());
183 if (SUCCEEDED(hrc) && !progress.isNull())
184 {
185 //RTPrintf("Stopping VM %ls...\n", machineName.raw());
186 CHECK_ERROR_L(progress, WaitForCompletion(-1));
187 if (SUCCEEDED(hrc))
188 {
189 BOOL completed = true;
190 CHECK_ERROR_L(progress, COMGETTER(Completed)(&completed));
191 if (SUCCEEDED(hrc))
192 {
193 //ASSERT(completed);
194 LONG iRc;
195 CHECK_ERROR_L(progress, COMGETTER(ResultCode)(&iRc));
196 if (SUCCEEDED(hrc))
197 {
198 if (FAILED(iRc))
199 {
200 ProgressErrorInfo info(progress);
201 RTPrintf("Stop VM %ls failed. Warning: %ls.\n", machineName.raw(), info.getText().raw());
202 hrc = iRc;
203 }
204 else
205 {
206 RTPrintf("VM '%ls' stopped.\n", machineName.raw());
207 }
208 }
209 }
210 }
211 if (!fSkipUnlock)
212 pSession->UnlockMachine();
213 else
214 RTPrintf("Session unlock skipped.\n");
215 }
216 }
217 }
218 }
219 return hrc;
220}
221
222
223/**
224 * Get random @a maxCount machines from list of existing VMs.
225 *
226 * @note Can return less then maxCount machines.
227 */
228static int tstGetMachinesList(IVirtualBox *pVBox, uint32_t maxCount, TMachinesList &listToFill)
229{
230 com::SafeIfaceArray<IMachine> machines;
231 HRESULT hrc = TST_COM_EXPR(pVBox->COMGETTER(Machines)(ComSafeArrayAsOutParam(machines)));
232 if (SUCCEEDED(hrc))
233 {
234
235 size_t cMachines = RT_MIN(machines.size(), maxCount);
236 for (size_t i = 0; i < cMachines; ++i)
237 {
238 // choose random index of machine
239 uint32_t idx = RTRandU32Ex(0, (uint32_t)machines.size() - 1);
240 if (machines[idx])
241 {
242 Bstr bstrId;
243 Bstr machineName;
244 CHECK_ERROR_L(machines[idx], COMGETTER(Id)(bstrId.asOutParam()));
245 if (SUCCEEDED(hrc))
246 CHECK_ERROR_L(machines[idx], COMGETTER(Name)(machineName.asOutParam()));
247 if (SUCCEEDED(hrc))
248 {
249 if (Utf8Str(machineName).startsWith("umtvm"))
250 listToFill.push_back(bstrId);
251 }
252 }
253 }
254
255 // remove duplicates from the vector
256 std::sort(listToFill.begin(), listToFill.end());
257 listToFill.erase(std::unique(listToFill.begin(), listToFill.end()), listToFill.end());
258 RTPrintf("Filled pack of %d from %d machines.\n", listToFill.size(), machines.size());
259 }
260
261 return hrc;
262}
263
264
265static int tstMachinesPack(IVirtualBox *pVBox, uint32_t maxPackSize, uint32_t percentage)
266{
267 HRESULT hrc = S_OK;
268 TMachinesList machinesList;
269 bool alwaysUnlock = false;
270 uint64_t percN = 0;
271
272 // choose and fill pack of machines for test
273 tstGetMachinesList(pVBox, maxPackSize, machinesList);
274
275 RTPrintf("Start test.\n");
276 // screw up counter
277 g_Counter = UINT64_MAX - machinesList.size() <= g_Counter ? 0 : g_Counter;
278 if (percentage > 0)
279 percN = 100 / percentage;
280 else
281 alwaysUnlock = true;
282
283 // start all machines in pack
284 for (TMachinesList::iterator it = machinesList.begin();
285 it != machinesList.end() && g_RunTest;
286 ++it)
287 {
288 ComPtr<ISession> session;
289 hrc = session.createInprocObject(CLSID_Session);
290 if (SUCCEEDED(hrc))
291 {
292 hrc = tstStartVM(pVBox, session, *it, !(alwaysUnlock || g_Counter++ % percN));
293 }
294 RTSemEventSignal(g_PingEevent);
295 RTThreadSleep(100);
296 }
297 // stop all machines in the pack
298 for (TMachinesList::iterator it = machinesList.begin();
299 it != machinesList.end() && g_RunTest;
300 ++it)
301 {
302 ComPtr<ISession> session;
303 hrc = session.createInprocObject(CLSID_Session);
304 if (SUCCEEDED(hrc))
305 {
306 // stop machines, skip session unlock of given % of machines
307 hrc = tstStopVM(pVBox, session, *it, !(alwaysUnlock || g_Counter++ % percN));
308 }
309 RTSemEventSignal(g_PingEevent);
310 RTThreadSleep(100);
311 }
312 return hrc;
313}
314
315
316static Bstr tstMakeMachineName(int i)
317{
318 char szMachineName[32];
319 RTStrPrintf(szMachineName, sizeof(szMachineName), "umtvm%d", i);
320 return Bstr(szMachineName);
321}
322
323
324static int tstCreateMachines(IVirtualBox *pVBox)
325{
326 HRESULT hrc = S_OK;
327 // create machines for the test
328 for (uint32_t i = 0; i < g_Args.numberMachines; i++)
329 {
330 ComPtr<IMachine> ptrMachine;
331 com::SafeArray<BSTR> groups;
332
333 Bstr machineName(tstMakeMachineName(i));
334 /* Default VM settings */
335 CHECK_ERROR_L(pVBox, CreateMachine(NULL, /* Settings */
336 machineName.raw(), /* Name */
337 PlatformArchitecture_x86,
338 ComSafeArrayAsInParam(groups), /* Groups */
339 NULL, /* OS Type */
340 NULL, /** Cipher */
341 NULL, /** Password id */
342 NULL, /** Password */
343 NULL, /* Create flags */
344 ptrMachine.asOutParam()));
345 if (SUCCEEDED(hrc))
346 {
347 CHECK_ERROR_L(pVBox, RegisterMachine(ptrMachine));
348 RTPrintf("Machine '%ls' created\n", machineName.raw());
349 }
350
351 RTSemEventSignal(g_PingEevent);
352 RTThreadSleep(100);
353 }
354 return hrc;
355}
356
357
358static int tstClean(IVirtualBox *pVBox, IVirtualBoxClient *pClient)
359{
360 RT_NOREF(pClient);
361 HRESULT hrc = S_OK;
362
363 // stop all machines created for the test
364 for (uint32_t i = 0; i < g_Args.numberMachines; i++)
365 {
366 ComPtr<IMachine> machine;
367 ComPtr<IProgress> progress;
368 ComPtr<ISession> session;
369 SafeIfaceArray<IMedium> media;
370
371 Bstr machineName(tstMakeMachineName(i));
372
373 /* Delete created VM and its files */
374 CHECK_ERROR_L(pVBox, FindMachine(machineName.raw(), machine.asOutParam()));
375
376 // try to stop it again if it was not stopped
377 if (SUCCEEDED(hrc))
378 {
379 MachineState_T machineState;
380 CHECK_ERROR_L(machine, COMGETTER(State)(&machineState));
381 if ( SUCCEEDED(hrc)
382 && ( machineState == MachineState_Running
383 || machineState == MachineState_Paused) )
384 {
385 hrc = session.createInprocObject(CLSID_Session);
386 if (SUCCEEDED(hrc))
387 tstStopVM(pVBox, session, machineName, FALSE);
388 }
389 }
390
391 if (SUCCEEDED(hrc))
392 CHECK_ERROR_L(machine, Unregister(CleanupMode_DetachAllReturnHardDisksOnly, ComSafeArrayAsOutParam(media)));
393 if (SUCCEEDED(hrc))
394 CHECK_ERROR_L(machine, DeleteConfig(ComSafeArrayAsInParam(media), progress.asOutParam()));
395 if (SUCCEEDED(hrc))
396 CHECK_ERROR_L(progress, WaitForCompletion(-1));
397 if (SUCCEEDED(hrc))
398 RTPrintf("Machine '%ls' deleted.\n", machineName.raw());
399 }
400 return hrc;
401}
402
403
404static DECLCALLBACK(int) tstThreadRun(RTTHREAD hThreadSelf, void *pvUser)
405{
406 RT_NOREF(hThreadSelf);
407 TestThreadArgs* args = (TestThreadArgs*)pvUser;
408 Assert(args != NULL);
409 uint32_t maxPackSize = args->machinesPackSize;
410 uint32_t percentage = args->percentsUnlok;
411
412 HRESULT hrc = com::Initialize();
413 if (SUCCEEDED(hrc))
414 {
415 ComPtr<IVirtualBoxClient> ptrVBoxClient;
416 ComPtr<IVirtualBox> ptrVBox;
417
418 hrc = TST_COM_EXPR(ptrVBoxClient.createInprocObject(CLSID_VirtualBoxClient));
419 if (SUCCEEDED(hrc))
420 hrc = TST_COM_EXPR(ptrVBoxClient->COMGETTER(VirtualBox)(ptrVBox.asOutParam()));
421 if (SUCCEEDED(hrc))
422 {
423 RTPrintf("Creating machines...\n");
424 tstCreateMachines(ptrVBox);
425
426 while (g_RunTest)
427 {
428 hrc = tstMachinesPack(ptrVBox, maxPackSize, percentage);
429 }
430
431 RTPrintf("Deleting machines...\n");
432 tstClean(ptrVBox, ptrVBoxClient);
433 }
434
435 g_RunTest = false;
436 RTSemEventSignal(g_PingEevent);
437 RTThreadSleep(100);
438
439 ptrVBox = NULL;
440 ptrVBoxClient = NULL;
441 com::Shutdown();
442 }
443 return hrc;
444}
445
446
447static int ParseArguments(int argc, char **argv, TestThreadArgs *pArgs)
448{
449 RTGETOPTSTATE GetState;
450 RTGETOPTUNION ValueUnion;
451 static const RTGETOPTDEF s_aOptions[] =
452 {
453 { "--packsize", 'p', RTGETOPT_REQ_UINT32 }, // number of machines to start together
454 { "--lock", 's', RTGETOPT_REQ_UINT32 }, // percentage of VM sessions closed without Unlok
455 { "--time", 't', RTGETOPT_REQ_UINT64 }, // required time of load test execution, in seconds
456 { "--machines" , 'u', RTGETOPT_REQ_UINT32 }
457 };
458 int rc = RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, 0 /*fFlags*/);
459 AssertRCReturn(rc, rc);
460 AssertPtr(pArgs);
461
462 while ((rc = RTGetOpt(&GetState, &ValueUnion)) != 0)
463 {
464 switch (rc)
465 {
466 case 'p':
467 if (ValueUnion.u32 == 0)
468 {
469 RTPrintf("--packsize should be more then zero\n");
470 return VERR_INVALID_PARAMETER;
471 }
472 if (ValueUnion.u32 > 16000)
473 {
474 RTPrintf("maximum --packsize value is 16000.\n"
475 "That means can use no more then 16000 machines for the test.\n");
476 return VERR_INVALID_PARAMETER;
477 }
478 pArgs->machinesPackSize = ValueUnion.u32;
479 break;
480
481 case 's':
482 if (ValueUnion.u32 > 100)
483 {
484 RTPrintf("maximum --lock value is 100.\n"
485 "That means 100 percent of sessions should be closed without unlock.\n");
486 return VERR_INVALID_PARAMETER;
487 }
488 pArgs->percentsUnlok = ValueUnion.u32;
489 break;
490
491 case 't':
492 pArgs->cMsExecutionTime = ValueUnion.u64 * 1000;
493 break;
494
495 case 'u':
496 if (ValueUnion.u32 > 16000)
497 {
498 RTPrintf("maximum --machines value is 16000.\n"
499 "That means can make no more then 16000 machines for the test.\n");
500 return VERR_INVALID_PARAMETER;
501 }
502 if (ValueUnion.u32 < pArgs->machinesPackSize)
503 {
504 RTPrintf("--machines value should be larger then --packsize value.\n");
505 return VERR_INVALID_PARAMETER;
506 }
507 pArgs->numberMachines = ValueUnion.u32;
508 break;
509
510 default:
511 RTGetOptPrintError(rc, &ValueUnion);
512 return rc;
513 }
514 }
515 return rc;
516}
517
518#endif /* RT_ARCH_AMD64 */
519
520
521/**
522 *
523 * Examples:
524 * - tstVBoxClientWatcherLoad --packsize 500 --lock 10 --time 14400 --machines 4000
525 * It will create 4000 VMs with names "utmvm0"..."utmvm3999". It will start
526 * 500 random VMs together, stop them, without closing their session with
527 * probability 10%, will repeat this over 4 hours. After test it will
528 * delete all "utmvm..." machines.
529 *
530 * - tstVBoxClientWatcherLoad --packsize 1 --lock 30 --time 3600 --machines 1000
531 * It will create 1000 VMs with names "utmvm0"..."utmvm999". It will start
532 * random VM - stop them, without closing their session with probability
533 * 30%, will repeat this over 30 minutes. After test it will delete all
534 * "utmvm..." machines.
535 */
536int main(int argc, char **argv)
537{
538 RT_NOREF(argc, argv);
539 RTEXITCODE rcExit = RTTestInitAndCreate("tstVBoxMultipleVM", &g_hTest);
540 if (rcExit != RTEXITCODE_SUCCESS)
541 return rcExit;
542 SUPR3Init(NULL);
543 com::Initialize();
544 RTTestBanner(g_hTest);
545
546#ifndef RT_ARCH_AMD64
547 /*
548 * Linux OOM killer when running many VMs on a 32-bit host.
549 */
550 return RTTestSkipAndDestroy(g_hTest, "The test can only run reliably on 64-bit hosts.");
551#else /* RT_ARCH_AMD64 */
552
553 RTPrintf("Initializing ...\n");
554 int rc = RTSemEventCreate(&g_PingEevent);
555 AssertRC(rc);
556
557 g_Args.machinesPackSize = 100;
558 g_Args.percentsUnlok = 10;
559 g_Args.cMsExecutionTime = 3*RT_MS_1MIN;
560 g_Args.numberMachines = 200;
561
562 /*
563 * Skip this test for the time being. Saw crashes on several test boxes but no time
564 * to debug.
565 */
566 if (argc == 1)
567 return RTTestSkipAndDestroy(g_hTest, "Test crashes sometimes.\n");
568
569 rc = ParseArguments(argc, argv, &g_Args);
570 if (RT_FAILURE(rc))
571 return RTTestSkipAndDestroy(g_hTest, "Invalid arguments.\n");
572
573 RTPrintf("Arguments packSize = %d, percentUnlok = %d, time = %lld.\n",
574 g_Args.machinesPackSize, g_Args.percentsUnlok, g_Args.cMsExecutionTime);
575
576 RTTHREAD hThread;
577 rc = RTThreadCreate(&hThread, tstThreadRun, (void *)&g_Args,
578 0, RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "tstThreadRun");
579 if (RT_SUCCESS(rc))
580 {
581 AssertRC(rc);
582
583 uint64_t msStart = RTTimeMilliTS();
584 while (RTTimeMilliTS() - msStart < g_Args.cMsExecutionTime && g_RunTest)
585 {
586 // check that test thread didn't hang and call us periodically
587 // allowed 30 seconds for operation - msStart or stop VM
588 rc = RTSemEventWait(g_PingEevent, 3 * 60 * 1000);
589 if (RT_FAILURE(rc))
590 {
591 if (rc == VERR_TIMEOUT)
592 {
593 RTTestFailed(g_hTest, "Timeout. Deadlock?\n");
594 com::Shutdown();
595 return RTTestSummaryAndDestroy(g_hTest);
596 }
597 AssertRC(rc);
598 }
599 }
600
601 RTPrintf("Finishing...\n");
602
603 // finish test thread
604 g_RunTest = false;
605 // wait it for finish
606 RTThreadWait(hThread, RT_INDEFINITE_WAIT, &rc);
607 }
608 RTSemEventDestroy(g_PingEevent);
609
610 com::Shutdown();
611 if (RT_FAILURE(rc))
612 RTTestFailed(g_hTest, "Test failed.\n");
613 else
614 RTTestPassed(g_hTest, "Test finished.\n");
615 return RTTestSummaryAndDestroy(g_hTest);
616#endif /* RT_ARCH_AMD64 */
617}
618
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette