Revision fa8ef9d6
b/lib/cmdlib.py | ||
---|---|---|
8972 | 8972 |
return (total_size - written) * avg_time |
8973 | 8973 |
|
8974 | 8974 |
|
8975 |
def _WipeDisks(lu, instance): |
|
8975 |
def _WipeDisks(lu, instance, disks=None):
|
|
8976 | 8976 |
"""Wipes instance disks. |
8977 | 8977 |
|
8978 | 8978 |
@type lu: L{LogicalUnit} |
... | ... | |
8984 | 8984 |
""" |
8985 | 8985 |
node = instance.primary_node |
8986 | 8986 |
|
8987 |
for device in instance.disks: |
|
8987 |
if disks is None: |
|
8988 |
disks = [(idx, disk, 0) |
|
8989 |
for (idx, disk) in enumerate(instance.disks)] |
|
8990 |
|
|
8991 |
for (_, device, _) in disks: |
|
8988 | 8992 |
lu.cfg.SetDiskID(device, node) |
8989 | 8993 |
|
8990 | 8994 |
logging.info("Pausing synchronization of disks of instance '%s'", |
8991 | 8995 |
instance.name) |
8992 | 8996 |
result = lu.rpc.call_blockdev_pause_resume_sync(node, |
8993 |
(instance.disks, instance), |
|
8997 |
(map(compat.snd, disks), |
|
8998 |
instance), |
|
8994 | 8999 |
True) |
8995 | 9000 |
result.Raise("Failed to pause disk synchronization on node '%s'" % node) |
8996 | 9001 |
|
... | ... | |
9000 | 9005 |
" failed", idx, instance.name) |
9001 | 9006 |
|
9002 | 9007 |
try: |
9003 |
for idx, device in enumerate(instance.disks):
|
|
9008 |
for (idx, device, offset) in disks:
|
|
9004 | 9009 |
# The wipe size is MIN_WIPE_CHUNK_PERCENT % of the instance disk but |
9005 | 9010 |
# MAX_WIPE_CHUNK at max. Truncating to integer to avoid rounding errors. |
9006 | 9011 |
wipe_chunk_size = \ |
9007 | 9012 |
int(min(constants.MAX_WIPE_CHUNK, |
9008 | 9013 |
device.size / 100.0 * constants.MIN_WIPE_CHUNK_PERCENT)) |
9009 | 9014 |
|
9010 |
lu.LogInfo("* Wiping disk %d", idx) |
|
9011 |
logging.info("Wiping disk %d for instance %s on node %s using" |
|
9012 |
" chunk size %s", idx, instance.name, node, wipe_chunk_size) |
|
9013 |
|
|
9014 |
offset = 0 |
|
9015 | 9015 |
size = device.size |
9016 | 9016 |
last_output = 0 |
9017 | 9017 |
start_time = time.time() |
9018 | 9018 |
|
9019 |
if offset == 0: |
|
9020 |
info_text = "" |
|
9021 |
else: |
|
9022 |
info_text = (" (from %s to %s)" % |
|
9023 |
(utils.FormatUnit(offset, "h"), |
|
9024 |
utils.FormatUnit(size, "h"))) |
|
9025 |
|
|
9026 |
lu.LogInfo("* Wiping disk %s%s", idx, info_text) |
|
9027 |
|
|
9028 |
logging.info("Wiping disk %d for instance %s on node %s using" |
|
9029 |
" chunk size %s", idx, instance.name, node, wipe_chunk_size) |
|
9030 |
|
|
9019 | 9031 |
while offset < size: |
9020 | 9032 |
wipe_size = min(wipe_chunk_size, size - offset) |
9021 | 9033 |
|
... | ... | |
9039 | 9051 |
instance.name) |
9040 | 9052 |
|
9041 | 9053 |
result = lu.rpc.call_blockdev_pause_resume_sync(node, |
9042 |
(instance.disks, instance), |
|
9054 |
(map(compat.snd, disks), |
|
9055 |
instance), |
|
9043 | 9056 |
False) |
9044 | 9057 |
|
9045 | 9058 |
if result.fail_msg: |
... | ... | |
11832 | 11845 |
for ops in jobs] |
11833 | 11846 |
|
11834 | 11847 |
|
11848 |
def _DiskSizeInBytesToMebibytes(lu, size): |
|
11849 |
"""Converts a disk size in bytes to mebibytes. |
|
11850 |
|
|
11851 |
Warns and rounds up if the size isn't an even multiple of 1 MiB. |
|
11852 |
|
|
11853 |
""" |
|
11854 |
(mib, remainder) = divmod(size, 1024 * 1024) |
|
11855 |
|
|
11856 |
if remainder != 0: |
|
11857 |
lu.LogWarning("Disk size is not an even multiple of 1 MiB; rounding up" |
|
11858 |
" to not overwrite existing data (%s bytes will not be" |
|
11859 |
" wiped)", (1024 * 1024) - remainder) |
|
11860 |
mib += 1 |
|
11861 |
|
|
11862 |
return mib |
|
11863 |
|
|
11864 |
|
|
11835 | 11865 |
class LUInstanceGrowDisk(LogicalUnit): |
11836 | 11866 |
"""Grow a disk of an instance. |
11837 | 11867 |
|
... | ... | |
11933 | 11963 |
assert (self.owned_locks(locking.LEVEL_NODE) == |
11934 | 11964 |
self.owned_locks(locking.LEVEL_NODE_RES)) |
11935 | 11965 |
|
11966 |
wipe_disks = self.cfg.GetClusterInfo().prealloc_wipe_disks |
|
11967 |
|
|
11936 | 11968 |
disks_ok, _ = _AssembleInstanceDisks(self, self.instance, disks=[disk]) |
11937 | 11969 |
if not disks_ok: |
11938 | 11970 |
raise errors.OpExecError("Cannot activate block device to grow") |
... | ... | |
11947 | 11979 |
self.cfg.SetDiskID(disk, node) |
11948 | 11980 |
result = self.rpc.call_blockdev_grow(node, (disk, instance), self.delta, |
11949 | 11981 |
True, True) |
11950 |
result.Raise("Grow request failed to node %s" % node) |
|
11982 |
result.Raise("Dry-run grow request failed to node %s" % node) |
|
11983 |
|
|
11984 |
if wipe_disks: |
|
11985 |
# Get disk size from primary node for wiping |
|
11986 |
result = self.rpc.call_blockdev_getsize(instance.primary_node, [disk]) |
|
11987 |
result.Raise("Failed to retrieve disk size from node '%s'" % |
|
11988 |
instance.primary_node) |
|
11989 |
|
|
11990 |
(disk_size_in_bytes, ) = result.payload |
|
11991 |
|
|
11992 |
if disk_size_in_bytes is None: |
|
11993 |
raise errors.OpExecError("Failed to retrieve disk size from primary" |
|
11994 |
" node '%s'" % instance.primary_node) |
|
11995 |
|
|
11996 |
old_disk_size = _DiskSizeInBytesToMebibytes(self, disk_size_in_bytes) |
|
11997 |
|
|
11998 |
assert old_disk_size >= disk.size, \ |
|
11999 |
("Retrieved disk size too small (got %s, should be at least %s)" % |
|
12000 |
(old_disk_size, disk.size)) |
|
12001 |
else: |
|
12002 |
old_disk_size = None |
|
11951 | 12003 |
|
11952 | 12004 |
# We know that (as far as we can test) operations across different |
11953 | 12005 |
# nodes will succeed, time to run it for real on the backing storage |
... | ... | |
11973 | 12025 |
# Downgrade lock while waiting for sync |
11974 | 12026 |
self.glm.downgrade(locking.LEVEL_INSTANCE) |
11975 | 12027 |
|
12028 |
assert wipe_disks ^ (old_disk_size is None) |
|
12029 |
|
|
12030 |
if wipe_disks: |
|
12031 |
assert instance.disks[self.op.disk] == disk |
|
12032 |
|
|
12033 |
# Wipe newly added disk space |
|
12034 |
_WipeDisks(self, instance, |
|
12035 |
disks=[(self.op.disk, disk, old_disk_size)]) |
|
12036 |
|
|
11976 | 12037 |
if self.op.wait_for_sync: |
11977 | 12038 |
disk_abort = not _WaitForSync(self, instance, disks=[disk]) |
11978 | 12039 |
if disk_abort: |
b/man/gnt-cluster.rst | ||
---|---|---|
268 | 268 |
use for storing the instance disk files when using file storage as |
269 | 269 |
backend for instance disks. |
270 | 270 |
|
271 |
The ``--prealloc-wipe-disks`` sets a cluster wide configuration |
|
272 |
value for wiping disks prior to allocation. This increases security
|
|
273 |
on instance level as the instance can't access untouched data from
|
|
274 |
it's underlying storage.
|
|
271 |
The ``--prealloc-wipe-disks`` sets a cluster wide configuration value
|
|
272 |
for wiping disks prior to allocation and size changes (``gnt-instance
|
|
273 |
grow-disk``). This increases security on instance level as the instance
|
|
274 |
can't access untouched data from its underlying storage.
|
|
275 | 275 |
|
276 | 276 |
The ``--enabled-hypervisors`` option allows you to set the list of |
277 | 277 |
hypervisors that will be enabled for this cluster. Instance |
b/test/ganeti.cmdlib_unittest.py | ||
---|---|---|
1265 | 1265 |
self.history = [] |
1266 | 1266 |
|
1267 | 1267 |
def __call__(self, (disks, instance), pause): |
1268 |
assert instance.disks == disks
|
|
1268 |
assert not (set(disks) - set(instance.disks))
|
|
1269 | 1269 |
|
1270 | 1270 |
self.history.extend((i.logical_id, i.size, pause) |
1271 | 1271 |
for i in disks) |
... | ... | |
1336 | 1336 |
]) |
1337 | 1337 |
|
1338 | 1338 |
def testNormalWipe(self): |
1339 |
pt = _DiskPauseTracker() |
|
1339 |
for start_offset in [0, 280, 8895, 1563204]: |
|
1340 |
pt = _DiskPauseTracker() |
|
1340 | 1341 |
|
1341 |
progress = {} |
|
1342 |
progress = {}
|
|
1342 | 1343 |
|
1343 |
def _WipeCb((disk, _), offset, size): |
|
1344 |
assert isinstance(offset, (long, int)) |
|
1345 |
assert isinstance(size, (long, int)) |
|
1344 |
def _WipeCb((disk, _), offset, size):
|
|
1345 |
assert isinstance(offset, (long, int))
|
|
1346 |
assert isinstance(size, (long, int))
|
|
1346 | 1347 |
|
1347 |
max_chunk_size = (disk.size / 100.0 * constants.MIN_WIPE_CHUNK_PERCENT) |
|
1348 |
max_chunk_size = (disk.size / 100.0 * constants.MIN_WIPE_CHUNK_PERCENT)
|
|
1348 | 1349 |
|
1349 |
self.assertTrue(offset >= 0)
|
|
1350 |
self.assertTrue((offset + size) <= disk.size) |
|
1350 |
self.assertTrue(offset >= start_offset)
|
|
1351 |
self.assertTrue((offset + size) <= disk.size)
|
|
1351 | 1352 |
|
1352 |
self.assertTrue(size > 0) |
|
1353 |
self.assertTrue(size <= constants.MAX_WIPE_CHUNK) |
|
1354 |
self.assertTrue(size <= max_chunk_size) |
|
1353 |
self.assertTrue(size > 0)
|
|
1354 |
self.assertTrue(size <= constants.MAX_WIPE_CHUNK)
|
|
1355 |
self.assertTrue(size <= max_chunk_size)
|
|
1355 | 1356 |
|
1356 |
self.assertTrue(offset == 0 or disk.logical_id in progress)
|
|
1357 |
self.assertTrue(offset == start_offset or disk.logical_id in progress)
|
|
1357 | 1358 |
|
1358 |
# Keep track of progress |
|
1359 |
cur_progress = progress.setdefault(disk.logical_id, 0)
|
|
1360 |
self.assertEqual(cur_progress, offset) |
|
1359 |
# Keep track of progress
|
|
1360 |
cur_progress = progress.setdefault(disk.logical_id, start_offset)
|
|
1361 |
self.assertEqual(cur_progress, offset)
|
|
1361 | 1362 |
|
1362 |
progress[disk.logical_id] += size |
|
1363 |
progress[disk.logical_id] += size
|
|
1363 | 1364 |
|
1364 |
return (True, None) |
|
1365 |
return (True, None)
|
|
1365 | 1366 |
|
1366 |
lu = _FakeLU(rpc=_RpcForDiskWipe(pt, _WipeCb), |
|
1367 |
cfg=_ConfigForDiskWipe()) |
|
1367 |
lu = _FakeLU(rpc=_RpcForDiskWipe(pt, _WipeCb),
|
|
1368 |
cfg=_ConfigForDiskWipe())
|
|
1368 | 1369 |
|
1369 |
disks = [ |
|
1370 |
objects.Disk(dev_type=constants.LD_LV, logical_id="disk0", size=1024), |
|
1371 |
objects.Disk(dev_type=constants.LD_LV, logical_id="disk1", |
|
1372 |
size=500 * 1024), |
|
1373 |
objects.Disk(dev_type=constants.LD_LV, logical_id="disk2", size=128), |
|
1374 |
objects.Disk(dev_type=constants.LD_LV, logical_id="disk3", |
|
1375 |
size=constants.MAX_WIPE_CHUNK), |
|
1376 |
] |
|
1377 |
|
|
1378 |
instance = objects.Instance(name="inst3560", |
|
1379 |
primary_node="node1.example.com", |
|
1380 |
disk_template=constants.DT_PLAIN, |
|
1381 |
disks=disks) |
|
1382 |
|
|
1383 |
cmdlib._WipeDisks(lu, instance) |
|
1384 |
|
|
1385 |
self.assertEqual(pt.history, [ |
|
1386 |
("disk0", 1024, True), |
|
1387 |
("disk1", 500 * 1024, True), |
|
1388 |
("disk2", 128, True), |
|
1389 |
("disk3", constants.MAX_WIPE_CHUNK, True), |
|
1390 |
("disk0", 1024, False), |
|
1391 |
("disk1", 500 * 1024, False), |
|
1392 |
("disk2", 128, False), |
|
1393 |
("disk3", constants.MAX_WIPE_CHUNK, False), |
|
1394 |
]) |
|
1395 |
|
|
1396 |
# Ensure the complete disk has been wiped |
|
1397 |
self.assertEqual(progress, dict((i.logical_id, i.size) for i in disks)) |
|
1370 |
if start_offset > 0: |
|
1371 |
disks = [ |
|
1372 |
objects.Disk(dev_type=constants.LD_LV, logical_id="disk0", |
|
1373 |
size=128), |
|
1374 |
objects.Disk(dev_type=constants.LD_LV, logical_id="disk1", |
|
1375 |
size=start_offset + (100 * 1024)), |
|
1376 |
] |
|
1377 |
else: |
|
1378 |
disks = [ |
|
1379 |
objects.Disk(dev_type=constants.LD_LV, logical_id="disk0", size=1024), |
|
1380 |
objects.Disk(dev_type=constants.LD_LV, logical_id="disk1", |
|
1381 |
size=500 * 1024), |
|
1382 |
objects.Disk(dev_type=constants.LD_LV, logical_id="disk2", size=128), |
|
1383 |
objects.Disk(dev_type=constants.LD_LV, logical_id="disk3", |
|
1384 |
size=constants.MAX_WIPE_CHUNK), |
|
1385 |
] |
|
1386 |
|
|
1387 |
instance = objects.Instance(name="inst3560", |
|
1388 |
primary_node="node1.example.com", |
|
1389 |
disk_template=constants.DT_PLAIN, |
|
1390 |
disks=disks) |
|
1391 |
|
|
1392 |
if start_offset > 0: |
|
1393 |
# Test start offset with only one disk |
|
1394 |
cmdlib._WipeDisks(lu, instance, |
|
1395 |
disks=[(1, disks[1], start_offset)]) |
|
1396 |
self.assertEqual(pt.history, [ |
|
1397 |
("disk1", start_offset + (100 * 1024), True), |
|
1398 |
("disk1", start_offset + (100 * 1024), False), |
|
1399 |
]) |
|
1400 |
self.assertEqual(progress, { |
|
1401 |
"disk1": disks[1].size, |
|
1402 |
}) |
|
1403 |
else: |
|
1404 |
cmdlib._WipeDisks(lu, instance) |
|
1405 |
|
|
1406 |
self.assertEqual(pt.history, [ |
|
1407 |
("disk0", 1024, True), |
|
1408 |
("disk1", 500 * 1024, True), |
|
1409 |
("disk2", 128, True), |
|
1410 |
("disk3", constants.MAX_WIPE_CHUNK, True), |
|
1411 |
("disk0", 1024, False), |
|
1412 |
("disk1", 500 * 1024, False), |
|
1413 |
("disk2", 128, False), |
|
1414 |
("disk3", constants.MAX_WIPE_CHUNK, False), |
|
1415 |
]) |
|
1416 |
|
|
1417 |
# Ensure the complete disk has been wiped |
|
1418 |
self.assertEqual(progress, dict((i.logical_id, i.size) for i in disks)) |
|
1419 |
|
|
1420 |
|
|
1421 |
class TestDiskSizeInBytesToMebibytes(unittest.TestCase): |
|
1422 |
def testLessThanOneMebibyte(self): |
|
1423 |
for i in [1, 2, 7, 512, 1000, 1023]: |
|
1424 |
lu = _FakeLU() |
|
1425 |
result = cmdlib._DiskSizeInBytesToMebibytes(lu, i) |
|
1426 |
self.assertEqual(result, 1) |
|
1427 |
self.assertEqual(len(lu.warning_log), 1) |
|
1428 |
self.assertEqual(len(lu.warning_log[0]), 2) |
|
1429 |
(_, (warnsize, )) = lu.warning_log[0] |
|
1430 |
self.assertEqual(warnsize, (1024 * 1024) - i) |
|
1431 |
|
|
1432 |
def testEven(self): |
|
1433 |
for i in [1, 2, 7, 512, 1000, 1023]: |
|
1434 |
lu = _FakeLU() |
|
1435 |
result = cmdlib._DiskSizeInBytesToMebibytes(lu, i * 1024 * 1024) |
|
1436 |
self.assertEqual(result, i) |
|
1437 |
self.assertFalse(lu.warning_log) |
|
1438 |
|
|
1439 |
def testLargeNumber(self): |
|
1440 |
for i in [1, 2, 7, 512, 1000, 1023, 2724, 12420]: |
|
1441 |
for j in [1, 2, 486, 326, 986, 1023]: |
|
1442 |
lu = _FakeLU() |
|
1443 |
size = (1024 * 1024 * i) + j |
|
1444 |
result = cmdlib._DiskSizeInBytesToMebibytes(lu, size) |
|
1445 |
self.assertEqual(result, i + 1, msg="Amount was not rounded up") |
|
1446 |
self.assertEqual(len(lu.warning_log), 1) |
|
1447 |
self.assertEqual(len(lu.warning_log[0]), 2) |
|
1448 |
(_, (warnsize, )) = lu.warning_log[0] |
|
1449 |
self.assertEqual(warnsize, (1024 * 1024) - j) |
|
1398 | 1450 |
|
1399 | 1451 |
|
1400 | 1452 |
if __name__ == "__main__": |
Also available in: Unified diff