예제 #1
0
def test_client_defaults(mocker):
    get_session_mock = mocker.patch("faculty.session.get_session")
    for_resource_mock = mocker.patch("faculty.clients.for_resource")

    faculty.client("test-resource")

    for_resource_mock.assert_called_once_with("test-resource")
    get_session_mock.assert_called_once_with(
        credentials_path=None,
        profile_name=None,
        domain=None,
        protocol=None,
        client_id=None,
        client_secret=None,
        access_token_cache=None,
    )

    returned_class = for_resource_mock.return_value
    returned_session = get_session_mock.return_value

    returned_session.service_url.assert_called_once_with(
        returned_class.SERVICE_NAME)

    returned_class.assert_called_once_with(
        returned_session.service_url.return_value, returned_session)
예제 #2
0
def test_client(mocker):
    get_session_mock = mocker.patch("faculty.session.get_session")
    for_resource_mock = mocker.patch("faculty.clients.for_resource")

    options = {
        "credentials_path": "/path/to/credentials",
        "profile_name": "my-profile",
        "domain": "domain.com",
        "protocol": "http",
        "client_id": "client-id",
        "client_secret": "client-secret",
        "access_token_cache": mocker.Mock(),
    }

    faculty.client("test-resource", **options)

    for_resource_mock.assert_called_once_with("test-resource")
    get_session_mock.assert_called_once_with(**options)

    returned_class = for_resource_mock.return_value
    returned_session = get_session_mock.return_value

    returned_session.service_url.assert_called_once_with(
        returned_class.SERVICE_NAME)

    returned_class.assert_called_once_with(
        returned_session.service_url.return_value, returned_session)
예제 #3
0
def publish(
    report_name: str,
    path: Path,
    show_code: bool = False,
    report_id: Optional[UUID] = None,
    project_id: Optional[UUID] = None,
    user_id: Optional[UUID] = None,
):
    """Publish a notebook as a report.

    Parameters
    ----------
    report_name : str
        The name of the report
    path : Path
        The path of the notebook.
    report_id : Optional[UUID], optional
        The report ID, if you want to publish it as a version of an existing
        report (the default is None, in which case we search for an existing
        report with the provided name)
    project_id : Optional[UUID], optional
        The project ID. Only needed if not invoking from within a project.
    user_id : Optional[UUID], optional
        The user ID. Only needed if not invoking from within a project.
    show_code : bool, optional
        Whether the code should be shown in the report or not (default False)
    """

    if project_id is None:
        project_id = UUID(os.getenv("SHERLOCKML_PROJECT_ID"))

    if user_id is None:
        user_id = faculty.client("account").authenticated_user_id()

    report_client = faculty.client("report")

    if report_id is None:
        report_id = get_report_id_by_name(report_name, project_id)

    if report_id is not None:
        report_client.create_version(
            report_id,
            str(path.relative_to("/project/")),
            user_id,
            show_code=show_code,
        )
        click.echo("Publishing report version...")
    else:
        report_client.create(
            project_id,
            report_name,
            str(path.relative_to("/project/")),
            user_id,
            show_code=show_code,
        )
        click.echo("Publishing report...")
    # this is to allow farah to process the notebook before deleting it
    time.sleep(5)
    click.echo("Done!")
예제 #4
0
def test_client(mocker):
    get_session_mock = mocker.patch("faculty.session.get_session")
    for_resource_mock = mocker.patch("faculty.clients.for_resource")

    options = {"foo": "bar"}
    faculty.client("test-resource", **options)

    get_session_mock.assert_called_once_with(**options)
    for_resource_mock.assert_called_once_with("test-resource")

    returned_session = get_session_mock.return_value
    returned_class = for_resource_mock.return_value
    returned_class.assert_called_once_with(returned_session)
예제 #5
0
def status(project, server):
    """Get the execution status for an environment."""
    project_id, server_id = _resolve_server(project, server)

    server_client = faculty.client("server")
    server = server_client.get(project_id, server_id)

    hound_url = _get_hound_url(server)

    client = faculty_cli.hound.Hound(hound_url)
    execution = client.latest_environment_execution()

    if execution is None:
        msg = "No environment has yet been applied to this server."
        _print_and_exit(msg, 64)

    click.echo("Latest environment execution:")
    click.echo("  Status: {}".format(execution.status))
    for i, environment in enumerate(execution.environments):
        click.echo("")
        click.echo("Environment {}".format(i))
        for j, step in enumerate(environment.steps):
            click.echo("")
            click.echo("Step {}:".format(j))
            click.echo("  Status:  {}".format(step.status))
            click.echo("  Command: {}".format(_format_command(step.command)))
예제 #6
0
def _server_by_name(project_id, server_name, status=None):
    """Resolve a project ID and server name to a server ID."""
    client = faculty.client("server")
    matching_servers = [
        server
        for server in client.list(project_id)
        if server.name == server_name
    ]
    if status is not None:
        matching_servers = [
            server
            for server in matching_servers
            if server.status.value == status
        ]
    if len(matching_servers) == 1:
        return matching_servers[0]
    else:
        if not matching_servers:
            tpl = 'no {} server of name "{}" in this project'
        else:
            tpl = (
                'more than one {} server of name "{}", please select by '
                "server ID instead"
            )
        adjective = "available" if status is None else status
        raise NoValidServer(tpl.format(adjective, server_name))
예제 #7
0
def logs(project, server, step_number):
    """Stream the logs for a server environment application."""
    project_id, server_id = _resolve_server(project, server)

    server_client = faculty.client("server")
    server = server_client.get(project_id, server_id)

    hound_url = _get_hound_url(server)

    client = faculty_cli.hound.Hound(hound_url)
    execution = client.latest_environment_execution()

    if execution is None:
        msg = "No environment has yet been applied to this server."
        _print_and_exit(msg, 64)

    steps = [
        step for environment_execution in execution.environments
        for step in environment_execution.steps
    ]

    if step_number is not None:
        try:
            steps = [steps[step_number]]
        except IndexError:
            _print_and_exit("step {} out of range".format(step_number), 64)

    for step in steps:
        for line in client.stream_environment_execution_step_logs(step):
            click.echo(line)
예제 #8
0
    def __init__(self, project_id, job_id, clean=True, tmpdir_prefix=None):
        self.project_id = project_id
        self.job_id = job_id
        self.job_client = client("job")

        self.clean = clean
        self.tmpdir_prefix = tmpdir_prefix
예제 #9
0
def instance_types(verbose):
    """List the types of servers available on dedicated infrastructure."""
    client = faculty.client("cluster")
    types = client.list_single_tenanted_node_types(
        interactive_instances_configured=True)
    types = sorted(types, key=operator.attrgetter("cost_usd_per_hour"))

    if verbose:
        if not types:
            click.echo("No servers on dedicated infrastructure available.")

        else:
            headers = (
                "Machine Type",
                "CPUs",
                "RAM",
                "GPUs",
                "GPU Name",
                "Cost",
            )
            rows = [(
                type_.name,
                "{:.3g}".format(type_.milli_cpus / 1000),
                "{:.3g} GB".format(type_.memory_mb / 1000),
                type_.num_gpus or "-",
                type_.gpu_name or "-",
                "$ {:.3f} / hour".format(type_.cost_usd_per_hour),
            ) for type_ in types]
            click.echo(tabulate(rows, headers, tablefmt="plain"))

    else:
        for type_ in types:
            click.echo(type_.name)
예제 #10
0
def _list_user_servers(user_id, status=None):
    """List all servers owned by user."""
    client = faculty.client("server")
    servers = client.list_for_user(user_id)
    if status is not None:
        servers = [s for s in servers if s.status == status]
    return servers
예제 #11
0
def ls(project, path):
    """List files and directories on Faculty workspace."""
    if not path.startswith("/project"):
        _print_and_exit("{} is outside the project workspace".format(path), 66)

    project_id = _resolve_project(project)
    relative_path = os.path.relpath(path, "/project")
    client = faculty.client("workspace")

    try:
        directory_details_list = client.list(project_id=project_id,
                                             prefix=relative_path,
                                             depth=1)
    except faculty.clients.base.NotFound:
        _print_and_exit("{}: No such file or directory".format(path), 66)

    try:
        [directory_details] = directory_details_list
    except ValueError:
        _print_and_exit("Zero or more than one objects returned", 70)

    for item in directory_details.content:
        if hasattr(item, "content"):
            click.echo("/project{}/".format(item.path))
        else:
            click.echo("/project{}".format(item.path))
예제 #12
0
def _get_servers(project_id, name=None, status=None):
    """List servers in the given project."""
    client = faculty.client("server")
    servers = client.list(project_id, name)
    if status is not None:
        servers = [s for s in servers if s.status == status]
    return servers
예제 #13
0
def job_name_to_job_id(job_name, project_id=None):
    """
    Queries faculty platform so as to convert a specified job name into its
    corresponding job id.

    Parameters
    ----------

    job_name: String
        Job name to query the platform for.

    project_id: uuid
        Unique id of the project on the platform.

    Returns
    -------

    job_id: uuid
        Unique job id corresponding to the specified job name and project.
    """

    if project_id is None:
        project_id = os.environ["FACULTY_PROJECT_ID"]
    job_client = client("job")
    for job in job_client.list(project_id):
        if job.metadata.name == job_name:
            return job.id
예제 #14
0
def _rsync(project, local, remote, server, rsync_opts, up):
    """Sync files from or to server."""

    project_id, server_id = _resolve_server(project, server)

    client = faculty.client("server")
    details = client.get_ssh_details(project_id, server_id)

    escaped_remote = faculty_cli.shell.quote(remote)
    if up:
        path_from = local
        path_to = u"{}@{}:{}".format(details.username, details.hostname,
                                     escaped_remote)
    else:
        path_from = u"{}@{}:{}".format(details.username, details.hostname,
                                       escaped_remote)
        path_to = local

    with _save_key_to_file(details.key) as filename:
        ssh_cmd = "ssh {} -p {} -i {}".format(" ".join(SSH_OPTIONS),
                                              details.port, filename)

        rsync_cmd = ["rsync", "-a", "-e", ssh_cmd, path_from, path_to]
        rsync_cmd += list(rsync_opts)

        _run_ssh_cmd(rsync_cmd)
예제 #15
0
    def _run(self, num_subruns):
        """
        Acquire the specified compute resources and execute the
        cross_validation task.
        
        Parameters
        ---------- 
        
        num_subruns: Integer
            Number of subruns within the cross-validation job. It corresponds
            to the number of servers used.

        """

        # Assign multiple train-test splits to each subrun.
        subrun_chunks = get_chunks(self.split_ids, num_subruns)
        subrun_args = []
        for subrun_chunk in subrun_chunks:
            in_paths = []
            for split_id in subrun_chunk:
                in_paths.append(
                    os.path.join(
                        self.subsub_dir.format(self.dump_time, split_id),
                        self.in_base))
            subrun_args.append({"in_paths": ":".join(in_paths)})

        # Launch parallel cross-validation.
        job_client = client("job")
        job_client.create_run(self.project_id, self.job_id, subrun_args)
        LOGGER.info("Launching cross validation. " +
                    "Refer to the Jobs tab for progress information")
예제 #16
0
    def list_artifacts(self, path=None):
        if path is None:
            path = "./"
        datasets_path = self._datasets_path(path)

        # Make sure path interpreted as a directory
        prefix = datasets_path.rstrip("/") + "/"

        # Go directly to the object store so we can get file sizes in the
        # response
        client = faculty.client("object")

        list_response = client.list(self.project_id, prefix)
        objects = list_response.objects

        while list_response.next_page_token is not None:
            list_response = client.list(
                self.project_id, prefix, list_response.next_page_token
            )
            objects += list_response.objects

        infos = [
            faculty_object_to_mlflow_file_info(
                obj, self.datasets_artifact_root
            )
            for obj in objects
        ]

        # Remove root
        return [i for i in infos if i.path != "/"]
예제 #17
0
def resolve_project(project):
    """Resolve a project name or ID to a project ID."""
    projects_client = faculty.client("project")
    try:
        project_id = uuid.UUID(project)
        project = projects_client.get(project_id)
    except ValueError:
        account_client = faculty.client("account")
        user_id = account_client.authenticated_user_id()
        projects = [
            p for p in projects_client.list_accessible_by_user(user_id)
            if p.name == project
        ]
        if len(projects) == 1:
            project = projects[0]
        else:
            raise ValueError("Could not resolve project.")
    return project
예제 #18
0
def apply(project, server, environment):
    """Apply an environment to the server."""
    project_id, server_id = _resolve_server(project, server)
    environment_id = _resolve_environment(project_id, environment)

    client = faculty.client("server")
    client.apply_environment(server_id, environment_id)

    click.echo("Applying environment {} to server {} for project {}".format(
        environment, server, project))
예제 #19
0
def new_project(name):
    """Create new project."""
    client = faculty.client("project")
    user_id = _get_authenticated_user_id()
    try:
        returned_project = client.create(user_id, name)
    except faculty.clients.base.BadRequest as err:
        _print_and_exit(err.error, 64)
    click.echo("Created project {} with ID {}".format(returned_project.name,
                                                      returned_project.id))
예제 #20
0
def job_run_logs(project, job, run):
    """Print the logs for a run."""

    project_id, job_id = _resolve_job(project, job)

    job_client = faculty.client("job")
    run_details = job_client.get_run(project_id, job_id, run.run_number)
    if run.subrun_number is not None:
        subrun_number = run.subrun_number
    elif len(run_details.subruns) == 1:
        subrun_number = run_details.subruns[0].subrun_number
    else:
        _print_and_exit(
            ("Run {0} has {1} subruns. You must specify the subrun "
             "to show logs from, e.g. '{0}.1'.").format(
                 run.run_number, len(run_details.subruns)),
            64,
        )

    subrun_details = job_client.get_subrun(project_id, job_id, run.run_number,
                                           subrun_number)

    log_client = faculty.client("log")

    for env_step_exec in subrun_details.environment_step_executions:
        env_name = env_step_exec.environment_name
        click.secho('Logs for step of environment "{}":'.format(env_name),
                    fg="yellow")
        parts = log_client.get_subrun_environment_step_logs(
            project_id,
            job_id,
            run_details.id,
            subrun_details.id,
            env_step_exec.environment_step_id,
        )
        click.echo("".join(part.content for part in parts), nl=False)

    click.secho("Logs for job command:", fg="yellow")
    parts = log_client.get_subrun_command_logs(project_id, job_id,
                                               run_details.id,
                                               subrun_details.id)
    click.echo("".join(part.content for part in parts), nl=False)
예제 #21
0
def get_ssh_details(configuration):
    client = faculty.client("server")
    details = client.get_ssh_details(configuration.project.id,
                                     configuration.server_id)
    hostname = details.hostname
    port = details.port
    username = details.username
    key = details.key
    with _save_key_to_file(key) as key_file:
        ssh_details = SshDetails(hostname, port, username, key_file)
        yield ssh_details
예제 #22
0
def _any_server(project_id, status=None):
    """Get any running server from project."""
    client = faculty.client("server")
    servers_ = [server for server in client.list(project_id)]
    if status is not None:
        servers_ = [
            server for server in servers_ if server.status.value == status
        ]
    if not servers_:
        adjective = "available" if status is None else status
        message = "No {} server in project.".format(adjective)
        raise NoValidServer(message)
    return servers_[0].id
예제 #23
0
파일: cli.py 프로젝트: jkeelan/faculty-cli
def _resolve_project(project):
    """Resolve a project name or ID to a project ID."""
    try:
        project_id = uuid.UUID(project)
    except ValueError:
        account_client = faculty.client("account")
        user_id = account_client.authenticated_user_id()

        project_client = faculty.client("project")
        projects = project_client.list_accessible_by_user(user_id)
        matching_projects = [p for p in projects if p.name == project]
        if len(matching_projects) == 1:
            project_id = matching_projects[0].id
        else:
            if not matching_projects:
                msg = 'no project of name "{}" found'.format(project)
                raise NameNotFoundError(msg)
            else:
                msg = ('more than one project of name "{}", please select by '
                       "project ID instead").format(project)
                raise AmbiguousNameError(msg)
    return project_id
예제 #24
0
def list_job_runs(project, job, verbose):
    """List the runs of a job."""

    project_id, job_id = _resolve_job(project, job)

    client = faculty.client("job")

    def list_runs():
        list_runs_result = client.list_runs(project_id, job_id)
        for run in list_runs_result.runs:
            yield run
        while list_runs_result.pagination.next is not None:
            list_runs_result = client.list_runs(
                project_id,
                job_id,
                start=list_runs_result.pagination.next.start,
                limit=list_runs_result.pagination.next.limit,
            )
            for run in list_runs_result.runs:
                yield run

    runs = list(list_runs())
    if verbose:
        if not runs:
            click.echo("No runs.")
        else:
            rows = [(
                run.run_number,
                run.id,
                run.state.value,
                _format_datetime(run.submitted_at),
                _format_datetime(run.started_at),
                _format_datetime(run.ended_at),
            ) for run in runs]
            click.echo(
                tabulate(
                    rows,
                    (
                        "Number",
                        "ID",
                        "State",
                        "Submitted At",
                        "Started At",
                        "Ended At",
                    ),
                    tablefmt="plain",
                ))
    else:
        for run in runs:
            click.echo(run.run_number)
예제 #25
0
def _job_by_name(project_id, job_name):
    """Resolve a project ID and job name to a job ID."""
    client = faculty.client("job")
    jobs = client.list(project_id)
    matching_jobs = [job for job in jobs if job.metadata.name == job_name]
    if len(matching_jobs) == 1:
        return matching_jobs[0]
    else:
        if not matching_jobs:
            msg = 'no job of name "{}" in this project'.format(job_name)
            raise NameNotFoundError(msg)
        else:
            msg = ('more than one job of name "{}", please select by job ID '
                   "instead").format(job_name)
            raise AmbiguousNameError(msg)
예제 #26
0
def _get_model_version(project_id, model_id, version=None):
    client = faculty.client("model")
    model_versions = client.list_versions(project_id, model_id)
    if version is None:
        matching = model_versions[-1:]
    else:
        matching = [v for v in model_versions if v.version_number == version]

    try:
        [model_version] = matching
        return model_version
    except ValueError:
        if len(matching) == 0:
            tpl = "No version of model {} with version number {} found"
        else:
            tpl = "Multiple versions of model {} with version number {} found"
        raise ValueError(tpl.format(model_id, version))
예제 #27
0
def _environment_by_name(project_id, environment_name):
    client = faculty.client("environment")
    environments = client.list(project_id)
    matching_environments = [
        e for e in environments if e.name == environment_name
    ]
    if len(matching_environments) == 1:
        return matching_environments[0]
    else:
        if not matching_environments:
            msg = 'no available environment of name "{}"'.format(
                environment_name)
            raise NameNotFoundError(msg)
        else:
            msg = ('more than one environment of name "{}", please select by '
                   "environment ID instead").format(environment_name)
            raise AmbiguousNameError(msg)
예제 #28
0
def run_job(project, job, parameter_values, num_subruns):
    """Run a job.

    \b
    To run a single job:
    $ faculty job run PROJECT JOB

    \b
    To run a single job with parameters:
    $ faculty job run PROJECT JOB "foo=bar,eggs=spam"

    \b
    To run a job multiple times with different parameters:
    $ faculty job run PROJECT JOB "foo=bar,eggs=spam" "foo=bar2,eggs=spam2"

    \b
    To run a job multiple times with no parameters:
    $ faculty job run PROJECT JOB --num-subruns 2

    """

    if num_subruns is None and not parameter_values:
        parameter_values = [{}]
    elif num_subruns is None and parameter_values:
        pass
    elif num_subruns is not None and not parameter_values:
        parameter_values = [{} for _ in range(num_subruns)]
    else:
        _print_and_exit(
            "Cannot set both 'parameter_values' and 'num_subruns'.", 64)

    project_id, job_id = _resolve_job(project, job)

    client = faculty.client("job")
    client.create_run(project_id, job_id, parameter_values)

    if len(parameter_values) == 1:
        run_type = "run"
        suffix = ""
    else:
        run_type = "run array"
        suffix = " with {} subruns".format(len(parameter_values))

    click.echo("Submitted {} of job '{}' in project '{}'{}".format(
        run_type, job, project, suffix))
예제 #29
0
    def __init__(self, store_uri, **_):
        parsed_uri = urllib.parse.urlparse(store_uri)
        if parsed_uri.scheme != "faculty":
            raise ValueError("Not a faculty URI: {}".format(store_uri))
        # Test for PROJECT_ID in netloc rather than path.
        elif parsed_uri.netloc != "":
            raise ValueError("Invalid URI {}. Netloc is reserved. "
                             "Did you mean 'faculty:{}".format(
                                 store_uri, parsed_uri.netloc))

        cleaned_path = parsed_uri.path.strip("/")
        try:
            self._project_id = UUID(cleaned_path)
        except ValueError:
            raise ValueError("{} in given URI {} is not a valid UUID".format(
                cleaned_path, store_uri))

        self._client = faculty.client("experiment")
예제 #30
0
def list_environments(project, verbose):
    """List your environments."""
    client = faculty.client("environment")
    project_id = _resolve_project(project)
    environments = client.list(project_id)
    if verbose:
        if not environments:
            click.echo("No environments.")
        else:
            click.echo(
                tabulate(
                    [(e.name, e.id) for e in environments],
                    ("Environment Name", "ID"),
                    tablefmt="plain",
                ))
    else:
        for environment in environments:
            click.echo(environment.name)