forked from ray-project/ray
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Ray projects schema and validation (ray-project#5329)
- Loading branch information
Showing
19 changed files
with
318 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,6 +3,7 @@ click | |
filelock | ||
flatbuffers | ||
funcsigs | ||
jsonschema | ||
mock | ||
numpy | ||
opencv-python-headless | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
from __future__ import absolute_import | ||
from __future__ import division | ||
from __future__ import print_function | ||
|
||
from ray.projects.projects import (check_project_definition, find_root, | ||
load_project, validate_project_schema) | ||
|
||
__all__ = [ | ||
"check_project_definition", "find_root", "load_project", | ||
"validate_project_schema" | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
import json | ||
import jsonschema | ||
import os | ||
import yaml | ||
|
||
|
||
def find_root(directory): | ||
"""Find root directory of the ray project. | ||
Args: | ||
directory (str): Directory to start the search in. | ||
Returns: | ||
Path of the parent directory containing the .rayproject or | ||
None if no such project is found. | ||
""" | ||
prev, directory = None, os.path.abspath(directory) | ||
while prev != directory: | ||
if os.path.isdir(os.path.join(directory, ".rayproject")): | ||
return directory | ||
prev, directory = directory, os.path.abspath( | ||
os.path.join(directory, os.pardir)) | ||
return None | ||
|
||
|
||
def validate_project_schema(project_definition): | ||
"""Validate a project file against the official ray project schema. | ||
Args: | ||
project_definition (dict): Parsed project yaml. | ||
Raises: | ||
jsonschema.exceptions.ValidationError: This exception is raised | ||
if the project file is not valid. | ||
""" | ||
dir = os.path.dirname(os.path.abspath(__file__)) | ||
with open(os.path.join(dir, "schema.json")) as f: | ||
schema = json.load(f) | ||
|
||
jsonschema.validate(instance=project_definition, schema=schema) | ||
|
||
|
||
def check_project_definition(project_root, project_definition): | ||
"""Checks if the project definition is valid. | ||
Args: | ||
project_root (str): Path containing the .rayproject | ||
project_definition (dict): Project definition | ||
Raises: | ||
jsonschema.exceptions.ValidationError: This exception is raised | ||
if the project file is not valid. | ||
ValueError: This exception is raised if there are other errors in | ||
the project definition (e.g. files not existing). | ||
""" | ||
|
||
validate_project_schema(project_definition) | ||
|
||
# Make sure the cluster yaml file exists | ||
if "cluster" in project_definition: | ||
cluster_file = os.path.join(project_root, | ||
project_definition["cluster"]) | ||
if not os.path.exists(cluster_file): | ||
raise ValueError("'cluster' file does not exist " | ||
"in {}".format(project_root)) | ||
|
||
if "environment" in project_definition: | ||
env = project_definition["environment"] | ||
|
||
if sum(["dockerfile" in env, "dockerimage" in env]) > 1: | ||
raise ValueError("Cannot specify both 'dockerfile' and " | ||
"'dockerimage' in environment.") | ||
|
||
if "requirements" in env: | ||
requirements_file = os.path.join(project_root, env["requirements"]) | ||
if not os.path.exists(requirements_file): | ||
raise ValueError("'requirements' file in 'environment' does " | ||
"not exist in {}".format(project_root)) | ||
|
||
if "dockerfile" in env: | ||
docker_file = os.path.join(project_root, env["dockerfile"]) | ||
if not os.path.exists(docker_file): | ||
raise ValueError("'dockerfile' file in 'environment' does " | ||
"not exist in {}".format(project_root)) | ||
|
||
|
||
def load_project(current_dir): | ||
"""Finds .rayproject folder for current project, parse and validates it. | ||
Args: | ||
current_dir (str): Path from which to search for .rayproject. | ||
Returns: | ||
Dictionary containing the project definition. | ||
Raises: | ||
jsonschema.exceptions.ValidationError: This exception is raised | ||
if the project file is not valid. | ||
ValueError: This exception is raised if there are other errors in | ||
the project definition (e.g. files not existing). | ||
""" | ||
project_root = find_root(current_dir) | ||
|
||
if not project_root: | ||
raise ValueError("No project root found") | ||
|
||
project_file = os.path.join(project_root, ".rayproject", "project.yaml") | ||
|
||
if not os.path.exists(project_file): | ||
raise ValueError("Project file {} not found".format(project_file)) | ||
|
||
with open(project_file) as f: | ||
project_definition = yaml.load(f) | ||
|
||
check_project_definition(project_root, project_definition) | ||
|
||
return project_definition |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
{ | ||
"type": "object", | ||
"properties": { | ||
"name": { | ||
"description": "The name of the project", | ||
"type": "string" | ||
}, | ||
"description": { | ||
"description": "A short description of the project", | ||
"type": "string" | ||
}, | ||
"repo": { | ||
"description": "The URL of the repo this project is part of", | ||
"type": "string" | ||
}, | ||
"cluster": { | ||
"description": "Path to a .yaml cluster configuration file (relative to the project root)", | ||
"type": "string" | ||
}, | ||
"environment": { | ||
"description": "The environment that needs to be set up to run the project", | ||
"type": "object", | ||
"properties": { | ||
"dockerimage": { | ||
"description": "URL to a docker image that can be pulled to run the project in", | ||
"type": "string" | ||
}, | ||
"dockerfile": { | ||
"description": "Path to a Dockerfile to set up an image the project can run in (relative to the project root)", | ||
"type": "string" | ||
}, | ||
"requirements": { | ||
"description": "Path to a Python requirements.txt file to set up project dependencies (relative to the project root)", | ||
"type": "string" | ||
}, | ||
"shell": { | ||
"description": "A sequence of shell commands to run to set up the project environment", | ||
"type": "array", | ||
"items": { | ||
"type": "string" | ||
} | ||
} | ||
} | ||
}, | ||
"commands": { | ||
"type": "array", | ||
"items": { | ||
"description": "Possible commands to run to start a session", | ||
"type": "object", | ||
"properties": { | ||
"name": { | ||
"description": "Name of the command", | ||
"type": "string" | ||
}, | ||
"command": { | ||
"description": "Shell command to run on the cluster", | ||
"type": "string" | ||
} | ||
} | ||
} | ||
} | ||
}, | ||
"required": ["name", "cluster"] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
name: testproject1 | ||
description: "Test project for docker environment" | ||
|
||
environment: | ||
docker: "Dockerfile" | ||
|
||
cluster: "cluster.yaml" |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
name: testproject2 | ||
|
||
environment: | ||
shell: "one command" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
name: testproject3 | ||
|
||
environment: | ||
dockerfile: "Dockerfile" | ||
|
||
dockerimage: "some docker image" | ||
|
||
cluster: "cluster.yaml" |
Empty file.
10 changes: 10 additions & 0 deletions
10
python/ray/tests/project_files/project1/.rayproject/project.yaml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
name: "project1" | ||
|
||
cluster: .rayproject/cluster.yaml | ||
|
||
environment: | ||
requirements: requirements.txt | ||
|
||
commands: | ||
- name: default | ||
command: ls |
Empty file.
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
name: testproject2 | ||
|
||
environment: | ||
requirements: "requirements.txt" | ||
|
||
cluster: "cluster.yaml" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
name: testproject3 | ||
repo: "https://github.com/ray-project/ray" | ||
|
||
environment: | ||
shell: | ||
- first command | ||
- second command | ||
- third command | ||
|
||
cluster: "cluster.yaml" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
from __future__ import absolute_import | ||
from __future__ import division | ||
from __future__ import print_function | ||
|
||
import jsonschema | ||
import os | ||
import pytest | ||
import subprocess | ||
import yaml | ||
|
||
import ray | ||
|
||
TEST_DIR = os.path.dirname(os.path.abspath(__file__)) | ||
|
||
|
||
def load_project_description(project_file): | ||
path = os.path.join(TEST_DIR, "project_files", project_file) | ||
with open(path) as f: | ||
return yaml.load(f) | ||
|
||
|
||
def test_validation_success(): | ||
project_files = [ | ||
"docker_project.yaml", "requirements_project.yaml", | ||
"shell_project.yaml" | ||
] | ||
for project_file in project_files: | ||
project_definition = load_project_description(project_file) | ||
ray.projects.validate_project_schema(project_definition) | ||
|
||
|
||
def test_validation_failure(): | ||
project_files = ["no_project1.yaml", "no_project2.yaml"] | ||
for project_file in project_files: | ||
project_definition = load_project_description(project_file) | ||
with pytest.raises(jsonschema.exceptions.ValidationError): | ||
ray.projects.validate_project_schema(project_definition) | ||
|
||
|
||
def test_check_failure(): | ||
project_files = ["no_project3.yaml"] | ||
for project_file in project_files: | ||
project_definition = load_project_description(project_file) | ||
with pytest.raises(ValueError): | ||
ray.projects.check_project_definition("", project_definition) | ||
|
||
|
||
def test_project_root(): | ||
path = os.path.join(TEST_DIR, "project_files", "project1") | ||
assert ray.projects.find_root(path) == path | ||
|
||
path2 = os.path.join(TEST_DIR, "project_files", "project1", "subdir") | ||
assert ray.projects.find_root(path2) == path | ||
|
||
path3 = "/tmp/" | ||
assert ray.projects.find_root(path3) is None | ||
|
||
|
||
def test_project_validation(): | ||
path = os.path.join(TEST_DIR, "project_files", "project1") | ||
subprocess.check_call(["ray", "session", "create", "--dry"], cwd=path) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters