kalavai-client 0.5.30__py3-none-any.whl → 0.6.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- kalavai_client/__init__.py +1 -1
- kalavai_client/assets/apps.yaml +9 -7
- kalavai_client/assets/apps_values.yaml +0 -2
- kalavai_client/assets/docker-compose-gui.yaml +3 -0
- kalavai_client/assets/docker-compose-template.yaml +1 -1
- kalavai_client/auth.py +92 -50
- kalavai_client/bridge_api.py +39 -26
- kalavai_client/cli.py +112 -103
- kalavai_client/core.py +108 -113
- kalavai_client/env.py +1 -1
- kalavai_client/utils.py +66 -133
- {kalavai_client-0.5.30.dist-info → kalavai_client-0.6.0.dist-info}/METADATA +6 -27
- kalavai_client-0.6.0.dist-info/RECORD +25 -0
- {kalavai_client-0.5.30.dist-info → kalavai_client-0.6.0.dist-info}/WHEEL +1 -1
- kalavai_client-0.5.30.dist-info/RECORD +0 -25
- {kalavai_client-0.5.30.dist-info → kalavai_client-0.6.0.dist-info}/LICENSE +0 -0
- {kalavai_client-0.5.30.dist-info → kalavai_client-0.6.0.dist-info}/entry_points.txt +0 -0
kalavai_client/__init__.py
CHANGED
@@ -1,2 +1,2 @@
|
|
1
1
|
|
2
|
-
__version__ = "0.
|
2
|
+
__version__ = "0.6.0"
|
kalavai_client/assets/apps.yaml
CHANGED
@@ -28,7 +28,7 @@ releases:
|
|
28
28
|
- name: lago
|
29
29
|
namespace: kalavai
|
30
30
|
chart: kalavai/lago
|
31
|
-
installed:
|
31
|
+
installed: {{deploy_llm_sidecars}}
|
32
32
|
set:
|
33
33
|
- name: external.api.nodePort
|
34
34
|
value: 32000
|
@@ -79,7 +79,7 @@ releases:
|
|
79
79
|
namespace: kalavai
|
80
80
|
chart: kalavai/kalavai-helios
|
81
81
|
version: "0.1.11"
|
82
|
-
installed: {{
|
82
|
+
installed: false #{{deploy_llm_sidecars}}
|
83
83
|
set:
|
84
84
|
- name: deployment.watcher_endpoint
|
85
85
|
value: "http://{{watcher_service}}"
|
@@ -94,11 +94,11 @@ releases:
|
|
94
94
|
- name: opencost
|
95
95
|
namespace: opencost
|
96
96
|
chart: opencost-charts/opencost
|
97
|
-
installed: {{
|
97
|
+
installed: {{deploy_llm_sidecars}}
|
98
98
|
- name: prometheus
|
99
99
|
namespace: prometheus-system
|
100
100
|
chart: prometheus/prometheus
|
101
|
-
installed: {{
|
101
|
+
installed: {{deploy_llm_sidecars}}
|
102
102
|
set:
|
103
103
|
- name: prometheus-pushgateway.enabled
|
104
104
|
value: false
|
@@ -144,17 +144,19 @@ releases:
|
|
144
144
|
- name: kalavai-watcher
|
145
145
|
namespace: kalavai
|
146
146
|
chart: kalavai/kalavai-watcher
|
147
|
-
version: "0.3.
|
147
|
+
version: "0.3.8"
|
148
148
|
installed: true
|
149
149
|
set:
|
150
150
|
- name: namespace
|
151
151
|
value: kalavai
|
152
152
|
- name: replicas
|
153
|
-
value:
|
153
|
+
value: 1
|
154
154
|
- name: image_tag
|
155
|
-
value: "v2025.03.
|
155
|
+
value: "v2025.03.18"
|
156
156
|
- name: deployment.in_cluster
|
157
157
|
value: "True"
|
158
|
+
- name: deployment.kalavai_username_key
|
159
|
+
value: "{{kalavai_username_key}}"
|
158
160
|
- name: deployment.use_auth_key
|
159
161
|
value: "True"
|
160
162
|
- name: deployment.admin_key
|
kalavai_client/auth.py
CHANGED
@@ -1,68 +1,110 @@
|
|
1
|
+
from collections import defaultdict
|
1
2
|
import os
|
2
|
-
import
|
3
|
+
import json
|
4
|
+
from typing import Optional, Any
|
3
5
|
|
4
|
-
|
5
|
-
import anvil.users
|
6
|
+
_USER_KEY = "id"
|
6
7
|
|
7
8
|
|
8
|
-
|
9
|
+
class KalavaiAuth():
|
10
|
+
"""Client for authenticating with the Kalavai watcher service."""
|
11
|
+
|
12
|
+
def __init__(
|
13
|
+
self,
|
14
|
+
auth_service_url: str,
|
15
|
+
auth_service_key: str,
|
16
|
+
user_cookie_file: str
|
17
|
+
):
|
18
|
+
"""Initialize the Kalavai auth client.
|
19
|
+
|
20
|
+
Args:
|
21
|
+
user_cookie_file: Path to store user session data
|
22
|
+
"""
|
23
|
+
self.user_cookie = user_cookie_file
|
24
|
+
self.auth_service_url = auth_service_url
|
25
|
+
self.auth_service_key = auth_service_key
|
26
|
+
self.user_session = defaultdict(None)
|
27
|
+
self.load_user_session()
|
9
28
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
29
|
+
def save_auth(self, user_key: str) -> Optional[Any]:
|
30
|
+
"""Login with user key.
|
31
|
+
|
32
|
+
Args:
|
33
|
+
user_key: User's key (actual authentication key)
|
34
|
+
|
35
|
+
Returns:
|
36
|
+
User data if successful, None otherwise
|
37
|
+
"""
|
38
|
+
# Store the user key as the session
|
39
|
+
self.user_session[_USER_KEY] = user_key
|
40
|
+
with open(self.user_cookie, "w") as f:
|
41
|
+
json.dump(self.user_session, f)
|
42
|
+
print(f"User key securely saved to {self.user_cookie}")
|
15
43
|
|
16
|
-
def
|
44
|
+
def clear_auth(self) -> bool:
|
45
|
+
"""Clear the current user and local session.
|
46
|
+
|
47
|
+
Returns:
|
48
|
+
bool: True if successful, False otherwise
|
49
|
+
"""
|
50
|
+
self.user_session = defaultdict(None)
|
17
51
|
try:
|
18
|
-
|
19
|
-
|
20
|
-
|
52
|
+
if os.path.exists(self.user_cookie):
|
53
|
+
os.remove(self.user_cookie)
|
54
|
+
except Exception as e:
|
55
|
+
print(f"Failed to clear user session: {str(e)}")
|
56
|
+
|
57
|
+
def is_authenticated(self) -> bool:
|
58
|
+
"""Check if user is currently authenticated.
|
21
59
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
return
|
60
|
+
Returns:
|
61
|
+
bool: True if logged in, False otherwise
|
62
|
+
"""
|
63
|
+
return self.user_session[_USER_KEY] is not None
|
26
64
|
|
27
|
-
def
|
28
|
-
|
65
|
+
def load_user_session(self) -> Optional[Any]:
|
66
|
+
"""Load user session from local storage.
|
67
|
+
|
68
|
+
Returns:
|
69
|
+
User data if session exists, None otherwise
|
70
|
+
"""
|
29
71
|
try:
|
30
|
-
os.
|
31
|
-
|
32
|
-
|
72
|
+
if not os.path.exists(self.user_cookie):
|
73
|
+
return None
|
74
|
+
|
75
|
+
with open(self.user_cookie, "r") as f:
|
76
|
+
for key, value in json.load(f).items():
|
77
|
+
self.user_session[key] = value
|
78
|
+
return self.user_session
|
79
|
+
|
80
|
+
except Exception as e:
|
81
|
+
print(f"Failed to load user session: {str(e)}")
|
82
|
+
self.clear_auth()
|
83
|
+
return None
|
33
84
|
|
34
|
-
def
|
35
|
-
user
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
if user:
|
41
|
-
return user
|
85
|
+
def get_user_id(self) -> Optional[Any]:
|
86
|
+
"""Get the user key from the user session.
|
87
|
+
|
88
|
+
Returns:
|
89
|
+
User key if session exists, None otherwise
|
90
|
+
"""
|
42
91
|
try:
|
43
|
-
|
44
|
-
user = pickle.load(f)
|
45
|
-
return user
|
92
|
+
return self.user_session[_USER_KEY]
|
46
93
|
except:
|
47
94
|
return None
|
48
|
-
|
49
|
-
def call_function(self, fn, *args):
|
50
|
-
return anvil.server.call(
|
51
|
-
fn,
|
52
|
-
*args
|
53
|
-
)
|
54
95
|
|
55
|
-
|
56
96
|
|
97
|
+
# Example usage
|
57
98
|
if __name__ == "__main__":
|
58
|
-
|
59
|
-
|
99
|
+
# Initialize client
|
100
|
+
auth = KalavaiAuth(
|
101
|
+
auth_service_url="http://localhost:8000",
|
102
|
+
auth_service_key="example_auth_service_key",
|
103
|
+
user_cookie_file="user_cookie.json"
|
60
104
|
)
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
else:
|
68
|
-
print(user)
|
105
|
+
|
106
|
+
# Example of logging in with a user key
|
107
|
+
user_id = "example_user_id"
|
108
|
+
auth.save_auth(user_id)
|
109
|
+
|
110
|
+
print(f"User id: {auth.get_user_id()}")
|
kalavai_client/bridge_api.py
CHANGED
@@ -2,7 +2,8 @@
|
|
2
2
|
Core kalavai service.
|
3
3
|
Used as a bridge between the kalavai-client app and the reflex frontend
|
4
4
|
"""
|
5
|
-
from fastapi import FastAPI
|
5
|
+
from fastapi import FastAPI, HTTPException, Depends
|
6
|
+
from starlette.requests import Request
|
6
7
|
import uvicorn
|
7
8
|
|
8
9
|
from kalavai_client.bridge_models import (
|
@@ -47,11 +48,24 @@ from kalavai_client.core import (
|
|
47
48
|
uncordon_nodes,
|
48
49
|
TokenType
|
49
50
|
)
|
51
|
+
from kalavai_client.utils import load_user_id
|
50
52
|
|
51
53
|
app = FastAPI()
|
52
54
|
|
55
|
+
################################
|
56
|
+
## API Key Validation methods ##
|
57
|
+
################################
|
58
|
+
async def verify_api_key(request: Request):
|
59
|
+
user_id = load_user_id()
|
60
|
+
if user_id is None:
|
61
|
+
return None
|
62
|
+
api_key = request.headers.get("X-API-KEY")
|
63
|
+
if api_key != user_id:
|
64
|
+
raise HTTPException(status_code=401, detail="Request requires API Key")
|
65
|
+
return api_key
|
66
|
+
|
53
67
|
@app.post("/create_pool")
|
54
|
-
def pool_create(request: CreatePoolRequest):
|
68
|
+
def pool_create(request: CreatePoolRequest, api_key: str = Depends(verify_api_key)):
|
55
69
|
result = create_pool(
|
56
70
|
cluster_name=request.cluster_name,
|
57
71
|
ip_address=request.ip_address,
|
@@ -66,7 +80,7 @@ def pool_create(request: CreatePoolRequest):
|
|
66
80
|
return result
|
67
81
|
|
68
82
|
@app.post("/join_pool")
|
69
|
-
def pool_join(request: JoinPoolRequest):
|
83
|
+
def pool_join(request: JoinPoolRequest, api_key: str = Depends(verify_api_key)):
|
70
84
|
result = join_pool(
|
71
85
|
token=request.token,
|
72
86
|
num_gpus=request.num_gpus,
|
@@ -76,7 +90,7 @@ def pool_join(request: JoinPoolRequest):
|
|
76
90
|
return result
|
77
91
|
|
78
92
|
@app.post("/attach_to_pool")
|
79
|
-
def pool_attach(request: JoinPoolRequest):
|
93
|
+
def pool_attach(request: JoinPoolRequest, api_key: str = Depends(verify_api_key)):
|
80
94
|
result = attach_to_pool(
|
81
95
|
token=request.token,
|
82
96
|
node_name=request.node_name
|
@@ -84,64 +98,64 @@ def pool_attach(request: JoinPoolRequest):
|
|
84
98
|
return result
|
85
99
|
|
86
100
|
@app.post("/stop_pool")
|
87
|
-
def pool_stop(request: StopPoolRequest):
|
101
|
+
def pool_stop(request: StopPoolRequest, api_key: str = Depends(verify_api_key)):
|
88
102
|
result = stop_pool(
|
89
103
|
skip_node_deletion=request.skip_node_deletion
|
90
104
|
)
|
91
105
|
return result
|
92
106
|
|
93
107
|
@app.post("/delete_nodes")
|
94
|
-
def device_delete(request: NodesActionRequest):
|
108
|
+
def device_delete(request: NodesActionRequest, api_key: str = Depends(verify_api_key)):
|
95
109
|
result = delete_nodes(
|
96
110
|
nodes=request.nodes
|
97
111
|
)
|
98
112
|
return result
|
99
113
|
|
100
114
|
@app.post("/cordon_nodes")
|
101
|
-
def device_cordon(request: NodesActionRequest):
|
115
|
+
def device_cordon(request: NodesActionRequest, api_key: str = Depends(verify_api_key)):
|
102
116
|
result = cordon_nodes(
|
103
117
|
nodes=request.nodes
|
104
118
|
)
|
105
119
|
return result
|
106
120
|
|
107
121
|
@app.post("/uncordon_nodes")
|
108
|
-
def device_uncordon(request: NodesActionRequest):
|
122
|
+
def device_uncordon(request: NodesActionRequest, api_key: str = Depends(verify_api_key)):
|
109
123
|
result = uncordon_nodes(
|
110
124
|
nodes=request.nodes
|
111
125
|
)
|
112
126
|
return result
|
113
127
|
|
114
128
|
@app.get("/get_pool_token")
|
115
|
-
def get_token(mode: int):
|
129
|
+
def get_token(mode: int, api_key: str = Depends(verify_api_key)):
|
116
130
|
|
117
131
|
return get_pool_token(mode=TokenType(mode))
|
118
132
|
|
119
133
|
@app.get("/fetch_devices")
|
120
|
-
def get_devices():
|
134
|
+
def get_devices(api_key: str = Depends(verify_api_key)):
|
121
135
|
return fetch_devices()
|
122
136
|
|
123
137
|
@app.post("/send_pool_invites")
|
124
|
-
def send_pool_invites(request: InvitesRequest):
|
138
|
+
def send_pool_invites(request: InvitesRequest, api_key: str = Depends(verify_api_key)):
|
125
139
|
return send_invites(invitees=request.invitees)
|
126
140
|
|
127
141
|
@app.get("/fetch_resources")
|
128
|
-
def resources():
|
142
|
+
def resources(api_key: str = Depends(verify_api_key)):
|
129
143
|
return fetch_resources()
|
130
144
|
|
131
145
|
@app.get("/fetch_job_names")
|
132
|
-
def job_names():
|
146
|
+
def job_names(api_key: str = Depends(verify_api_key)):
|
133
147
|
return fetch_job_names()
|
134
148
|
|
135
149
|
@app.get("/fetch_gpus")
|
136
|
-
def gpus(available: bool = False):
|
150
|
+
def gpus(available: bool = False, api_key: str = Depends(verify_api_key)):
|
137
151
|
return fetch_gpus(available=available)
|
138
152
|
|
139
153
|
@app.post("/fetch_job_details")
|
140
|
-
def job_details(request: JobDetailsRequest):
|
154
|
+
def job_details(request: JobDetailsRequest, api_key: str = Depends(verify_api_key)):
|
141
155
|
return fetch_job_details(jobs=request.jobs)
|
142
156
|
|
143
157
|
@app.get("/fetch_job_logs")
|
144
|
-
def job_logs(job_name: str, force_namespace: str=None, pod_name: str=None, tail: int=100):
|
158
|
+
def job_logs(job_name: str, force_namespace: str=None, pod_name: str=None, tail: int=100, api_key: str = Depends(verify_api_key)):
|
145
159
|
return fetch_job_logs(
|
146
160
|
job_name=job_name,
|
147
161
|
force_namespace=force_namespace,
|
@@ -150,15 +164,15 @@ def job_logs(job_name: str, force_namespace: str=None, pod_name: str=None, tail:
|
|
150
164
|
)
|
151
165
|
|
152
166
|
@app.get("/fetch_job_templates")
|
153
|
-
def job_templates():
|
167
|
+
def job_templates(api_key: str = Depends(verify_api_key)):
|
154
168
|
return fetch_job_templates()
|
155
169
|
|
156
170
|
@app.get("/fetch_job_defaults")
|
157
|
-
def job_templates(name: str):
|
171
|
+
def job_templates(name: str, api_key: str = Depends(verify_api_key)):
|
158
172
|
return fetch_job_defaults(name=name)
|
159
173
|
|
160
174
|
@app.post("/deploy_job")
|
161
|
-
def job_deploy(request: DeployJobRequest):
|
175
|
+
def job_deploy(request: DeployJobRequest, api_key: str = Depends(verify_api_key)):
|
162
176
|
result = deploy_job(
|
163
177
|
template_name=request.template_name,
|
164
178
|
values_dict=request.values,
|
@@ -168,7 +182,7 @@ def job_deploy(request: DeployJobRequest):
|
|
168
182
|
return result
|
169
183
|
|
170
184
|
@app.post("/delete_job")
|
171
|
-
def job_delete(request: DeleteJobRequest):
|
185
|
+
def job_delete(request: DeleteJobRequest, api_key: str = Depends(verify_api_key)):
|
172
186
|
result = delete_job(
|
173
187
|
name=request.name,
|
174
188
|
force_namespace=request.force_namespace
|
@@ -176,15 +190,14 @@ def job_delete(request: DeleteJobRequest):
|
|
176
190
|
return result
|
177
191
|
|
178
192
|
@app.get("/authenticate_user")
|
179
|
-
def user_authenticate(
|
193
|
+
def user_authenticate(user_id: str, api_key: str = Depends(verify_api_key)):
|
180
194
|
result = authenticate_user(
|
181
|
-
|
182
|
-
password=password
|
195
|
+
user_id=user_id
|
183
196
|
)
|
184
197
|
return result
|
185
198
|
|
186
199
|
@app.get("/load_user_session")
|
187
|
-
def user_session():
|
200
|
+
def user_session(api_key: str = Depends(verify_api_key)):
|
188
201
|
result = load_user_session()
|
189
202
|
return result
|
190
203
|
|
@@ -219,12 +232,12 @@ def agent_resume():
|
|
219
232
|
return result
|
220
233
|
|
221
234
|
@app.get("/get_ip_addresses")
|
222
|
-
def ip_addresses(subnet: str=None):
|
235
|
+
def ip_addresses(subnet: str=None, api_key: str = Depends(verify_api_key)):
|
223
236
|
result = get_ip_addresses(subnet=subnet)
|
224
237
|
return result
|
225
238
|
|
226
239
|
@app.get("/list_available_pools")
|
227
|
-
def pool_connected(user_only: bool=False):
|
240
|
+
def pool_connected(user_only: bool=False, api_key: str = Depends(verify_api_key)):
|
228
241
|
result = list_available_pools(user_only=user_only)
|
229
242
|
return result
|
230
243
|
|