Пример #1
0
def create_id_dir(id_dir: Path, group: str, umask: int) -> (int, Path):
    """In the given directory, create the lowest numbered (positive integer)
    directory that doesn't already exist.

    :param id_dir: Path to the directory that contains these 'id'
        directories
    :param group: The group owner for this path.
    :param umask: The umask to apply to this path.
    :returns: The id and path to the created directory.
    :raises OSError: on directory creation failure.
    :raises TimeoutError: If we couldn't get the lock in time.
"""

    lockfile_path = id_dir/'.lockfile'
    with lockfile.LockFile(lockfile_path, timeout=1):
        next_fn = id_dir/PKEY_FN

        next_valid = True

        if next_fn.exists():
            try:
                with next_fn.open() as next_file:
                    next_id = int(next_file.read())

                next_id_path = make_id_path(id_dir, next_id)

                if next_id_path.exists():
                    next_valid = False

            except (OSError, ValueError):
                # In either case, on failure, invalidate the next file.
                next_valid = False
        else:
            next_valid = False

        if not next_valid:
            # If the next file's id wasn't valid, then find the next available
            # id directory the hard way.

            ids = list(os.listdir(str(id_dir)))
            # Only return the test directories that could be integers.
            ids = [id_ for id_ in ids if id_.isdigit()]
            ids = [int(id_) for id_ in ids]
            ids.sort()

            # Find the first unused id.
            next_id = 1
            while next_id in ids:
                next_id += 1

            next_id_path = make_id_path(id_dir, next_id)

        with permissions.PermissionsManager(next_id_path, group, umask), \
                permissions.PermissionsManager(next_fn, group, umask):
            next_id_path.mkdir()
            with next_fn.open('w') as next_file:
                next_file.write(str(next_id + 1))

        return next_id, next_id_path
Пример #2
0
def index(id_dir: Path, idx_name: str,
          transform: Callable[[Path], Dict[str, Any]],
          complete_key: str = 'complete',
          refresh_period: int = 1,
          remove_missing: bool = False,
          verbose: IO[str] = None,
          fn_base: int = 10) -> Index:
    """Load and/or update an index of the given directory for the given
    transform, and return it. The returned index is a dictionary by id of
    the transformed data.

    :param id_dir: The directory to index.
    :param idx_name: The name of the index.
    :param transform: A transformation function that produces a json
        compatible dictionary.
    :param complete_key: The key in the transformed dictionary that marks a
        record as complete. If not given, the record is always assumed to be
        complete. Incomplete records are recompiled every time the index is
        updated (hopefully they will be complete eventually).
    :param refresh_period: Only update the index if this much time (in seconds)
        has passed since the last update.
    :param remove_missing: Remove items that no longer exist from the index.
    :param fn_base: The integer base for dir_db.
    """

    idx_path = (id_dir/idx_name).with_suffix('.db')

    idx = Index({})
    idx_db = None

    # Open and read the index if it exists. Any errors cause the index to
    # regenerate from scratch.
    idx_mtime = 0
    if idx_path.exists():
        try:
            idx_db = dbm.open(idx_path.as_posix())
            idx_mtime = idx_path.stat().st_mtime
            idx = Index({int(k): json.loads(idx_db[k]) for k in idx_db.keys()})
        except (OSError, PermissionError, json.JSONDecodeError) as err:
            # In either error case, start from scratch.
            LOGGER.warning(
                "Error reading index at '%s'. Regenerating from "
                "scratch. %s", idx_path.as_posix(), err.args[0])

    new_items = {}

    # If the index hasn't been updated lately (or is empty) do so.
    # Small updates should happen unnoticeably fast, while full generation
    # will take a bit.
    if not idx or time.time() - idx_mtime > refresh_period:
        seen = []

        if verbose:
            if idx_db is None:
                output.fprint(
                    "No index file found for '{}'. This may take a while."
                    .format(id_dir.name),
                    file=verbose)

        last_perc = None
        progress = 0

        files = list(os.scandir(id_dir.as_posix()))
        for file in files:
            try:
                id_ = int(file.name, fn_base)
            except ValueError:
                continue

            seen.append(id_)

            progress += 1
            complete_perc = int(100*progress/len(files))
            if verbose and complete_perc != last_perc:
                output.fprint(
                    "Indexing: {}%".format(complete_perc),
                    end='\r', file=verbose)
                last_perc = complete_perc

            # Skip entries that are known and complete.
            if id_ in idx and idx[id_].get(complete_key, False):
                continue

            # Only directories with integer names are db entries.
            if not file.is_dir():
                continue

            # Update incomplete or unknown entries.
            try:
                new = transform(Path(file.path))
            except ValueError:
                continue

            old = idx.get(id_)
            if new != old:
                new_items[id_] = new
                idx[id_] = new

        missing = set(idx.keys()) - set(seen)

        if new_items or missing:
            try:
                group = idx_path.parent.stat().st_gid
                tmp_path = Path(tempfile.mktemp(
                    suffix='.dbtmp',
                    dir=idx_path.parent.as_posix()))
                if idx_path.exists():
                    try:
                        shutil.copyfile(idx_path, tmp_path.as_posix())
                    except OSError:
                        pass

                # Write our updated index atomically.
                with permissions.PermissionsManager(tmp_path,
                                                    group, 0o002):
                    out_db = dbm.open(tmp_path.as_posix(), 'c')

                    for id_, value in new_items.items():
                        out_db[str(id_)] = json.dumps(value)

                    for id_ in missing:

                        del out_db[str(id_)]
                        del idx[id_]

                tmp_path.rename(idx_path)
            except OSError:
                pass

    return idx
Пример #3
0
def main():
    """Setup Pavilion and run a command."""

    # Pavilion is compatible with python >= 3.4
    if sys.version_info[0] != 3 or sys.version_info[1] < 5:
        output.fprint("Pavilion requires python 3.5 or higher.",
                      color=output.RED,
                      file=sys.stderr)
        sys.exit(-1)

    # This has to be done before we initialize plugins
    parser = arguments.get_parser()

    # Get the config, and
    try:
        pav_cfg = config.find()
    except Exception as err:
        output.fprint(
            "Error getting config, exiting: {}"
            .format(err),
            file=sys.stderr,
            color=output.RED)
        sys.exit(-1)

    # Create the basic directories in the working directory and the .pavilion
    # directory.
    perm_man = permissions.PermissionsManager(None, pav_cfg['shared_group'],
                                              pav_cfg['umask'])
    for path in [
            config.USER_HOME_PAV,
            config.USER_HOME_PAV/'working_dir',
            pav_cfg.working_dir/'builds',
            pav_cfg.working_dir/'series',
            pav_cfg.working_dir/'test_runs',
            pav_cfg.working_dir/'users']:
        try:
            path = path.expanduser()
            path.mkdir(exist_ok=True)
            perm_man.set_perms(path)
        except OSError as err:
            output.fprint(
                "Could not create base directory '{}': {}"
                .format(path, err),
                color=output.RED,
                file=sys.stderr,
            )
            sys.exit(1)

    # Setup all the loggers for Pavilion
    if not log_setup.setup_loggers(pav_cfg):
        sys.exit(1)

    # Initialize all the plugins
    try:
        plugins.initialize_plugins(pav_cfg)
    except plugins.PluginError as err:
        output.fprint(
            "Error initializing plugins: {}"
            .format(err),
            color=output.RED,
            file=sys.stderr)
        sys.exit(-1)

    # Parse the arguments
    try:
        args = parser.parse_args()
    except Exception:
        raise

    if args.command_name is None:
        parser.print_help()
        sys.exit(0)

    pav_cfg.pav_vars = pavilion_variables.PavVars()

    if not args.profile:
        run_cmd(pav_cfg, args)

    else:
        import cProfile
        import pstats

        stats_path = '/tmp/{}_pav_pstats'.format(os.getlogin())

        cProfile.runctx('run_cmd(pav_cfg, args)', globals(), locals(),
                        stats_path)
        stats = pstats.Stats(stats_path)
        print("Profile Table")
        stats.strip_dirs().sort_stats(args.profile_sort)\
             .print_stats(args.profile_count)