~smoser/ubuntu/yakkety/curtin/pkg

« back to all changes in this revision

Viewing changes to curtin/commands/block_meta.py

  • Committer: Scott Moser
  • Date: 2016-08-05 20:51:16 UTC
  • mfrom: (1.1.40)
  • Revision ID: smoser@ubuntu.com-20160805205116-hm08b4uokfhn92hv
* New upstream snapshot.
  - Add apt configuration features. (LP: #1574113)
  - mkfs.vfat: add force flag for formating whole disks (LP: #1597923)
  - block.mkfs: fix sectorsize flag (LP: #1597522)
  - block_meta: cleanup use of sys_block_path and handle cciss knames
    (LP: #1562249)
  - block.get_blockdev_sector_size: handle _lsblock multi result return
    (LP: #1598310)
  - util: add target (chroot) support to subp, add target_path helper
  - block_meta: fallback to parted if blkid does not produce output
    (LP: #1524031)
  - commands.block_wipe:  correct default wipe mode to 'superblock'

Show diffs side-by-side

added added

removed removed

Lines of Context:
28
28
import glob
29
29
import os
30
30
import platform
31
 
import re
 
31
import string
32
32
import sys
33
33
import tempfile
34
34
import time
129
129
    return "mbr"
130
130
 
131
131
 
132
 
def block_find_sysfs_path(devname):
133
 
    # return the path in sys for device named devname
134
 
    # support either short name ('sda') or full path /dev/sda
135
 
    #  sda -> /sys/class/block/sda
136
 
    #  sda1 -> /sys/class/block/sda/sda1
137
 
    if not devname:
138
 
        raise ValueError("empty devname provided to find_sysfs_path")
139
 
 
140
 
    sys_class_block = '/sys/class/block/'
141
 
    basename = os.path.basename(devname)
142
 
    # try without parent blockdevice, then prepend parent
143
 
    paths = [
144
 
        os.path.join(sys_class_block, basename),
145
 
        os.path.join(sys_class_block,
146
 
                     re.split('[\d+]', basename)[0], basename),
147
 
    ]
148
 
 
149
 
    # find path to devname directory in sysfs
150
 
    devname_sysfs = None
151
 
    for path in paths:
152
 
        if os.path.exists(path):
153
 
            devname_sysfs = path
154
 
 
155
 
    if devname_sysfs is None:
156
 
        err = ('No sysfs path to device:'
157
 
               ' {}'.format(devname_sysfs))
158
 
        LOG.error(err)
159
 
        raise ValueError(err)
160
 
 
161
 
    return devname_sysfs
162
 
 
163
 
 
164
132
def get_holders(devname):
165
133
    # Look up any block device holders.
166
134
    # Handle devices and partitions as devnames (vdb, md0, vdb7)
167
 
    devname_sysfs = block_find_sysfs_path(devname)
 
135
    devname_sysfs = block.sys_block_path(devname)
168
136
    if devname_sysfs:
169
137
        holders = os.listdir(os.path.join(devname_sysfs, 'holders'))
170
138
        LOG.debug("devname '%s' had holders: %s", devname, ','.join(holders))
236
204
 
237
205
    if os.path.exists(os.path.join(sys_block_path, "md")):
238
206
        # md device
239
 
        block_dev = os.path.join("/dev/", os.path.split(sys_block_path)[-1])
240
 
        # if these fail its okay, the array might not be assembled and thats
241
 
        # fine
 
207
        block_dev_kname = block.path_to_kname(sys_block_path)
 
208
        block_dev = block.kname_to_path(block_dev_kname)
242
209
        mdadm.mdadm_stop(block_dev)
243
210
        mdadm.mdadm_remove(block_dev)
244
211
 
265
232
    raise OSError('Failed to find device at path: %s', devpath)
266
233
 
267
234
 
268
 
def determine_partition_kname(disk_kname, partition_number):
269
 
    for dev_type in ["nvme", "mmcblk"]:
270
 
        if disk_kname.startswith(dev_type):
271
 
            partition_number = "p%s" % partition_number
272
 
            break
273
 
    return "%s%s" % (disk_kname, partition_number)
274
 
 
275
 
 
276
235
def determine_partition_number(partition_id, storage_config):
277
236
    vol = storage_config.get(partition_id)
278
237
    partnumber = vol.get('number')
304
263
    return partnumber
305
264
 
306
265
 
 
266
def sanitize_dname(dname):
 
267
    """
 
268
    dnames should be sanitized before writing rule files, in case maas has
 
269
    emitted a dname with a special character
 
270
 
 
271
    only letters, numbers and '-' and '_' are permitted, as this will be
 
272
    used for a device path. spaces are also not permitted
 
273
    """
 
274
    valid = string.digits + string.ascii_letters + '-_'
 
275
    return ''.join(c if c in valid else '-' for c in dname)
 
276
 
 
277
 
307
278
def make_dname(volume, storage_config):
308
279
    state = util.load_command_environment()
309
280
    rules_dir = os.path.join(state['scratch'], "rules.d")
321
292
    # we may not always be able to find a uniq identifier on devices with names
322
293
    if not ptuuid and vol.get('type') in ["disk", "partition"]:
323
294
        LOG.warning("Can't find a uuid for volume: {}. Skipping dname.".format(
324
 
            dname))
 
295
            volume))
325
296
        return
326
297
 
327
298
    rule = [
346
317
        volgroup_name = storage_config.get(vol.get('volgroup')).get('name')
347
318
        dname = "%s-%s" % (volgroup_name, dname)
348
319
        rule.append(compose_udev_equality("ENV{DM_NAME}", dname))
349
 
    rule.append("SYMLINK+=\"disk/by-dname/%s\"" % dname)
 
320
    else:
 
321
        raise ValueError('cannot make dname for device with type: {}'
 
322
                         .format(vol.get('type')))
 
323
 
 
324
    # note: this sanitization is done here instead of for all name attributes
 
325
    #       at the beginning of storage configuration, as some devices, such as
 
326
    #       lvm devices may use the name attribute and may permit special chars
 
327
    sanitized = sanitize_dname(dname)
 
328
    if sanitized != dname:
 
329
        LOG.warning(
 
330
            "dname modified to remove invalid chars. old: '{}' new: '{}'"
 
331
            .format(dname, sanitized))
 
332
 
 
333
    rule.append("SYMLINK+=\"disk/by-dname/%s\"" % sanitized)
350
334
    LOG.debug("Writing dname udev rule '{}'".format(str(rule)))
351
335
    util.ensure_dir(rules_dir)
352
 
    with open(os.path.join(rules_dir, volume), "w") as fp:
353
 
        fp.write(', '.join(rule))
 
336
    rule_file = os.path.join(rules_dir, '{}.rules'.format(sanitized))
 
337
    util.write_file(rule_file, ', '.join(rule))
354
338
 
355
339
 
356
340
def get_path_to_storage_volume(volume, storage_config):
368
352
        partnumber = determine_partition_number(vol.get('id'), storage_config)
369
353
        disk_block_path = get_path_to_storage_volume(vol.get('device'),
370
354
                                                     storage_config)
371
 
        (base_path, disk_kname) = os.path.split(disk_block_path)
372
 
        partition_kname = determine_partition_kname(disk_kname, partnumber)
373
 
        volume_path = os.path.join(base_path, partition_kname)
 
355
        disk_kname = block.path_to_kname(disk_block_path)
 
356
        partition_kname = block.partition_kname(disk_kname, partnumber)
 
357
        volume_path = block.kname_to_path(partition_kname)
374
358
        devsync_vol = os.path.join(disk_block_path)
375
359
 
376
360
    elif vol.get('type') == "disk":
419
403
        # block devs are in the slaves dir there. Then, those blockdevs can be
420
404
        # checked against the kname of the devs in the config for the desired
421
405
        # bcache device. This is not very elegant though
422
 
        backing_device_kname = os.path.split(get_path_to_storage_volume(
423
 
            vol.get('backing_device'), storage_config))[-1]
 
406
        backing_device_path = get_path_to_storage_volume(
 
407
            vol.get('backing_device'), storage_config)
 
408
        backing_device_kname = block.path_to_kname(backing_device_path)
424
409
        sys_path = list(filter(lambda x: backing_device_kname in x,
425
410
                               glob.glob("/sys/block/bcache*/slaves/*")))[0]
426
411
        while "bcache" not in os.path.split(sys_path)[-1]:
427
412
            sys_path = os.path.split(sys_path)[0]
428
 
        volume_path = os.path.join("/dev", os.path.split(sys_path)[-1])
 
413
        bcache_kname = block.path_to_kname(sys_path)
 
414
        volume_path = block.kname_to_path(bcache_kname)
429
415
        LOG.debug('got bcache volume path {}'.format(volume_path))
430
416
 
431
417
    else:
452
438
            # Don't need to check state, return
453
439
            return
454
440
 
455
 
        # Check state of current ptable
 
441
        # Check state of current ptable, try to do this using blkid, but if
 
442
        # blkid fails then try to fall back to using parted.
 
443
        _possible_errors = (util.ProcessExecutionError, StopIteration,
 
444
                            IndexError, AttributeError)
456
445
        try:
457
446
            (out, _err) = util.subp(["blkid", "-o", "export", disk],
458
447
                                    capture=True)
459
 
        except util.ProcessExecutionError:
460
 
            raise ValueError("disk '%s' has no readable partition table or \
461
 
                cannot be accessed, but preserve is set to true, so cannot \
462
 
                continue")
463
 
        current_ptable = list(filter(lambda x: "PTTYPE" in x,
464
 
                                     out.splitlines()))[0].split("=")[-1]
465
 
        if current_ptable == "dos" and ptable != "msdos" or \
466
 
                current_ptable == "gpt" and ptable != "gpt":
467
 
            raise ValueError("disk '%s' does not have correct \
468
 
                partition table, but preserve is set to true, so not \
469
 
                creating table, so not creating table." % info.get('id'))
470
 
        LOG.info("disk '%s' marked to be preserved, so keeping partition \
471
 
                 table")
 
448
            current_ptable = next(l.split('=')[1] for l in out.splitlines()
 
449
                                  if 'TYPE' in l)
 
450
        except _possible_errors:
 
451
            try:
 
452
                (out, _err) = util.subp(["parted", disk, "--script", "print"],
 
453
                                        capture=True)
 
454
                current_ptable = next(l.split()[-1] for l in out.splitlines()
 
455
                                      if "Partition Table" in l)
 
456
            except _possible_errors:
 
457
                raise ValueError("disk '%s' has no readable partition table "
 
458
                                 "or cannot be accessed, but preserve is set "
 
459
                                 "to true, so cannot continue" % disk)
 
460
        if not (current_ptable == ptable or
 
461
                (current_ptable == "dos" and ptable == "msdos")):
 
462
            raise ValueError("disk '%s' does not have correct "
 
463
                             "partition table, but preserve is "
 
464
                             "set to true, so not creating table."
 
465
                             % info.get('id'))
 
466
        LOG.info("disk '%s' marked to be preserved, so keeping partition "
 
467
                 "table" % disk)
472
468
        return
473
469
 
474
470
    # Wipe the disk
475
471
    if info.get('wipe') and info.get('wipe') != "none":
476
472
        # The disk has a lable, clear all partitions
477
473
        mdadm.mdadm_assemble(scan=True)
478
 
        disk_kname = os.path.split(disk)[-1]
479
 
        syspath_partitions = list(
 
474
        disk_sysfs_path = block.sys_block_path(disk)
 
475
        sysfs_partitions = list(
480
476
            os.path.split(prt)[0] for prt in
481
 
            glob.glob("/sys/block/%s/*/partition" % disk_kname))
482
 
        for partition in syspath_partitions:
 
477
            glob.glob(os.path.join(disk_sysfs_path, '*', 'partition')))
 
478
        for partition in sysfs_partitions:
483
479
            clear_holders(partition)
484
480
            with open(os.path.join(partition, "dev"), "r") as fp:
485
481
                block_no = fp.read().rstrip()
487
483
                os.path.join("/dev/block", block_no))
488
484
            block.wipe_volume(partition_path, mode=info.get('wipe'))
489
485
 
490
 
        clear_holders("/sys/block/%s" % disk_kname)
 
486
        clear_holders(disk_sysfs_path)
491
487
        block.wipe_volume(disk, mode=info.get('wipe'))
492
488
 
493
489
    # Create partition table on disk
542
538
 
543
539
    disk = get_path_to_storage_volume(device, storage_config)
544
540
    partnumber = determine_partition_number(info.get('id'), storage_config)
545
 
 
546
 
    disk_kname = os.path.split(
547
 
        get_path_to_storage_volume(device, storage_config))[-1]
 
541
    disk_kname = block.path_to_kname(disk)
 
542
    disk_sysfs_path = block.sys_block_path(disk)
548
543
    # consider the disks logical sector size when calculating sectors
549
544
    try:
550
 
        prefix = "/sys/block/%s/queue/" % disk_kname
551
 
        with open(prefix + "logical_block_size", "r") as f:
 
545
        lbs_path = os.path.join(disk_sysfs_path, 'queue', 'logical_block_size')
 
546
        with open(lbs_path, 'r') as f:
552
547
            l = f.readline()
553
548
            logical_block_size_bytes = int(l)
554
549
    except:
566
561
                    extended_part_no = determine_partition_number(
567
562
                        key, storage_config)
568
563
                    break
569
 
            partition_kname = determine_partition_kname(
570
 
                disk_kname, extended_part_no)
571
 
            previous_partition = "/sys/block/%s/%s/" % \
572
 
                (disk_kname, partition_kname)
 
564
            pnum = extended_part_no
573
565
        else:
574
566
            pnum = find_previous_partition(device, info['id'], storage_config)
575
 
            LOG.debug("previous partition number for '%s' found to be '%s'",
576
 
                      info.get('id'), pnum)
577
 
            partition_kname = determine_partition_kname(disk_kname, pnum)
578
 
            previous_partition = "/sys/block/%s/%s/" % \
579
 
                (disk_kname, partition_kname)
 
567
 
 
568
        LOG.debug("previous partition number for '%s' found to be '%s'",
 
569
                  info.get('id'), pnum)
 
570
        partition_kname = block.partition_kname(disk_kname, pnum)
 
571
        previous_partition = os.path.join(disk_sysfs_path, partition_kname)
580
572
        LOG.debug("previous partition: {}".format(previous_partition))
581
573
        # XXX: sys/block/X/{size,start} is *ALWAYS* in 512b value
582
574
        previous_size = util.load_file(os.path.join(previous_partition,
1033
1025
 
1034
1026
    if cache_device:
1035
1027
        # /sys/class/block/XXX/YYY/
1036
 
        cache_device_sysfs = block_find_sysfs_path(cache_device)
 
1028
        cache_device_sysfs = block.sys_block_path(cache_device)
1037
1029
 
1038
1030
        if os.path.exists(os.path.join(cache_device_sysfs, "bcache")):
1039
1031
            LOG.debug('caching device already exists at {}/bcache. Read '
1058
1050
        ensure_bcache_is_registered(cache_device, target_sysfs_path)
1059
1051
 
1060
1052
    if backing_device:
1061
 
        backing_device_sysfs = block_find_sysfs_path(backing_device)
 
1053
        backing_device_sysfs = block.sys_block_path(backing_device)
1062
1054
        target_sysfs_path = os.path.join(backing_device_sysfs, "bcache")
1063
1055
        if not os.path.exists(os.path.join(backing_device_sysfs, "bcache")):
1064
1056
            util.subp(["make-bcache", "-B", backing_device])