def maybe_get_writable_device_path(devpath, info, log): """Return updated devpath if the devpath is a writable block device. @param devpath: Requested path to the root device we want to resize. @param info: String representing information about the requested device. @param log: Logger to which logs will be added upon error. @returns devpath or updated devpath per kernel commandline if the device path is a writable block device, returns None otherwise. """ container = util.is_container() # Ensure the path is a block device. if (devpath == "/dev/root" and not os.path.exists(devpath) and not container): devpath = util.rootdev_from_cmdline(util.get_cmdline()) if devpath is None: log.warn("Unable to find device '/dev/root'") return None log.debug("Converted /dev/root to '%s' per kernel cmdline", devpath) if devpath == 'overlayroot': log.debug("Not attempting to resize devpath '%s': %s", devpath, info) return None # FreeBSD zpool can also just use gpt/<label> # with that in mind we can not do an os.stat on "gpt/whatever" # therefore return the devpath already here. if devpath.startswith('gpt/'): log.debug('We have a gpt label - just go ahead') return devpath try: statret = os.stat(devpath) except OSError as exc: if container and exc.errno == errno.ENOENT: log.debug( "Device '%s' did not exist in container. " "cannot resize: %s", devpath, info) elif exc.errno == errno.ENOENT: log.warn("Device '%s' did not exist. cannot resize: %s", devpath, info) else: raise exc return None if not stat.S_ISBLK(statret.st_mode) and not stat.S_ISCHR(statret.st_mode): if container: log.debug("device '%s' not a block device in container." " cannot resize: %s" % (devpath, info)) else: log.warn("device '%s' not a block device. cannot resize: %s" % (devpath, info)) return None return devpath # The writable block devpath
def maybe_install_squashfuse(cloud): """Install squashfuse if we are in a container.""" if not util.is_container(): return try: cloud.distro.update_package_sources() except Exception: util.logexc(LOG, "Package update failed") raise try: cloud.distro.install_packages(['squashfuse']) except Exception: util.logexc(LOG, "Failed to install squashfuse") raise
def gather_timestamps_using_systemd(): """ Gather timestamps that corresponds to kernel begin initialization, kernel finish initialization. and cloud-init systemd unit activation :return: the three timestamps """ kernel_start = float(time.time()) - float(util.uptime()) try: delta_k_end = SystemctlReader( "UserspaceTimestampMonotonic" ).parse_epoch_as_float() delta_ci_s = SystemctlReader( "InactiveExitTimestampMonotonic", "cloud-init-local" ).parse_epoch_as_float() base_time = kernel_start status = SUCCESS_CODE # lxc based containers do not set their monotonic zero point to be when # the container starts, instead keep using host boot as zero point # time.CLOCK_MONOTONIC_RAW is only available in python 3.3 if util.is_container(): # clock.monotonic also uses host boot as zero point if sys.version_info >= (3, 3): base_time = float(time.time()) - float(time.monotonic()) # TODO: lxcfs automatically truncates /proc/uptime to seconds # in containers when https://github.com/lxc/lxcfs/issues/292 # is fixed, util.uptime() should be used instead of stat on try: file_stat = os.stat("/proc/1/cmdline") kernel_start = file_stat.st_atime except OSError as err: raise RuntimeError( "Could not determine container boot " "time from /proc/1/cmdline. ({})".format(err) ) from err status = CONTAINER_CODE else: status = FAIL_CODE kernel_end = base_time + delta_k_end cloudinit_sysd = base_time + delta_ci_s except Exception as e: # Except ALL exceptions as Systemctl reader can throw many different # errors, but any failure in systemctl means that timestamps cannot be # obtained print(e) return TIMESTAMP_UNKNOWN return status, kernel_start, kernel_end, cloudinit_sysd
def devent2dev(devent): if devent.startswith("/dev/"): return devent else: result = util.get_mount_info(devent) if not result: raise ValueError("Could not determine device of '%s' % dev_ent") dev = result[0] container = util.is_container() # Ensure the path is a block device. if (dev == "/dev/root" and not os.path.exists(dev) and not container): dev = util.rootdev_from_cmdline(util.get_cmdline()) if dev is None: raise ValueError("Unable to find device '/dev/root'") return dev
def read_dmi_data(key): """ Wrapper for reading DMI data. If running in a container return None. This is because DMI data is assumed to be not useful in a container as it does not represent the container but rather the host. This will do the following (returning the first that produces a result): 1) Use a mapping to translate `key` from dmidecode naming to sysfs naming and look in /sys/class/dmi/... for a value. 2) Use `key` as a sysfs key directly and look in /sys/class/dmi/... 3) Fall-back to passing `key` to `dmidecode --string`. If all of the above fail to find a value, None will be returned. """ if is_container(): return None if is_FreeBSD(): return _read_kenv(key) syspath_value = _read_dmi_syspath(key) if syspath_value is not None: return syspath_value def is_x86(arch): return (arch == 'x86_64' or (arch[0] == 'i' and arch[2:] == '86')) # running dmidecode can be problematic on some arches (LP: #1243287) uname_arch = os.uname()[4] if not (is_x86(uname_arch) or uname_arch in ('aarch64', 'amd64')): LOG.debug("dmidata is not supported on %s", uname_arch) return None dmidecode_path = subp.which('dmidecode') if dmidecode_path: return _call_dmidecode(key, dmidecode_path) LOG.debug("did not find either path %s or dmidecode command", DMI_SYS_PATH) return None
def devent2dev(devent): if devent.startswith("/dev/"): return devent else: result = util.get_mount_info(devent) if not result: raise ValueError("Could not determine device of '%s' % dev_ent") dev = result[0] container = util.is_container() # Ensure the path is a block device. if dev == "/dev/root" and not container: dev = util.rootdev_from_cmdline(util.get_cmdline()) if dev is None: if os.path.exists(dev): # if /dev/root exists, but we failed to convert # that to a "real" /dev/ path device, then return it. return dev raise ValueError("Unable to find device '/dev/root'") return dev
def devent2dev(devent): if devent.startswith("/dev/"): return devent else: result = util.get_mount_info(devent) if not result: raise ValueError("Could not determine device of '%s' % dev_ent") dev = result[0] container = util.is_container() # Ensure the path is a block device. if (dev == "/dev/root" and not container): dev = util.rootdev_from_cmdline(util.get_cmdline()) if dev is None: if os.path.exists(dev): # if /dev/root exists, but we failed to convert # that to a "real" /dev/ path device, then return it. return dev raise ValueError("Unable to find device '/dev/root'") return dev
def handle(name, cfg, _cloud, log, args): if len(args) != 0: resize_root = args[0] else: resize_root = util.get_cfg_option_str(cfg, "resize_rootfs", True) if not util.translate_bool(resize_root, addons=[NOBLOCK]): log.debug("Skipping module named %s, resizing disabled", name) return # TODO(harlowja) is the directory ok to be used?? resize_root_d = util.get_cfg_option_str(cfg, "resize_rootfs_tmp", "/run") util.ensure_dir(resize_root_d) # TODO(harlowja): allow what is to be resized to be configurable?? resize_what = "/" result = util.get_mount_info(resize_what, log) if not result: log.warn("Could not determine filesystem type of %s", resize_what) return (devpth, fs_type, mount_point) = result info = "dev=%s mnt_point=%s path=%s" % (devpth, mount_point, resize_what) log.debug("resize_info: %s" % info) container = util.is_container() # Ensure the path is a block device. if (devpth == "/dev/root" and not os.path.exists(devpth) and not container): devpth = util.rootdev_from_cmdline(util.get_cmdline()) if devpth is None: log.warn("Unable to find device '/dev/root'") return log.debug("Converted /dev/root to '%s' per kernel cmdline", devpth) try: statret = os.stat(devpth) except OSError as exc: if container and exc.errno == errno.ENOENT: log.debug( "Device '%s' did not exist in container. " "cannot resize: %s", devpth, info) elif exc.errno == errno.ENOENT: log.warn("Device '%s' did not exist. cannot resize: %s", devpth, info) else: raise exc return if not os.access(devpth, os.W_OK): if container: log.debug("'%s' not writable in container. cannot resize: %s", devpth, info) else: log.warn("'%s' not writable. cannot resize: %s", devpth, info) return if not stat.S_ISBLK(statret.st_mode) and not stat.S_ISCHR(statret.st_mode): if container: log.debug("device '%s' not a block device in container." " cannot resize: %s" % (devpth, info)) else: log.warn("device '%s' not a block device. cannot resize: %s" % (devpth, info)) return resizer = None if can_skip_resize(fs_type, resize_what, devpth): log.debug("Skip resize filesystem type %s for %s", fs_type, resize_what) return fstype_lc = fs_type.lower() for (pfix, root_cmd) in RESIZE_FS_PREFIXES_CMDS: if fstype_lc.startswith(pfix): resizer = root_cmd break if not resizer: log.warn("Not resizing unknown filesystem type %s for %s", fs_type, resize_what) return resize_cmd = resizer(resize_what, devpth) log.debug("Resizing %s (%s) using %s", resize_what, fs_type, ' '.join(resize_cmd)) if resize_root == NOBLOCK: # Fork to a child that will run # the resize command util.fork_cb(util.log_time, logfunc=log.debug, msg="backgrounded Resizing", func=do_resize, args=(resize_cmd, log)) else: util.log_time(logfunc=log.debug, msg="Resizing", func=do_resize, args=(resize_cmd, log)) action = 'Resized' if resize_root == NOBLOCK: action = 'Resizing (via forking)' log.debug("%s root filesystem (type=%s, val=%s)", action, fs_type, resize_root)
def handle(name, cfg, _cloud, log, args): if len(args) != 0: resize_root = args[0] else: resize_root = util.get_cfg_option_str(cfg, "resize_rootfs", True) if not util.translate_bool(resize_root, addons=[NOBLOCK]): log.debug("Skipping module named %s, resizing disabled", name) return # TODO(harlowja) is the directory ok to be used?? resize_root_d = util.get_cfg_option_str(cfg, "resize_rootfs_tmp", "/run") util.ensure_dir(resize_root_d) # TODO(harlowja): allow what is to be resized to be configurable?? resize_what = "/" result = util.get_mount_info(resize_what, log) if not result: log.warn("Could not determine filesystem type of %s", resize_what) return (devpth, fs_type, mount_point) = result # Ensure the path is a block device. info = "dev=%s mnt_point=%s path=%s" % (devpth, mount_point, resize_what) log.debug("resize_info: %s" % info) container = util.is_container() if (devpth == "/dev/root" and not os.path.exists(devpth) and not container): devpth = rootdev_from_cmdline(util.get_cmdline()) if devpth is None: log.warn("Unable to find device '/dev/root'") return log.debug("Converted /dev/root to '%s' per kernel cmdline", devpth) try: statret = os.stat(devpth) except OSError as exc: if container and exc.errno == errno.ENOENT: log.debug("Device '%s' did not exist in container. " "cannot resize: %s" % (devpth, info)) elif exc.errno == errno.ENOENT: log.warn("Device '%s' did not exist. cannot resize: %s" % (devpth, info)) else: raise exc return if not stat.S_ISBLK(statret.st_mode) and not stat.S_ISCHR(statret.st_mode): if container: log.debug("device '%s' not a block device in container." " cannot resize: %s" % (devpth, info)) else: log.warn("device '%s' not a block device. cannot resize: %s" % (devpth, info)) return resizer = None fstype_lc = fs_type.lower() for (pfix, root_cmd) in RESIZE_FS_PREFIXES_CMDS: if fstype_lc.startswith(pfix): resizer = root_cmd break if not resizer: log.warn("Not resizing unknown filesystem type %s for %s", fs_type, resize_what) return resize_cmd = resizer(resize_what, devpth) log.debug("Resizing %s (%s) using %s", resize_what, fs_type, ' '.join(resize_cmd)) if resize_root == NOBLOCK: # Fork to a child that will run # the resize command util.fork_cb( util.log_time(logfunc=log.debug, msg="backgrounded Resizing", func=do_resize, args=(resize_cmd, log))) else: util.log_time(logfunc=log.debug, msg="Resizing", func=do_resize, args=(resize_cmd, log)) action = 'Resized' if resize_root == NOBLOCK: action = 'Resizing (via forking)' log.debug("%s root filesystem (type=%s, val=%s)", action, fs_type, resize_root)
def maybe_get_writable_device_path(devpath, info, log): """Return updated devpath if the devpath is a writable block device. @param devpath: Requested path to the root device we want to resize. @param info: String representing information about the requested device. @param log: Logger to which logs will be added upon error. @returns devpath or updated devpath per kernel commandline if the device path is a writable block device, returns None otherwise. """ container = util.is_container() # Ensure the path is a block device. if (devpath == "/dev/root" and not os.path.exists(devpath) and not container): devpath = util.rootdev_from_cmdline(util.get_cmdline()) if devpath is None: log.warn("Unable to find device '/dev/root'") return None log.debug("Converted /dev/root to '%s' per kernel cmdline", devpath) if devpath == 'overlayroot': log.debug("Not attempting to resize devpath '%s': %s", devpath, info) return None # FreeBSD zpool can also just use gpt/<label> # with that in mind we can not do an os.stat on "gpt/whatever" # therefore return the devpath already here. if devpath.startswith('gpt/'): log.debug('We have a gpt label - just go ahead') return devpath # Alternatively, our device could simply be a name as returned by gpart, # such as da0p3 if not devpath.startswith('/dev/') and not os.path.exists(devpath): fulldevpath = '/dev/' + devpath.lstrip('/') log.debug("'%s' doesn't appear to be a valid device path. Trying '%s'", devpath, fulldevpath) devpath = fulldevpath try: statret = os.stat(devpath) except OSError as exc: if container and exc.errno == errno.ENOENT: log.debug("Device '%s' did not exist in container. " "cannot resize: %s", devpath, info) elif exc.errno == errno.ENOENT: log.warn("Device '%s' did not exist. cannot resize: %s", devpath, info) else: raise exc return None if not stat.S_ISBLK(statret.st_mode) and not stat.S_ISCHR(statret.st_mode): if container: log.debug("device '%s' not a block device in container." " cannot resize: %s" % (devpath, info)) else: log.warn("device '%s' not a block device. cannot resize: %s" % (devpath, info)) return None return devpath # The writable block devpath
def handle(_name, cfg, _cloud, log, args): if len(args) != 0: resize_root = False if str(args[0]).lower() in ['true', '1', 'on', 'yes']: resize_root = True else: resize_root = util.get_cfg_option_str(cfg, "resize_rootfs", True) if str(resize_root).lower() in ['false', '0']: return # we use mktemp rather than mkstemp because early in boot nothing # else should be able to race us for this, and we need to mknod. devpth = tempfile.mktemp(prefix="cloudinit.resizefs.", dir="/run") try: st_dev = os.stat("/").st_dev dev = os.makedev(os.major(st_dev), os.minor(st_dev)) os.mknod(devpth, 0400 | stat.S_IFBLK, dev) except: if util.is_container(): log.debug("inside container, ignoring mknod failure in resizefs") return log.warn("Failed to make device node to resize /") raise cmd = ['blkid', '-c', '/dev/null', '-sTYPE', '-ovalue', devpth] try: (fstype, _err) = util.subp(cmd) except subprocess.CalledProcessError as e: log.warn("Failed to get filesystem type of maj=%s, min=%s via: %s" % (os.major(st_dev), os.minor(st_dev), cmd)) log.warn("output=%s\nerror=%s\n", e.output[0], e.output[1]) os.unlink(devpth) raise if str(fstype).startswith("ext"): resize_cmd = ['resize2fs', devpth] elif fstype == "xfs": resize_cmd = ['xfs_growfs', devpth] else: os.unlink(devpth) log.debug("not resizing unknown filesystem %s" % fstype) return if resize_root == "noblock": fid = os.fork() if fid == 0: try: do_resize(resize_cmd, devpth, log) os._exit(0) # pylint: disable=W0212 except Exception as exc: sys.stderr.write("Failed: %s" % exc) os._exit(1) # pylint: disable=W0212 else: do_resize(resize_cmd, devpth, log) log.debug("resizing root filesystem (type=%s, maj=%i, min=%i, val=%s)" % (str(fstype).rstrip("\n"), os.major(st_dev), os.minor(st_dev), resize_root)) return