Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Alkanso/python client #901

Merged
merged 4 commits into from
Mar 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions clients/python-client/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@



# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class


# Distribution / packaging
bin/
build/
develop-eggs/
dist/
eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
.tox/
htmlcov
.coverage
.cache
nosetests.xml
coverage.xml
116 changes: 116 additions & 0 deletions clients/python-client/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
# Overview

This python client library provide APIs to handle `raycluster` from your python application.

## Prerequisites

It is assumed that your `k8s cluster in already setup`. Your kubectl configuration is expected to be in `~/.kube/config` if you are running the code directly from you terminal.

It is also expected that the `kuberay operator` is installed. [Installation instructions are here.](https://github.com/ray-project/kuberay#quick-start)

## Usage

There are multiple levels of using the api with increasing levels of complexity.

### director

This is the easiest form of using the api to create rayclusters with predefined cluster sizes

```python
my_kuberay_api = kuberay_cluster_api.RayClusterApi()

my_cluster_director = kuberay_cluster_builder.Director()

cluster0 = my_cluster_director.build_small_cluster(name="new-cluster0")

if cluster0:
my_kuberay_api.create_ray_cluster(body=cluster0)
```

the director create the custer definition, and the custer_api acts as the http client sending the create (post) request to the k8s api-server
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
the director create the custer definition, and the custer_api acts as the http client sending the create (post) request to the k8s api-server
The director creates the cluster definition, and the custer_api acts as the http client sending the create (post) request to the k8s api-server


### cluster_builder

The builder allows you to build the cluster piece by piece, you are can customize more the values of the cluster definition
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
The builder allows you to build the cluster piece by piece, you are can customize more the values of the cluster definition
The builder allows you to build the cluster piece by piece, giving you more control to customize the values of the cluster definition.


```python
cluster1 = (
my_cluster_builder.build_meta(name="new-cluster1")
.build_head()
.build_worker(group_name="workers", replicas=3)
.get_cluster()
)

if not my_cluster_builder.succeeded:
return

my_kuberay_api.create_ray_cluster(body=cluster1)
```

### cluster_utils

the cluster_utils gives you even more options to modify your cluster definition, add/remove worker groups, change replicas in a worker group, duplicate a worker group, etc.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
the cluster_utils gives you even more options to modify your cluster definition, add/remove worker groups, change replicas in a worker group, duplicate a worker group, etc.
The cluster_utils gives you even more options to modify your cluster definition, add/remove worker groups, change replicas in a worker group, duplicate a worker group, etc.


```python
my_Cluster_utils = kuberay_cluster_utils.ClusterUtils()

cluster_to_patch, succeeded = my_Cluster_utils.update_worker_group_replicas(
cluster2, group_name="workers", max_replicas=4, min_replicas=1, replicas=2
)

if succeeded:
my_kuberay_api.patch_ray_cluster(
name=cluster_to_patch["metadata"]["name"], ray_patch=cluster_to_patch
)
```

### cluster_api

Finally, the cluster_api is the one you always use to implement your cluster change in k8s. You can use it with raw `JSON` if you wish. The director/cluster_builder/cluster_utils are just tools to shield the user from using raw `JSON`.

## Code Organization

clients/
└── python-client
├── README.md
├── examples
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use-raw-config_map_with-api.py

│ ├── complete-example.py
│ ├── use-builder.py
│ ├── use-director.py
│ ├── use-raw-with-api.py
│ └── use-utils.py
├── python_client
│ ├── LICENSE
│ ├── __init__.py
│ ├── constants.py
│ ├── kuberay_cluster_api.py
│ ├── pyproject.toml
│ ├── setup.cfg
│ └── utils
│ ├── __init__.py
│ ├── kuberay_cluster_builder.py
│ └── kuberay_cluster_utils.py
└── python_client_test
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

test_api.py

├── README.md
├── test_director.py
└── test_utils.py

## For developers
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
## For developers
## Development


make sure you have installed setuptool

`pip install -U pip setuptools`

#### run the pip command
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#### run the pip command
#### Install the module


from the directory `path/to/kuberay/clients/python-client/python_client`

`pip install -e .`

#### to uninstall the module run
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#### to uninstall the module run
#### Uninstall the module


`pip uninstall python-client`

### For testing run
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
### For testing run
#### Test


`python -m unittest discover 'path/to/kuberay/clients/python_client_test/'`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we can add a section to describe how to run examples locally.

""" 
in case you are working directly with the source, and don't wish to 
install the module with pip install, you can directly import the packages by uncommenting the following code.
"""

122 changes: 122 additions & 0 deletions clients/python-client/examples/complete-example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import sys
import os
from os import path


"""
in case you are working directly with the source, and don't wish to
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I install python_client by pip install -e .. However, I cannot import python_client. Do I miss anything? Thanks!

Screen Shot 2023-02-28 at 2 48 17 PM

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

import kuberay_cluster_api

from utils import kuberay_cluster_utils, kuberay_cluster_builder

install the module with pip install, you can directly import the packages by uncommenting the following code.
"""

"""
sys.path.append(path.dirname(path.dirname(path.abspath(__file__))))

current_dir = os.path.dirname(os.path.abspath(__file__))
parent_dir = os.path.abspath(os.path.join(current_dir, os.pardir))
sibling_dirs = [
d for d in os.listdir(parent_dir) if os.path.isdir(os.path.join(parent_dir, d))
]
for sibling_dir in sibling_dirs:
sys.path.append(os.path.join(parent_dir, sibling_dir))
"""

import kuberay_cluster_api

from utils import kuberay_cluster_utils, kuberay_cluster_builder


def main():

print("starting cluster handler...")
my_kuberay_api = kuberay_cluster_api.RayClusterApi() # this is the main api object

my_cluster_director = kuberay_cluster_builder.Director() # this is the director object, to create a cluster with a single line of code

my_cluster_builder = kuberay_cluster_builder.ClusterBuilder() # this is the builder object, to create a cluster with a more granular control

my_Cluster_utils = kuberay_cluster_utils.ClusterUtils() # this is the utils object, to perform operations on a cluster

cluster0 = my_cluster_director.build_small_cluster(name="new-cluster0", labels={'demo-cluster':'yes'}) # this is the cluster object, it is a dict

if cluster0:
my_kuberay_api.create_ray_cluster(body=cluster0) # this is the api call to create the cluster0 in k8s

cluster1 = (
my_cluster_builder.build_meta(name="new-cluster1",labels={'demo-cluster':'yes'})
.build_head()
.build_worker(group_name="workers")
.get_cluster()
)

if not my_cluster_builder.succeeded:
print("error building the cluster, aborting...")
return
my_kuberay_api.create_ray_cluster(body=cluster1) # this is the api call to create the cluster1 in k8s

cluster2 = (
my_cluster_builder.build_meta(name="new-cluster2", labels={'demo-cluster':'yes'})
.build_head()
.build_worker(group_name="workers")
.get_cluster()
)

if not my_cluster_builder.succeeded:
print("error building the cluster, aborting...")
return

my_kuberay_api.create_ray_cluster(body=cluster2) # this is the api call to create the cluster2 in k8s

# modifying the number of replicas in the workergroup
cluster_to_patch, succeeded = my_Cluster_utils.update_worker_group_replicas(
cluster2, group_name="workers", max_replicas=4, min_replicas=1, replicas=2
)

if succeeded:
print(
"trying to patch raycluster = {}".format(
cluster_to_patch["metadata"]["name"]
)
)
my_kuberay_api.patch_ray_cluster(
name=cluster_to_patch["metadata"]["name"], ray_patch=cluster_to_patch
) # this is the api call to patch the cluster2 in k8s

cluster_to_patch, succeeded = my_Cluster_utils.duplicate_worker_group(
cluster1, group_name="workers", new_group_name="new-workers"
) # this is the call to duplicate the worker group in cluster1
if succeeded:
print(
"trying to patch raycluster = {}".format(
cluster_to_patch["metadata"]["name"]
)
)
my_kuberay_api.patch_ray_cluster(
name=cluster_to_patch["metadata"]["name"], ray_patch=cluster_to_patch
) # this is the api call to patch the cluster1 in k8s

kube_ray_list = my_kuberay_api.list_ray_clusters(k8s_namespace="default", label_selector='demo-cluster=yes') # this is the api call to list all the clusters in k8s
if "items" in kube_ray_list:
line = "-" * 72
print(line)
print("{:<63s}{:>2s}".format("Name", "Namespace"))
print(line)
for cluster in kube_ray_list["items"]:
print(
"{:<63s}{:>2s}".format(
cluster["metadata"]["name"],
cluster["metadata"]["namespace"],
)
)
print(line)

if "items" in kube_ray_list:
for cluster in kube_ray_list["items"]:
print("deleting raycluster = {}".format(cluster["metadata"]["name"]))
my_kuberay_api.delete_ray_cluster(
name=cluster["metadata"]["name"],
k8s_namespace=cluster["metadata"]["namespace"],
) # this is the api call to delete the cluster in k8s


if __name__ == "__main__":
main()
76 changes: 76 additions & 0 deletions clients/python-client/examples/use-builder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import sys
import os
from os import path
import json


"""
in case you are working directly with the source, and don't wish to
install the module with pip install, you can directly import the packages by uncommenting the following code.
"""

"""
sys.path.append(path.dirname(path.dirname(path.abspath(__file__))))

current_dir = os.path.dirname(os.path.abspath(__file__))
parent_dir = os.path.abspath(os.path.join(current_dir, os.pardir))
sibling_dirs = [
d for d in os.listdir(parent_dir) if os.path.isdir(os.path.join(parent_dir, d))
]
for sibling_dir in sibling_dirs:
sys.path.append(os.path.join(parent_dir, sibling_dir))
"""

import kuberay_cluster_api

from utils import kuberay_cluster_builder


def main():

print("starting cluster handler...")
my_kuberay_api = kuberay_cluster_api.RayClusterApi()

my_cluster_builder = kuberay_cluster_builder.ClusterBuilder()

cluster1 = (
my_cluster_builder.build_meta(name="new-cluster1", labels={'demo-cluster':'yes'})
.build_head()
.build_worker(group_name="workers")
.get_cluster()
)

if not my_cluster_builder.succeeded:
print("error building the cluster, aborting...")
return

print("creating raycluster = {}".format(cluster1["metadata"]["name"]))
my_kuberay_api.create_ray_cluster(body=cluster1)

# the rest of the code is simply to list and cleanup the created cluster
kube_ray_list = my_kuberay_api.list_ray_clusters(k8s_namespace="default", label_selector='demo-cluster=yes')
if "items" in kube_ray_list:
line = "-" * 72
print(line)
print("{:<63s}{:>2s}".format("Name", "Namespace"))
print(line)
for cluster in kube_ray_list["items"]:
print(
"{:<63s}{:>2s}".format(
cluster["metadata"]["name"],
cluster["metadata"]["namespace"],
)
)
print(line)

if "items" in kube_ray_list:
for cluster in kube_ray_list["items"]:
print("deleting raycluster = {}".format(cluster["metadata"]["name"]))
my_kuberay_api.delete_ray_cluster(
name=cluster["metadata"]["name"],
k8s_namespace=cluster["metadata"]["namespace"],
)


if __name__ == "__main__":
main()
Loading