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