VirtualBox

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

Last change on this file since 78352 was 76553, checked in by vboxsync, 6 years ago

scm --update-copyright-year

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