VirtualBox

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

Last change on this file since 100841 was 98103, checked in by vboxsync, 2 years 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-2023 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 ComSafeArrayAsInParam(groups), /* Groups */
338 NULL, /* OS Type */
339 NULL, /** Cipher */
340 NULL, /** Password id */
341 NULL, /** Password */
342 NULL, /* Create flags */
343 ptrMachine.asOutParam()));
344 if (SUCCEEDED(hrc))
345 {
346 CHECK_ERROR_L(pVBox, RegisterMachine(ptrMachine));
347 RTPrintf("Machine '%ls' created\n", machineName.raw());
348 }
349
350 RTSemEventSignal(g_PingEevent);
351 RTThreadSleep(100);
352 }
353 return hrc;
354}
355
356
357static int tstClean(IVirtualBox *pVBox, IVirtualBoxClient *pClient)
358{
359 RT_NOREF(pClient);
360 HRESULT hrc = S_OK;
361
362 // stop all machines created for the test
363 for (uint32_t i = 0; i < g_Args.numberMachines; i++)
364 {
365 ComPtr<IMachine> machine;
366 ComPtr<IProgress> progress;
367 ComPtr<ISession> session;
368 SafeIfaceArray<IMedium> media;
369
370 Bstr machineName(tstMakeMachineName(i));
371
372 /* Delete created VM and its files */
373 CHECK_ERROR_L(pVBox, FindMachine(machineName.raw(), machine.asOutParam()));
374
375 // try to stop it again if it was not stopped
376 if (SUCCEEDED(hrc))
377 {
378 MachineState_T machineState;
379 CHECK_ERROR_L(machine, COMGETTER(State)(&machineState));
380 if ( SUCCEEDED(hrc)
381 && ( machineState == MachineState_Running
382 || machineState == MachineState_Paused) )
383 {
384 hrc = session.createInprocObject(CLSID_Session);
385 if (SUCCEEDED(hrc))
386 tstStopVM(pVBox, session, machineName, FALSE);
387 }
388 }
389
390 if (SUCCEEDED(hrc))
391 CHECK_ERROR_L(machine, Unregister(CleanupMode_DetachAllReturnHardDisksOnly, ComSafeArrayAsOutParam(media)));
392 if (SUCCEEDED(hrc))
393 CHECK_ERROR_L(machine, DeleteConfig(ComSafeArrayAsInParam(media), progress.asOutParam()));
394 if (SUCCEEDED(hrc))
395 CHECK_ERROR_L(progress, WaitForCompletion(-1));
396 if (SUCCEEDED(hrc))
397 RTPrintf("Machine '%ls' deleted.\n", machineName.raw());
398 }
399 return hrc;
400}
401
402
403static DECLCALLBACK(int) tstThreadRun(RTTHREAD hThreadSelf, void *pvUser)
404{
405 RT_NOREF(hThreadSelf);
406 TestThreadArgs* args = (TestThreadArgs*)pvUser;
407 Assert(args != NULL);
408 uint32_t maxPackSize = args->machinesPackSize;
409 uint32_t percentage = args->percentsUnlok;
410
411 HRESULT hrc = com::Initialize();
412 if (SUCCEEDED(hrc))
413 {
414 ComPtr<IVirtualBoxClient> ptrVBoxClient;
415 ComPtr<IVirtualBox> ptrVBox;
416
417 hrc = TST_COM_EXPR(ptrVBoxClient.createInprocObject(CLSID_VirtualBoxClient));
418 if (SUCCEEDED(hrc))
419 hrc = TST_COM_EXPR(ptrVBoxClient->COMGETTER(VirtualBox)(ptrVBox.asOutParam()));
420 if (SUCCEEDED(hrc))
421 {
422 RTPrintf("Creating machines...\n");
423 tstCreateMachines(ptrVBox);
424
425 while (g_RunTest)
426 {
427 hrc = tstMachinesPack(ptrVBox, maxPackSize, percentage);
428 }
429
430 RTPrintf("Deleting machines...\n");
431 tstClean(ptrVBox, ptrVBoxClient);
432 }
433
434 g_RunTest = false;
435 RTSemEventSignal(g_PingEevent);
436 RTThreadSleep(100);
437
438 ptrVBox = NULL;
439 ptrVBoxClient = NULL;
440 com::Shutdown();
441 }
442 return hrc;
443}
444
445
446static int ParseArguments(int argc, char **argv, TestThreadArgs *pArgs)
447{
448 RTGETOPTSTATE GetState;
449 RTGETOPTUNION ValueUnion;
450 static const RTGETOPTDEF s_aOptions[] =
451 {
452 { "--packsize", 'p', RTGETOPT_REQ_UINT32 }, // number of machines to start together
453 { "--lock", 's', RTGETOPT_REQ_UINT32 }, // percentage of VM sessions closed without Unlok
454 { "--time", 't', RTGETOPT_REQ_UINT64 }, // required time of load test execution, in seconds
455 { "--machines" , 'u', RTGETOPT_REQ_UINT32 }
456 };
457 int rc = RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, 0 /*fFlags*/);
458 AssertRCReturn(rc, rc);
459 AssertPtr(pArgs);
460
461 while ((rc = RTGetOpt(&GetState, &ValueUnion)) != 0)
462 {
463 switch (rc)
464 {
465 case 'p':
466 if (ValueUnion.u32 == 0)
467 {
468 RTPrintf("--packsize should be more then zero\n");
469 return VERR_INVALID_PARAMETER;
470 }
471 if (ValueUnion.u32 > 16000)
472 {
473 RTPrintf("maximum --packsize value is 16000.\n"
474 "That means can use no more then 16000 machines for the test.\n");
475 return VERR_INVALID_PARAMETER;
476 }
477 pArgs->machinesPackSize = ValueUnion.u32;
478 break;
479
480 case 's':
481 if (ValueUnion.u32 > 100)
482 {
483 RTPrintf("maximum --lock value is 100.\n"
484 "That means 100 percent of sessions should be closed without unlock.\n");
485 return VERR_INVALID_PARAMETER;
486 }
487 pArgs->percentsUnlok = ValueUnion.u32;
488 break;
489
490 case 't':
491 pArgs->cMsExecutionTime = ValueUnion.u64 * 1000;
492 break;
493
494 case 'u':
495 if (ValueUnion.u32 > 16000)
496 {
497 RTPrintf("maximum --machines value is 16000.\n"
498 "That means can make no more then 16000 machines for the test.\n");
499 return VERR_INVALID_PARAMETER;
500 }
501 if (ValueUnion.u32 < pArgs->machinesPackSize)
502 {
503 RTPrintf("--machines value should be larger then --packsize value.\n");
504 return VERR_INVALID_PARAMETER;
505 }
506 pArgs->numberMachines = ValueUnion.u32;
507 break;
508
509 default:
510 RTGetOptPrintError(rc, &ValueUnion);
511 return rc;
512 }
513 }
514 return rc;
515}
516
517#endif /* RT_ARCH_AMD64 */
518
519
520/**
521 *
522 * Examples:
523 * - tstVBoxClientWatcherLoad --packsize 500 --lock 10 --time 14400 --machines 4000
524 * It will create 4000 VMs with names "utmvm0"..."utmvm3999". It will start
525 * 500 random VMs together, stop them, without closing their session with
526 * probability 10%, will repeat this over 4 hours. After test it will
527 * delete all "utmvm..." machines.
528 *
529 * - tstVBoxClientWatcherLoad --packsize 1 --lock 30 --time 3600 --machines 1000
530 * It will create 1000 VMs with names "utmvm0"..."utmvm999". It will start
531 * random VM - stop them, without closing their session with probability
532 * 30%, will repeat this over 30 minutes. After test it will delete all
533 * "utmvm..." machines.
534 */
535int main(int argc, char **argv)
536{
537 RT_NOREF(argc, argv);
538 RTEXITCODE rcExit = RTTestInitAndCreate("tstVBoxMultipleVM", &g_hTest);
539 if (rcExit != RTEXITCODE_SUCCESS)
540 return rcExit;
541 SUPR3Init(NULL);
542 com::Initialize();
543 RTTestBanner(g_hTest);
544
545#ifndef RT_ARCH_AMD64
546 /*
547 * Linux OOM killer when running many VMs on a 32-bit host.
548 */
549 return RTTestSkipAndDestroy(g_hTest, "The test can only run reliably on 64-bit hosts.");
550#else /* RT_ARCH_AMD64 */
551
552 RTPrintf("Initializing ...\n");
553 int rc = RTSemEventCreate(&g_PingEevent);
554 AssertRC(rc);
555
556 g_Args.machinesPackSize = 100;
557 g_Args.percentsUnlok = 10;
558 g_Args.cMsExecutionTime = 3*RT_MS_1MIN;
559 g_Args.numberMachines = 200;
560
561 /*
562 * Skip this test for the time being. Saw crashes on several test boxes but no time
563 * to debug.
564 */
565 if (argc == 1)
566 return RTTestSkipAndDestroy(g_hTest, "Test crashes sometimes.\n");
567
568 rc = ParseArguments(argc, argv, &g_Args);
569 if (RT_FAILURE(rc))
570 return RTTestSkipAndDestroy(g_hTest, "Invalid arguments.\n");
571
572 RTPrintf("Arguments packSize = %d, percentUnlok = %d, time = %lld.\n",
573 g_Args.machinesPackSize, g_Args.percentsUnlok, g_Args.cMsExecutionTime);
574
575 RTTHREAD hThread;
576 rc = RTThreadCreate(&hThread, tstThreadRun, (void *)&g_Args,
577 0, RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "tstThreadRun");
578 if (RT_SUCCESS(rc))
579 {
580 AssertRC(rc);
581
582 uint64_t msStart = RTTimeMilliTS();
583 while (RTTimeMilliTS() - msStart < g_Args.cMsExecutionTime && g_RunTest)
584 {
585 // check that test thread didn't hang and call us periodically
586 // allowed 30 seconds for operation - msStart or stop VM
587 rc = RTSemEventWait(g_PingEevent, 3 * 60 * 1000);
588 if (RT_FAILURE(rc))
589 {
590 if (rc == VERR_TIMEOUT)
591 {
592 RTTestFailed(g_hTest, "Timeout. Deadlock?\n");
593 com::Shutdown();
594 return RTTestSummaryAndDestroy(g_hTest);
595 }
596 AssertRC(rc);
597 }
598 }
599
600 RTPrintf("Finishing...\n");
601
602 // finish test thread
603 g_RunTest = false;
604 // wait it for finish
605 RTThreadWait(hThread, RT_INDEFINITE_WAIT, &rc);
606 }
607 RTSemEventDestroy(g_PingEevent);
608
609 com::Shutdown();
610 if (RT_FAILURE(rc))
611 RTTestFailed(g_hTest, "Test failed.\n");
612 else
613 RTTestPassed(g_hTest, "Test finished.\n");
614 return RTTestSummaryAndDestroy(g_hTest);
615#endif /* RT_ARCH_AMD64 */
616}
617
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