VirtualBox

source: vbox/trunk/src/VBox/ValidationKit/tests/storage/storagecfg.py@ 79602

Last change on this file since 79602 was 79602, checked in by vboxsync, 5 years ago

ValidationKit/tdStorageBenchmark1: Testcase fixes for Windows

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 22.0 KB
Line 
1# -*- coding: utf-8 -*-
2# $Id: storagecfg.py 79602 2019-07-08 17:00:21Z vboxsync $
3
4"""
5VirtualBox Validation Kit - Storage test configuration API.
6"""
7
8__copyright__ = \
9"""
10Copyright (C) 2016-2019 Oracle Corporation
11
12This file is part of VirtualBox Open Source Edition (OSE), as
13available from http://www.virtualbox.org. This file is free software;
14you can redistribute it and/or modify it under the terms of the GNU
15General Public License (GPL) as published by the Free Software
16Foundation, in version 2 as it comes in the "COPYING" file of the
17VirtualBox OSE distribution. VirtualBox OSE is distributed in the
18hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
19
20The contents of this file may alternatively be used under the terms
21of the Common Development and Distribution License Version 1.0
22(CDDL) only, as it comes in the "COPYING.CDDL" file of the
23VirtualBox OSE distribution, in which case the provisions of the
24CDDL are applicable instead of those of the GPL.
25
26You may elect to license modified versions of this file under the
27terms and conditions of either the GPL or the CDDL or both.
28"""
29__version__ = "$Revision: 79602 $"
30
31# Standard Python imports.
32import os;
33import re;
34
35
36class StorageDisk(object):
37 """
38 Class representing a disk for testing.
39 """
40
41 def __init__(self, sPath, fRamDisk = False):
42 self.sPath = sPath;
43 self.fUsed = False;
44 self.fRamDisk = fRamDisk;
45
46 def getPath(self):
47 """
48 Return the disk path.
49 """
50 return self.sPath;
51
52 def isUsed(self):
53 """
54 Returns whether the disk is currently in use.
55 """
56 return self.fUsed;
57
58 def isRamDisk(self):
59 """
60 Returns whether the disk objecthas a RAM backing.
61 """
62 return self.fRamDisk;
63
64 def setUsed(self, fUsed):
65 """
66 Sets the used flag for the disk.
67 """
68 if fUsed:
69 if self.fUsed:
70 return False;
71
72 self.fUsed = True;
73 else:
74 self.fUsed = fUsed;
75
76 return True;
77
78class StorageConfigOs(object):
79 """
80 Base class for a single hosts OS storage configuration.
81 """
82
83 def _getDisksMatchingRegExpWithPath(self, sPath, sRegExp):
84 """
85 Adds new disks to the config matching the given regular expression.
86 """
87
88 lstDisks = [];
89 oRegExp = re.compile(sRegExp);
90 asFiles = os.listdir(sPath);
91 for sFile in asFiles:
92 if oRegExp.match(os.path.basename(sFile)) and os.path.exists(sPath + '/' + sFile):
93 lstDisks.append(StorageDisk(sPath + '/' + sFile));
94
95 return lstDisks;
96
97class StorageConfigOsSolaris(StorageConfigOs):
98 """
99 Class implementing the Solaris specifics for a storage configuration.
100 """
101
102 def __init__(self):
103 StorageConfigOs.__init__(self);
104 self.idxRamDisk = 0;
105
106 def _getActivePoolsStartingWith(self, oExec, sPoolIdStart):
107 """
108 Returns a list of pools starting with the given ID or None on failure.
109 """
110 lstPools = None;
111 fRc, sOutput, _ = oExec.execBinary('zpool', ('list', '-H'));
112 if fRc:
113 lstPools = [];
114 asPools = sOutput.splitlines();
115 for sPool in asPools:
116 if sPool.startswith(sPoolIdStart):
117 # Extract the whole name and add it to the list.
118 asItems = sPool.split('\t');
119 lstPools.append(asItems[0]);
120 return lstPools;
121
122 def _getActiveVolumesInPoolStartingWith(self, oExec, sPool, sVolumeIdStart):
123 """
124 Returns a list of active volumes for the given pool starting with the given
125 identifier or None on failure.
126 """
127 lstVolumes = None;
128 fRc, sOutput, _ = oExec.execBinary('zfs', ('list', '-H'));
129 if fRc:
130 lstVolumes = [];
131 asVolumes = sOutput.splitlines();
132 for sVolume in asVolumes:
133 if sVolume.startswith(sPool + '/' + sVolumeIdStart):
134 # Extract the whole name and add it to the list.
135 asItems = sVolume.split('\t');
136 lstVolumes.append(asItems[0]);
137 return lstVolumes;
138
139 def getDisksMatchingRegExp(self, sRegExp):
140 """
141 Returns a list of disks matching the regular expression.
142 """
143 return self._getDisksMatchingRegExpWithPath('/dev/dsk', sRegExp);
144
145 def getMntBase(self):
146 """
147 Returns the mountpoint base for the host.
148 """
149 return '/pools';
150
151 def createStoragePool(self, oExec, sPool, asDisks, sRaidLvl):
152 """
153 Creates a new storage pool with the given disks and the given RAID level.
154 """
155 sZPoolRaid = None;
156 if len(asDisks) > 1 and (sRaidLvl == 'raid5' or sRaidLvl is None):
157 sZPoolRaid = 'raidz';
158
159 fRc = True;
160 if sZPoolRaid is not None:
161 fRc = oExec.execBinaryNoStdOut('zpool', ('create', '-f', sPool, sZPoolRaid,) + tuple(asDisks));
162 else:
163 fRc = oExec.execBinaryNoStdOut('zpool', ('create', '-f', sPool,) + tuple(asDisks));
164
165 return fRc;
166
167 def createVolume(self, oExec, sPool, sVol, sMountPoint, cbVol = None):
168 """
169 Creates and mounts a filesystem at the given mountpoint using the
170 given pool and volume IDs.
171 """
172 fRc = True;
173 if cbVol is not None:
174 fRc = oExec.execBinaryNoStdOut('zfs', ('create', '-o', 'mountpoint='+sMountPoint, '-V', cbVol, sPool + '/' + sVol));
175 else:
176 fRc = oExec.execBinaryNoStdOut('zfs', ('create', '-o', 'mountpoint='+sMountPoint, sPool + '/' + sVol));
177
178 return fRc;
179
180 def destroyVolume(self, oExec, sPool, sVol):
181 """
182 Destroys the given volume.
183 """
184 fRc = oExec.execBinaryNoStdOut('zfs', ('destroy', sPool + '/' + sVol));
185 return fRc;
186
187 def destroyPool(self, oExec, sPool):
188 """
189 Destroys the given storage pool.
190 """
191 fRc = oExec.execBinaryNoStdOut('zpool', ('destroy', sPool));
192 return fRc;
193
194 def cleanupPoolsAndVolumes(self, oExec, sPoolIdStart, sVolIdStart):
195 """
196 Cleans up any pools and volumes starting with the name in the given
197 parameters.
198 """
199 fRc = True;
200 lstPools = self._getActivePoolsStartingWith(oExec, sPoolIdStart);
201 if lstPools is not None:
202 for sPool in lstPools:
203 lstVolumes = self._getActiveVolumesInPoolStartingWith(oExec, sPool, sVolIdStart);
204 if lstVolumes is not None:
205 # Destroy all the volumes first
206 for sVolume in lstVolumes:
207 fRc2 = oExec.execBinaryNoStdOut('zfs', ('destroy', sVolume));
208 if not fRc2:
209 fRc = fRc2;
210
211 # Destroy the pool
212 fRc2 = self.destroyPool(oExec, sPool);
213 if not fRc2:
214 fRc = fRc2;
215 else:
216 fRc = False;
217 else:
218 fRc = False;
219
220 return fRc;
221
222 def createRamDisk(self, oExec, cbRamDisk):
223 """
224 Creates a RAM backed disk with the given size.
225 """
226 oDisk = None;
227 sRamDiskName = 'ramdisk%u' % (self.idxRamDisk,);
228 fRc, _ , _ = oExec.execBinary('ramdiskadm', ('-a', sRamDiskName, str(cbRamDisk)));
229 if fRc:
230 self.idxRamDisk += 1;
231 oDisk = StorageDisk('/dev/ramdisk/%s' % (sRamDiskName, ), True);
232
233 return oDisk;
234
235 def destroyRamDisk(self, oExec, oDisk):
236 """
237 Destroys the given ramdisk object.
238 """
239 sRamDiskName = os.path.basename(oDisk.getPath());
240 return oExec.execBinaryNoStdOut('ramdiskadm', ('-d', sRamDiskName));
241
242class StorageConfigOsLinux(StorageConfigOs):
243 """
244 Class implementing the Linux specifics for a storage configuration.
245 """
246
247 def __init__(self):
248 StorageConfigOs.__init__(self);
249 self.dSimplePools = { }; # Simple storage pools which don't use lvm (just one partition)
250 self.dMounts = { }; # Pool/Volume to mountpoint mapping.
251
252 def _getDmRaidLevelFromLvl(self, sRaidLvl):
253 """
254 Converts our raid level indicators to something mdadm can understand.
255 """
256 if sRaidLvl is None or sRaidLvl == 'raid0':
257 return 'stripe';
258 if sRaidLvl == 'raid5':
259 return '5';
260 if sRaidLvl == 'raid1':
261 return 'mirror';
262 return 'stripe';
263
264 def getDisksMatchingRegExp(self, sRegExp):
265 """
266 Returns a list of disks matching the regular expression.
267 """
268 return self._getDisksMatchingRegExpWithPath('/dev/', sRegExp);
269
270 def getMntBase(self):
271 """
272 Returns the mountpoint base for the host.
273 """
274 return '/mnt';
275
276 def createStoragePool(self, oExec, sPool, asDisks, sRaidLvl):
277 """
278 Creates a new storage pool with the given disks and the given RAID level.
279 """
280 fRc = True;
281 if len(asDisks) == 1 and sRaidLvl is None:
282 # Doesn't require LVM, put into the simple pools dictionary so we can
283 # use it when creating a volume later.
284 self.dSimplePools[sPool] = asDisks[0];
285 else:
286 # If a RAID is required use dm-raid first to create one.
287 asLvmPvDisks = asDisks;
288 fRc = oExec.execBinaryNoStdOut('mdadm', ('--create', '/dev/md0', '--assume-clean',
289 '--level=' + self._getDmRaidLevelFromLvl(sRaidLvl),
290 '--raid-devices=' + str(len(asDisks))) + tuple(asDisks));
291 if fRc:
292 # /dev/md0 is the only block device to use for our volume group.
293 asLvmPvDisks = [ '/dev/md0' ];
294
295 # Create a physical volume on every disk first.
296 for sLvmPvDisk in asLvmPvDisks:
297 fRc = oExec.execBinaryNoStdOut('pvcreate', (sLvmPvDisk, ));
298 if not fRc:
299 break;
300
301 if fRc:
302 # Create volume group with all physical volumes included
303 fRc = oExec.execBinaryNoStdOut('vgcreate', (sPool, ) + tuple(asLvmPvDisks));
304 return fRc;
305
306 def createVolume(self, oExec, sPool, sVol, sMountPoint, cbVol = None):
307 """
308 Creates and mounts a filesystem at the given mountpoint using the
309 given pool and volume IDs.
310 """
311 fRc = True;
312 sBlkDev = None;
313 if sPool in self.dSimplePools:
314 sDiskPath = self.dSimplePools.get(sPool);
315 if sDiskPath.find('zram') != -1:
316 sBlkDev = sDiskPath;
317 else:
318 # Create a partition with the requested size
319 sFdiskScript = ';\n'; # Single partition filling everything
320 if cbVol is not None:
321 sFdiskScript = ',' + str(cbVol // 512) + '\n'; # Get number of sectors
322 fRc = oExec.execBinaryNoStdOut('sfdisk', ('--no-reread', '--wipe', 'always', '-q', '-f', sDiskPath), \
323 sFdiskScript);
324 if fRc:
325 if sDiskPath.find('nvme') != -1:
326 sBlkDev = sDiskPath + 'p1';
327 else:
328 sBlkDev = sDiskPath + '1';
329 else:
330 if cbVol is None:
331 fRc = oExec.execBinaryNoStdOut('lvcreate', ('-l', '100%FREE', '-n', sVol, sPool));
332 else:
333 fRc = oExec.execBinaryNoStdOut('lvcreate', ('-L', str(cbVol), '-n', sVol, sPool));
334 if fRc:
335 sBlkDev = '/dev/mapper' + sPool + '-' + sVol;
336
337 if fRc is True and sBlkDev is not None:
338 # Create a filesystem and mount it
339 fRc = oExec.execBinaryNoStdOut('mkfs.ext4', ('-F', '-F', sBlkDev,));
340 fRc = fRc and oExec.mkDir(sMountPoint);
341 fRc = fRc and oExec.execBinaryNoStdOut('mount', (sBlkDev, sMountPoint));
342 if fRc:
343 self.dMounts[sPool + '/' + sVol] = sMountPoint;
344 return fRc;
345
346 def destroyVolume(self, oExec, sPool, sVol):
347 """
348 Destroys the given volume.
349 """
350 # Unmount first
351 sMountPoint = self.dMounts[sPool + '/' + sVol];
352 fRc = oExec.execBinaryNoStdOut('umount', (sMountPoint,));
353 self.dMounts.pop(sPool + '/' + sVol);
354 oExec.rmDir(sMountPoint);
355 if sPool in self.dSimplePools:
356 # Wipe partition table
357 sDiskPath = self.dSimplePools.get(sPool);
358 if sDiskPath.find('zram') == -1:
359 fRc = oExec.execBinaryNoStdOut('sfdisk', ('--no-reread', '--wipe', 'always', '-q', '-f', '--delete', \
360 sDiskPath));
361 else:
362 fRc = oExec.execBinaryNoStdOut('lvremove', (sPool + '/' + sVol,));
363 return fRc;
364
365 def destroyPool(self, oExec, sPool):
366 """
367 Destroys the given storage pool.
368 """
369 fRc = True;
370 if sPool in self.dSimplePools:
371 self.dSimplePools.pop(sPool);
372 else:
373 fRc = oExec.execBinaryNoStdOut('vgremove', (sPool,));
374 return fRc;
375
376 def cleanupPoolsAndVolumes(self, oExec, sPoolIdStart, sVolIdStart):
377 """
378 Cleans up any pools and volumes starting with the name in the given
379 parameters.
380 """
381 # @todo: Needs implementation, for LVM based configs a similar approach can be used
382 # as for Solaris.
383 _ = oExec;
384 _ = sPoolIdStart;
385 _ = sVolIdStart;
386 return True;
387
388 def createRamDisk(self, oExec, cbRamDisk):
389 """
390 Creates a RAM backed disk with the given size.
391 """
392 # Make sure the ZRAM module is loaded.
393 oDisk = None;
394 fRc = oExec.execBinaryNoStdOut('modprobe', ('zram',));
395 if fRc:
396 fRc, sOut, _ = oExec.execBinary('zramctl', ('--raw', '-f', '-s', str(cbRamDisk)));
397 if fRc:
398 oDisk = StorageDisk(sOut.rstrip(), True);
399
400 return oDisk;
401
402 def destroyRamDisk(self, oExec, oDisk):
403 """
404 Destroys the given ramdisk object.
405 """
406 return oExec.execBinaryNoStdOut('zramctl', ('-r', oDisk.getPath()));
407
408## @name Host disk config types.
409## @{
410g_ksDiskCfgStatic = 'StaticDir';
411g_ksDiskCfgRegExp = 'RegExp';
412g_ksDiskCfgList = 'DiskList';
413## @}
414
415class DiskCfg(object):
416 """
417 Host disk configuration.
418 """
419
420 def __init__(self, sTargetOs, sCfgType, oDisks):
421 self.sTargetOs = sTargetOs;
422 self.sCfgType = sCfgType;
423 self.oDisks = oDisks;
424
425 def getTargetOs(self):
426 return self.sTargetOs;
427
428 def getCfgType(self):
429 return self.sCfgType;
430
431 def isCfgStaticDir(self):
432 return self.sCfgType == g_ksDiskCfgStatic;
433
434 def isCfgRegExp(self):
435 return self.sCfgType == g_ksDiskCfgRegExp;
436
437 def isCfgList(self):
438 return self.sCfgType == g_ksDiskCfgList;
439
440 def getDisks(self):
441 return self.oDisks;
442
443class StorageCfg(object):
444 """
445 Storage configuration helper class taking care of the different host OS.
446 """
447
448 def __init__(self, oExec, oDiskCfg):
449 self.oExec = oExec;
450 self.lstDisks = [ ]; # List of disks present in the system.
451 self.dPools = { }; # Dictionary of storage pools.
452 self.dVols = { }; # Dictionary of volumes.
453 self.iPoolId = 0;
454 self.iVolId = 0;
455 self.oDiskCfg = oDiskCfg;
456
457 fRc = True;
458 oStorOs = None;
459 if oDiskCfg.getTargetOs() == 'solaris':
460 oStorOs = StorageConfigOsSolaris();
461 elif oDiskCfg.getTargetOs() == 'linux':
462 oStorOs = StorageConfigOsLinux(); # pylint: disable=redefined-variable-type
463 elif not oDiskCfg.isCfgStaticDir(): # For unknown hosts we only allow a static testing directory we don't care about setting up.
464 fRc = False;
465
466 if fRc:
467 self.oStorOs = oStorOs;
468 if oDiskCfg.isCfgRegExp():
469 self.lstDisks = oStorOs.getDisksMatchingRegExp(oDiskCfg.getDisks());
470 elif oDiskCfg.isCfgList():
471 # Assume a list of of disks and add.
472 for sDisk in oDiskCfg.getDisks():
473 self.lstDisks.append(StorageDisk(sDisk));
474 elif oDiskCfg.isCfgStaticDir():
475 if not os.path.exists(oDiskCfg.getDisks()):
476 self.oExec.mkDir(oDiskCfg.getDisks(), 0o700);
477
478 def __del__(self):
479 self.cleanup();
480 self.oDiskCfg = None;
481
482 def cleanup(self):
483 """
484 Cleans up any created storage configs.
485 """
486
487 if not self.oDiskCfg.isCfgStaticDir():
488 # Destroy all volumes first.
489 for sMountPoint in self.dVols.keys(): # pylint: disable=consider-iterating-dictionary
490 self.destroyVolume(sMountPoint);
491
492 # Destroy all pools.
493 for sPool in self.dPools.keys(): # pylint: disable=consider-iterating-dictionary
494 self.destroyStoragePool(sPool);
495
496 self.dVols.clear();
497 self.dPools.clear();
498 self.iPoolId = 0;
499 self.iVolId = 0;
500
501 def getRawDisk(self):
502 """
503 Returns a raw disk device from the list of free devices for use.
504 """
505
506 for oDisk in self.lstDisks:
507 if oDisk.isUsed() is False:
508 oDisk.setUsed(True);
509 return oDisk.getPath();
510
511 return None;
512
513 def getUnusedDiskCount(self):
514 """
515 Returns the number of unused disks.
516 """
517
518 cDisksUnused = 0;
519 for oDisk in self.lstDisks:
520 if not oDisk.isUsed():
521 cDisksUnused += 1;
522
523 return cDisksUnused;
524
525 def createStoragePool(self, cDisks = 0, sRaidLvl = None,
526 cbPool = None, fRamDisk = False):
527 """
528 Create a new storage pool
529 """
530 lstDisks = [ ];
531 fRc = True;
532 sPool = None;
533
534 if not self.oDiskCfg.isCfgStaticDir():
535 if fRamDisk:
536 oDisk = self.oStorOs.createRamDisk(self.oExec, cbPool);
537 if oDisk is not None:
538 lstDisks.append(oDisk);
539 cDisks = 1;
540 else:
541 if cDisks == 0:
542 cDisks = self.getUnusedDiskCount();
543
544 for oDisk in self.lstDisks:
545 if not oDisk.isUsed():
546 oDisk.setUsed(True);
547 lstDisks.append(oDisk);
548 if len(lstDisks) == cDisks:
549 break;
550
551 # Enough drives to satisfy the request?
552 if len(lstDisks) == cDisks:
553 # Create a list of all device paths
554 lstDiskPaths = [ ];
555 for oDisk in lstDisks:
556 lstDiskPaths.append(oDisk.getPath());
557
558 # Find a name for the pool
559 sPool = 'pool' + str(self.iPoolId);
560 self.iPoolId += 1;
561
562 fRc = self.oStorOs.createStoragePool(self.oExec, sPool, lstDiskPaths, sRaidLvl);
563 if fRc:
564 self.dPools[sPool] = lstDisks;
565 else:
566 self.iPoolId -= 1;
567 else:
568 fRc = False;
569
570 # Cleanup in case of error.
571 if not fRc:
572 for oDisk in lstDisks:
573 oDisk.setUsed(False);
574 if oDisk.isRamDisk():
575 self.oStorOs.destroyRamDisk(self.oExec, oDisk);
576 else:
577 sPool = 'StaticDummy';
578
579 return fRc, sPool;
580
581 def destroyStoragePool(self, sPool):
582 """
583 Destroys the storage pool with the given ID.
584 """
585
586 fRc = True;
587
588 if not self.oDiskCfg.isCfgStaticDir():
589 lstDisks = self.dPools.get(sPool);
590 if lstDisks is not None:
591 fRc = self.oStorOs.destroyPool(self.oExec, sPool);
592 if fRc:
593 # Mark disks as unused
594 self.dPools.pop(sPool);
595 for oDisk in lstDisks:
596 oDisk.setUsed(False);
597 if oDisk.isRamDisk():
598 self.oStorOs.destroyRamDisk(self.oExec, oDisk);
599 else:
600 fRc = False;
601
602 return fRc;
603
604 def createVolume(self, sPool, cbVol = None):
605 """
606 Creates a new volume from the given pool returning the mountpoint.
607 """
608
609 fRc = True;
610 sMountPoint = None;
611 if not self.oDiskCfg.isCfgStaticDir():
612 if sPool in self.dPools:
613 sVol = 'vol' + str(self.iVolId);
614 sMountPoint = self.oStorOs.getMntBase() + '/' + sVol;
615 self.iVolId += 1;
616 fRc = self.oStorOs.createVolume(self.oExec, sPool, sVol, sMountPoint, cbVol);
617 if fRc:
618 self.dVols[sMountPoint] = (sVol, sPool);
619 else:
620 self.iVolId -= 1;
621 else:
622 fRc = False;
623 else:
624 sMountPoint = self.oDiskCfg.getDisks();
625
626 return fRc, sMountPoint;
627
628 def destroyVolume(self, sMountPoint):
629 """
630 Destroy the volume at the given mount point.
631 """
632
633 fRc = True;
634 if not self.oDiskCfg.isCfgStaticDir():
635 sVol, sPool = self.dVols.get(sMountPoint);
636 if sVol is not None:
637 fRc = self.oStorOs.destroyVolume(self.oExec, sPool, sVol);
638 if fRc:
639 self.dVols.pop(sMountPoint);
640 else:
641 fRc = False;
642
643 return fRc;
644
645 def mkDirOnVolume(self, sMountPoint, sDir, fMode = 0o700):
646 """
647 Creates a new directory on the volume pointed to by the given mount point.
648 """
649 return self.oExec.mkDir(sMountPoint + '/' + sDir, fMode);
650
651 def cleanupLeftovers(self):
652 """
653 Tries to cleanup any leftover pools and volumes from a failed previous run.
654 """
655 if not self.oDiskCfg.isCfgStaticDir():
656 return self.oStorOs.cleanupPoolsAndVolumes(self.oExec, 'pool', 'vol');
657
658 fRc = True;
659 if os.path.exists(self.oDiskCfg.getDisks()):
660 for sEntry in os.listdir(self.oDiskCfg.getDisks()):
661 fRc = fRc and self.oExec.rmTree(os.path.join(self.oDiskCfg.getDisks(), sEntry));
662
663 return fRc;
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