Beispiel #1
0
def add_swap(cfg, target, fstab):
    # add swap file per cfg to filesystem root at target. update fstab.
    #
    # swap:
    #  filename: 'swap.img',
    #  size: None # (or 1G)
    #  maxsize: 2G
    if 'swap' in cfg and not cfg.get('swap'):
        LOG.debug("disabling 'add_swap' due to config")
        return

    swapcfg = cfg.get('swap', {})
    fname = swapcfg.get('filename', None)
    size = swapcfg.get('size', None)
    maxsize = swapcfg.get('maxsize', None)

    if size:
        size = util.human2bytes(str(size))
    if maxsize:
        maxsize = util.human2bytes(str(maxsize))

    swap.setup_swapfile(target=target,
                        fstab=fstab,
                        swapfile=fname,
                        size=size,
                        maxsize=maxsize)
Beispiel #2
0
def detect_required_packages(cfg):
    """
    detect packages that will be required in-target by custom config items
    """

    mapping = {
        'storage': block.detect_required_packages_mapping(),
        'network': net.detect_required_packages_mapping(),
    }

    needed_packages = []
    for cfg_type, cfg_map in mapping.items():

        # skip missing or invalid config items, configs may
        # only have network or storage, not always both
        if not isinstance(cfg.get(cfg_type), dict):
            continue

        cfg_version = cfg[cfg_type].get('version')
        if not isinstance(cfg_version, int) or cfg_version not in cfg_map:
            msg = ('Supplied configuration version "%s", for config type'
                   '"%s" is not present in the known mapping.' %
                   (cfg_version, cfg_type))
            raise ValueError(msg)

        mapped_config = cfg_map[cfg_version]
        found_reqs = mapped_config['handler'](cfg, mapped_config['mapping'])
        needed_packages.extend(found_reqs)

    LOG.debug('Curtin config dependencies requires additional packages: %s',
              needed_packages)
    return needed_packages
Beispiel #3
0
def extract_root_layered_fsimage_url(uri, target):
    ''' Build images list to consider from a layered structure

    uri: URI of the layer file
    target: Target file system to provision

    return: None
    '''
    path = _path_from_file_url(uri)

    image_stack = _get_image_stack(path)
    LOG.debug("Considering fsimages: '%s'", ",".join(image_stack))

    tmp_dir = None
    try:
        # Download every remote images if remote url
        if url_helper.urlparse(path).scheme != "":
            tmp_dir = tempfile.mkdtemp()
            image_stack = _download_layered_images(image_stack, tmp_dir)

        # Check that all images exists on disk and are not empty
        for img in image_stack:
            if not os.path.isfile(img) or os.path.getsize(img) <= 0:
                raise ValueError(
                    "Failed to use fsimage: '%s' doesn't exist " +
                    "or is invalid", img)

        return _extract_root_layered_fsimage(image_stack, target)
    finally:
        if tmp_dir and os.path.exists(tmp_dir):
            shutil.rmtree(tmp_dir)
Beispiel #4
0
def render_network_state(target, network_state):
    LOG.debug("rendering eni from netconfig")
    eni = 'etc/network/interfaces'
    netrules = 'etc/udev/rules.d/70-persistent-net.rules'
    cc = 'etc/cloud/cloud.cfg.d/curtin-disable-cloudinit-networking.cfg'

    eni = os.path.sep.join((
        target,
        eni,
    ))
    LOG.info('Writing ' + eni)
    util.write_file(eni, content=render_interfaces(network_state))

    netrules = os.path.sep.join((
        target,
        netrules,
    ))
    LOG.info('Writing ' + netrules)
    util.write_file(netrules, content=render_persistent_net(network_state))

    cc_disable = os.path.sep.join((
        target,
        cc,
    ))
    LOG.info('Writing ' + cc_disable)
    util.write_file(cc_disable, content='network: {config: disabled}\n')
Beispiel #5
0
def is_connected(devname):
    # is_connected isn't really as simple as that.  2 is
    # 'physically connected'. 3 is 'not connected'. but a wlan interface will
    # always show 3.
    try:
        iflink = read_sys_net(devname, "iflink", enoent=False)
        if iflink == "2":
            return True
        if not is_wireless(devname):
            return False
        LOG.debug("'%s' is wireless, basing 'connected' on carrier", devname)

        return read_sys_net(devname,
                            "carrier",
                            enoent=False,
                            keyerror=False,
                            translate={
                                '0': False,
                                '1': True
                            })

    except IOError as e:
        if e.errno == errno.EINVAL:
            return False
        raise
Beispiel #6
0
def disable_overlayroot(cfg, target):
    # cloud images come with overlayroot, but installed systems need disabled
    disable = cfg.get('disable_overlayroot', True)
    local_conf = os.path.sep.join([target, 'etc/overlayroot.local.conf'])
    if disable and os.path.exists(local_conf):
        LOG.debug("renaming %s to %s", local_conf, local_conf + ".old")
        shutil.move(local_conf, local_conf + ".old")
Beispiel #7
0
def quick_zero(path, partitions=True, exclusive=True):
    """
    zero 1M at front, 1M at end, and 1M at front
    if this is a block device and partitions is true, then
    zero 1M at front and end of each partition.
    """
    buflen = 1024
    count = 1024
    zero_size = buflen * count
    offsets = [0, -zero_size]
    is_block = is_block_device(path)
    if not (is_block or os.path.isfile(path)):
        raise ValueError("%s: not an existing file or block device", path)

    pt_names = []
    if partitions and is_block:
        ptdata = sysfs_partition_data(path)
        for kname, ptnum, start, size in ptdata:
            pt_names.append((dev_path(kname), kname, ptnum))
        pt_names.reverse()

    for (pt, kname, ptnum) in pt_names:
        LOG.debug('Wiping path: dev:%s kname:%s partnum:%s', pt, kname, ptnum)
        quick_zero(pt, partitions=False)

    LOG.debug("wiping 1M on %s at offsets %s", path, offsets)
    return zero_file_at_offsets(path,
                                offsets,
                                buflen=buflen,
                                count=count,
                                exclusive=exclusive)
Beispiel #8
0
def apt_command(args):
    """ Main entry point for curtin apt-config standalone command
        This does not read the global config as handled by curthooks, but
        instead one can specify a different "target" and a new cfg via --config
        """
    cfg = config.load_command_config(args, {})

    if args.target is not None:
        target = args.target
    else:
        state = util.load_command_environment()
        target = state['target']

    if target is None:
        sys.stderr.write("Unable to find target.  "
                         "Use --target or set TARGET_MOUNT_POINT\n")
        sys.exit(2)

    apt_cfg = cfg.get("apt")
    # if no apt config section is available, do nothing
    if apt_cfg is not None:
        LOG.debug("Handling apt to target %s with config %s", target, apt_cfg)
        try:
            with util.ChrootableTarget(target, sys_resolvconf=True):
                handle_apt(apt_cfg, target)
        except (RuntimeError, TypeError, ValueError, IOError):
            LOG.exception("Failed to configure apt features '%s'", apt_cfg)
            sys.exit(1)
    else:
        LOG.info("No apt config provided, skipping")

    sys.exit(0)
Beispiel #9
0
def disconnect_target_disks(target_root_path=None):
    target_nodes_path = paths.target_path(target_root_path, '/etc/iscsi/nodes')
    fails = []
    if os.path.isdir(target_nodes_path):
        for target in os.listdir(target_nodes_path):
            if target not in iscsiadm_sessions():
                LOG.debug('iscsi target %s not active, skipping', target)
                continue
            # conn is "host,port,lun"
            for conn in os.listdir(
                            os.path.sep.join([target_nodes_path, target])):
                host, port, _ = conn.split(',')
                try:
                    util.subp(['sync'])
                    iscsiadm_logout(target, '%s:%s' % (host, port))
                except util.ProcessExecutionError as e:
                    fails.append(target)
                    LOG.warn("Unable to logout of iSCSI target %s: %s",
                             target, e)
    else:
        LOG.warning('Skipping disconnect: failed to find iscsi nodes path: %s',
                    target_nodes_path)
    if fails:
        raise RuntimeError(
            "Unable to logout of iSCSI targets: %s" % ', '.join(fails))
Beispiel #10
0
def fail_device(mddev, arraydev):
    assert_valid_devpath(mddev)

    LOG.info("mdadm mark faulty: %s in array %s", arraydev, mddev)
    out, err = util.subp(["mdadm", "--fail", mddev, arraydev],
                         rcs=[0], capture=True)
    LOG.debug("mdadm mark faulty:\n%s\n%s", out, err)
Beispiel #11
0
def md_check(md_devname, raidlevel, devices, spares, container):
    ''' Check passed in variables from storage configuration against
        the system we're running upon.
    '''
    LOG.debug('RAID validation: ' +
              'name={} raidlevel={} devices={} spares={} container={}'.format(
                  md_devname, raidlevel, devices, spares, container))
    assert_valid_devpath(md_devname)

    detail = mdadm_query_detail(md_devname)

    if raidlevel != "container":
        md_check_array_state(md_devname)
    md_check_raidlevel(md_devname, detail, raidlevel)
    md_check_uuid(md_devname)
    if container is None:
        md_check_devices(md_devname, devices)
        md_check_spares(md_devname, spares)
        md_check_array_membership(md_devname, devices + spares)
    else:
        if 'MD_CONTAINER' not in detail:
            raise ValueError("%s is not in a container" % (md_devname))
        actual_container = os.path.realpath(detail['MD_CONTAINER'])
        if actual_container != container:
            raise ValueError("%s is in container %r, not %r" %
                             (md_devname, actual_container, container))

    LOG.debug('RAID array OK: ' + md_devname)
Beispiel #12
0
    def needs_formatting(self, blksize, layout, volser):
        """ Determine if DasdDevice attributes matches the required parameters.

        Note that devices that indicate they are unformatted will require
        formatting.

        :param blksize: expected blocksize of the device.
        :param layout: expected disk layout.
        :param volser: expected label, if None, label is ignored.
        :returns: boolean, True if formatting is needed, else False.
        """
        LOG.debug('Checking if dasd %s needs formatting', self.device_id)
        if self.is_not_formatted():
            LOG.debug('dasd %s is not formatted', self.device_id)
            return True

        if int(blksize) != int(self.blocksize()):
            LOG.debug('dasd %s block size (%s) does not match (%s)',
                      self.device_id, self.blocksize(), blksize)
            return True

        if layout != self.disk_layout():
            LOG.debug('dasd %s disk layout (%s) does not match %s',
                      self.device_id, self.disk_layout(), layout)
            return True

        if volser and volser != self.label():
            LOG.debug('dasd %s volser (%s) does not match %s',
                      self.device_id, self.label(), volser)
            return True

        return False
Beispiel #13
0
def shutdown_lvm(device):
    """
    Shutdown specified lvm device.
    """
    device = block.sys_block_path(device)
    # lvm devices have a dm directory that containes a file 'name' containing
    # '{volume group}-{logical volume}'. The volume can be freed using lvremove
    name_file = os.path.join(device, 'dm', 'name')
    lvm_name = util.load_file(name_file).strip()
    (vg_name, lv_name) = lvm.split_lvm_name(lvm_name)
    vg_lv_name = "%s/%s" % (vg_name, lv_name)
    devname = "/dev/" + vg_lv_name

    # wipe contents of the logical volume first
    LOG.info('Wiping lvm logical volume: %s', devname)
    block.quick_zero(devname, partitions=False)

    # remove the logical volume
    LOG.debug('using "lvremove" on %s', vg_lv_name)
    util.subp(['lvremove', '--force', '--force', vg_lv_name])

    # if that was the last lvol in the volgroup, get rid of volgroup
    if len(lvm.get_lvols_in_volgroup(vg_name)) == 0:
        pvols = lvm.get_pvols_in_volgroup(vg_name)
        util.subp(['vgremove', '--force', '--force', vg_name], rcs=[0, 5])

        # wipe the underlying physical volumes
        for pv in pvols:
            LOG.info('Wiping lvm physical volume: %s', pv)
            block.quick_zero(pv, partitions=False)

    # refresh lvmetad
    lvm.lvm_scan()
Beispiel #14
0
    def from_fdasd(cls, devname):
        """Use fdasd to construct a DasdPartitionTable.

        % fdasd --table /dev/dasdc
        reading volume label ..: VOL1
        reading vtoc ..........: ok


        Disk /dev/dasdc:
          cylinders ............: 10017
          tracks per cylinder ..: 15
          blocks per track .....: 12
          bytes per block ......: 4096
          volume label .........: VOL1
          volume serial ........: 0X1522
          max partitions .......: 3

         ------------------------------- tracks -------------------------------
                       Device      start      end   length   Id  System
                  /dev/dasdc1          2    43694    43693    1  Linux native
                  /dev/dasdc2      43695    87387    43693    2  Linux native
                  /dev/dasdc3      87388   131080    43693    3  Linux native
                                  131081   150254    19174       unused
        exiting...
        """
        cmd = ['fdasd', '--table', devname]
        out, _err = util.subp(cmd, capture=True)
        LOG.debug("from_fdasd output:\n---\n%s\n---\n", out)
        return cls.from_fdasd_output(devname, out)
Beispiel #15
0
def mdadm_remove(devpath):
    assert_valid_devpath(devpath)

    LOG.info("mdadm removing: %s" % devpath)
    out, err = util.subp(["mdadm", "--remove", devpath],
                         rcs=[0], capture=True)
    LOG.debug("mdadm remove:\n%s\n%s", out, err)
Beispiel #16
0
def apply_preserve_sources_list(target):
    # protect the just generated sources.list from cloud-init
    cloudfile = "/etc/cloud/cloud.cfg.d/curtin-preserve-sources.cfg"

    target_ver = distro.get_package_version('cloud-init', target=target)
    if not target_ver:
        LOG.info(
            "Attempt to read cloud-init version from target returned "
            "'%s', not writing preserve_sources_list config.", target_ver)
        return

    cfg = {'apt': {'preserve_sources_list': True}}
    if target_ver['major'] < 1:
        # anything cloud-init 0.X.X will get the old config key.
        cfg = {'apt_preserve_sources_list': True}

    try:
        util.write_file(paths.target_path(target, cloudfile),
                        config.dump_config(cfg),
                        mode=0o644)
        LOG.debug("Set preserve_sources_list to True in %s with: %s",
                  cloudfile, cfg)
    except IOError:
        LOG.exception(
            "Failed to protect /etc/apt/sources.list from cloud-init in '%s'",
            cloudfile)
        raise
Beispiel #17
0
def lookup_disk(serial):
    """
    Search for a disk by its serial number using /dev/disk/by-id/
    """
    # Get all volumes in /dev/disk/by-id/ containing the serial string. The
    # string specified can be either in the short or long serial format
    # hack, some serials have spaces, udev usually converts ' ' -> '_'
    serial_udev = serial.replace(' ', '_')
    LOG.info('Processing serial %s via udev to %s', serial, serial_udev)

    disks = list(
        filter(lambda x: serial_udev in x, os.listdir("/dev/disk/by-id/")))
    if not disks or len(disks) < 1:
        raise ValueError("no disk with serial '%s' found" % serial_udev)

    # Sort by length and take the shortest path name, as the longer path names
    # will be the partitions on the disk. Then use os.path.realpath to
    # determine the path to the block device in /dev/
    disks.sort(key=lambda x: len(x))
    LOG.debug('lookup_disks found: %s', disks)
    path = os.path.realpath("/dev/disk/by-id/%s" % disks[0])
    # /dev/dm-X
    if multipath.is_mpath_device(path):
        info = udevadm_info(path)
        path = os.path.join('/dev/mapper', info['DM_NAME'])
    # /dev/sdX
    elif multipath.is_mpath_member(path):
        mp_name = multipath.find_mpath_id_by_path(path)
        path = os.path.join('/dev/mapper', mp_name)

    if not os.path.exists(path):
        raise ValueError("path '%s' to block device for disk with serial '%s' \
            does not exist" % (path, serial_udev))
    LOG.debug('block.lookup_disk() returning path %s', path)
    return path
Beispiel #18
0
def mdadm_assemble(md_devname=None, devices=[], spares=[], scan=False,
                   ignore_errors=False):
    # md_devname is a /dev/XXXX
    # devices is non-empty list of /dev/xxx
    # if spares is non-empt list append of /dev/xxx
    cmd = ["mdadm", "--assemble"]
    if scan:
        cmd += ['--scan', '-v']
    else:
        valid_mdname(md_devname)
        cmd += [md_devname, "--run"] + devices
        if spares:
            cmd += spares

    try:
        # mdadm assemble returns 1 when no arrays are found. this might not be
        # an error depending on the situation this function was called in, so
        # accept a return code of 1
        # mdadm assemble returns 2 when called on an array that is already
        # assembled. this is not an error, so accept return code of 2
        # all other return codes can be accepted with ignore_error set to true
        scan, err = util.subp(cmd, capture=True, rcs=[0, 1, 2])
        LOG.debug('mdadm assemble scan results:\n%s\n%s', scan, err)
        scan, err = util.subp(['mdadm', '--detail', '--scan', '-v'],
                              capture=True, rcs=[0, 1])
        LOG.debug('mdadm detail scan after assemble:\n%s\n%s',
                  scan, err)
    except util.ProcessExecutionError:
        LOG.warning("mdadm_assemble had unexpected return code")
        if not ignore_errors:
            raise

    udev.udevadm_settle()
Beispiel #19
0
def is_mpath_partition(devpath):
    if devpath.startswith('/dev/dm-'):
        if 'DM_PART' in udev.udevadm_info(devpath):
            LOG.debug("%s is multipath device partition", devpath)
            return True

    return False
Beispiel #20
0
def wipe_file(path, reader=None, buflen=4 * 1024 * 1024, exclusive=True):
    """
    wipe the existing file at path.
    if reader is provided, it will be called as a 'reader(buflen)'
    to provide data for each write.  Otherwise, zeros are used.
    writes will be done in size of buflen.
    """
    if reader:
        readfunc = reader
    else:
        buf = buflen * b'\0'

        def readfunc(size):
            return buf

    size = util.file_size(path)
    LOG.debug("%s is %s bytes. wiping with buflen=%s", path, size, buflen)

    with exclusive_open(path, exclusive=exclusive) as fp:
        while True:
            pbuf = readfunc(buflen)
            pos = fp.tell()
            if len(pbuf) != buflen and len(pbuf) + pos < size:
                raise ValueError(
                    "short read on reader got %d expected %d after %d" %
                    (len(pbuf), buflen, pos))

            if pos + buflen >= size:
                fp.write(pbuf[0:size - pos])
                break
            else:
                fp.write(pbuf)
Beispiel #21
0
def force_devmapper_symlinks():
    """Check if /dev/mapper/mpath* files are symlinks, if not trigger udev."""
    LOG.debug('Verifying /dev/mapper/mpath* files are symlinks')
    needs_trigger = []
    for mp_id, dm_dev in dmname_to_blkdev_mapping().items():
        if mp_id.startswith('mpath'):
            mapper_path = '/dev/mapper/' + mp_id
            if not os.path.islink(mapper_path):
                LOG.warning(
                    'Found invalid device mapper mp path: %s, removing',
                    mapper_path)
                util.del_file(mapper_path)
                needs_trigger.append((mapper_path, dm_dev))

    if len(needs_trigger):
        for (mapper_path, dm_dev) in needs_trigger:
            LOG.debug('multipath: regenerating symlink for %s (%s)',
                      mapper_path, dm_dev)
            util.subp([
                'udevadm', 'trigger', '--subsystem-match=block',
                '--action=add', '/sys/class/block/' + os.path.basename(dm_dev)
            ])
            udev.udevadm_settle(exists=mapper_path)
            if not os.path.islink(mapper_path):
                LOG.error('Failed to regenerate udev symlink %s', mapper_path)
Beispiel #22
0
def get_backing_device(bcache_kname):
    """ For a given bcacheN kname, return the backing device
        bcache sysfs dir.

        bcache0 -> /sys/.../devices/.../device/bcache
    """
    bcache_deps = '/sys/class/block/%s/slaves' % bcache_kname

    try:
        # if the bcache device is deleted, this may fail
        deps = os.listdir(bcache_deps)
    except util.FileMissingError as e:
        LOG.debug('Transient race, bcache slave path not found: %s', e)
        return None

    # a running bcache device has two entries in slaves, the cacheset
    # device, and the backing device. There may only be the backing
    # device (if a bcache device is found but not currently attached
    # to a cacheset.
    if len(deps) == 0:
        raise RuntimeError('%s unexpected empty dir: %s' %
                           (bcache_kname, bcache_deps))

    for dev in (sysfs_path(dep) for dep in deps):
        if is_backing(dev):
            return dev

    return None
Beispiel #23
0
def get_iscsi_disks_from_config(cfg):
    """Return a list of IscsiDisk objects for each iscsi volume present."""
    # Construct IscsiDisk objects for each iscsi volume present
    iscsi_disks = [IscsiDisk(volume) for volume in
                   get_iscsi_volumes_from_config(cfg)]
    LOG.debug('Found %s iscsi disks in storage config', len(iscsi_disks))
    return iscsi_disks
Beispiel #24
0
def remove_device(mddev, arraydev):
    assert_valid_devpath(mddev)

    LOG.info("mdadm remove %s from array %s", arraydev, mddev)
    out, err = util.subp(["mdadm", "--remove", mddev, arraydev],
                         rcs=[0], capture=True)
    LOG.debug("mdadm remove:\n%s\n%s", out, err)
Beispiel #25
0
def dpkg_reconfigure(packages, target=None):
    # For any packages that are already installed, but have preseed data
    # we populate the debconf database, but the filesystem configuration
    # would be preferred on a subsequent dpkg-reconfigure.
    # so, what we have to do is "know" information about certain packages
    # to unconfigure them.
    unhandled = []
    to_config = []
    for pkg in packages:
        if pkg in CONFIG_CLEANERS:
            LOG.debug("unconfiguring %s", pkg)
            CONFIG_CLEANERS[pkg](target)
            to_config.append(pkg)
        else:
            unhandled.append(pkg)

    if len(unhandled):
        LOG.warn(
            "The following packages were installed and preseeded, "
            "but cannot be unconfigured: %s", unhandled)

    if len(to_config):
        util.subp(['dpkg-reconfigure', '--frontend=noninteractive'] +
                  list(to_config),
                  data=None,
                  target=target,
                  capture=True)
Beispiel #26
0
def apply_debconf_selections(cfg, target=None):
    """apply_debconf_selections - push content to debconf"""
    # debconf_selections:
    #  set1: |
    #   cloud-init cloud-init/datasources multiselect MAAS
    #  set2: pkg pkg/value string bar
    selsets = cfg.get('debconf_selections')
    if not selsets:
        LOG.debug("debconf_selections was not set in config")
        return

    LOG.debug('Applying debconf selections')
    selections = '\n'.join([selsets[key] for key in sorted(selsets.keys())])
    debconf_set_selections(selections.encode() + b"\n", target=target)

    # get a complete list of packages listed in input
    pkgs_cfgd = set()
    for key, content in selsets.items():
        for line in content.splitlines():
            if line.startswith("#"):
                continue
            pkg = re.sub(r"[:\s].*", "", line)
            pkgs_cfgd.add(pkg)

    pkgs_installed = distro.get_installed_packages(target)
    need_reconfig = pkgs_cfgd.intersection(pkgs_installed)
    if len(need_reconfig) == 0:
        return

    dpkg_reconfigure(need_reconfig, target=target)
Beispiel #27
0
def clean_cloud_init(target):
    """clean out any local cloud-init config"""
    flist = glob.glob(
        paths.target_path(target, "/etc/cloud/cloud.cfg.d/*dpkg*"))

    LOG.debug("cleaning cloud-init config from: %s", flist)
    for dpkg_cfg in flist:
        os.unlink(dpkg_cfg)
Beispiel #28
0
def hook(args):
    if not args.target:
        raise ValueError("Target must be provided or set in environment")

    LOG.debug("Finalizing %s" % args.target)
    curtin.util.run_hook_if_exists(args.target, "finalize")

    sys.exit(0)
Beispiel #29
0
def iscsiadm_logout(target, portal):
    LOG.debug('iscsiadm_logout: target=%s portal=%s', target, portal)

    cmd = ['iscsiadm', '--mode=node', '--targetname=%s' % target,
           '--portal=%s' % portal, '--logout']
    util.subp(cmd, capture=True, log_captured=True)

    udev.udevadm_settle()
Beispiel #30
0
def iscsiadm_set_automatic(target, portal):
    LOG.debug('iscsiadm_set_automatic: target=%s portal=%s', target, portal)

    cmd = ['iscsiadm', '--mode=node', '--targetname=%s' % target,
           '--portal=%s' % portal, '--op=update',
           '--name=node.startup', '--value=automatic']

    util.subp(cmd, capture=True, log_captured=True)