Skip to content
This repository was archived by the owner on Aug 11, 2020. It is now read-only.

Commit 0e5bf88

Browse files
committed
Merge branch 'PS-9645-Add-support-for-managing-deployments' of github.com:Paperspace/paperspace-python into development
2 parents 83333fc + ee7cf30 commit 0e5bf88

File tree

12 files changed

+980
-39
lines changed

12 files changed

+980
-39
lines changed

Pipfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,4 @@ twine = "*"
1919
pypandoc = "*"
2020
pytest = "*"
2121
mock = "*"
22+
coverage = "*"

paperspace/cli.py

Lines changed: 164 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1+
import collections
12
import functools
23
import json
34

45
import click
56

67
from paperspace import constants, client, config
7-
from paperspace.commands import experiments as experiments_commands
8+
from paperspace.commands import experiments as experiments_commands, deployments as deployments_commands
89

910

1011
class ChoiceType(click.Choice):
@@ -19,10 +20,12 @@ def convert(self, value, param, ctx):
1920
return self.type_map[value]
2021

2122

22-
MULTI_NODE_EXPERIMENT_TYPES_MAP = {
23-
"GRPC": constants.ExperimentType.GRPC_MULTI_NODE,
24-
"MPI": constants.ExperimentType.MPI_MULTI_NODE,
25-
}
23+
MULTI_NODE_EXPERIMENT_TYPES_MAP = collections.OrderedDict(
24+
(
25+
("GRPC", constants.ExperimentType.GRPC_MULTI_NODE),
26+
("MPI", constants.ExperimentType.MPI_MULTI_NODE),
27+
)
28+
)
2629

2730

2831
def json_string(val):
@@ -312,6 +315,162 @@ def get_experiment_details(experiment_handle, api_key):
312315
experiments_api = client.API(config.CONFIG_EXPERIMENTS_HOST, api_key=api_key)
313316
experiments_commands.get_experiment_details(experiment_handle, api=experiments_api)
314317

318+
315319
# TODO: delete experiment - not implemented in the api
316320
# TODO: modify experiment - not implemented in the api
317321
# TODO: create experiment template?? What is the difference between experiment and experiment template?
322+
323+
324+
DEPLOYMENT_TYPES_MAP = collections.OrderedDict(
325+
(
326+
("TFSERVING", "Tensorflow Serving on K8s"),
327+
("GRADIENT", "Gradient Jobs"),
328+
)
329+
)
330+
331+
332+
@cli.group("deployments")
333+
def deployments():
334+
pass
335+
336+
337+
@deployments.command("create")
338+
@click.option(
339+
"--deploymentType",
340+
"deploymentType",
341+
type=ChoiceType(DEPLOYMENT_TYPES_MAP, case_sensitive=False),
342+
required=True,
343+
)
344+
@click.option(
345+
"--modelId",
346+
"modelId",
347+
required=True,
348+
)
349+
@click.option(
350+
"--name",
351+
"name",
352+
required=True,
353+
)
354+
@click.option(
355+
"--machineType",
356+
"machineType",
357+
required=True,
358+
)
359+
@click.option(
360+
"--imageUrl",
361+
"imageUrl",
362+
required=True,
363+
)
364+
@click.option(
365+
"--instanceCount",
366+
"instanceCount",
367+
type=int,
368+
required=True,
369+
)
370+
@click.option(
371+
"--apiKey",
372+
"api_key",
373+
)
374+
def create_deployment(api_key=None, **kwargs):
375+
del_if_value_is_none(kwargs)
376+
deployments_api = client.API(config.CONFIG_HOST, api_key=api_key)
377+
command = deployments_commands.CreateDeploymentCommand(api=deployments_api)
378+
command.execute(kwargs)
379+
380+
381+
DEPLOYMENT_STATES_MAP = collections.OrderedDict(
382+
(
383+
("BUILDING", "Building"),
384+
("PROVISIONING", "Provisioning"),
385+
("STARTING", "Starting"),
386+
("RUNNING", "Running"),
387+
("STOPPING", "Stopping"),
388+
("STOPPED", "Stopped"),
389+
("ERROR", "Error"),
390+
)
391+
)
392+
393+
394+
@deployments.command("list")
395+
@click.option(
396+
"--state",
397+
"state",
398+
type=ChoiceType(DEPLOYMENT_STATES_MAP, case_sensitive=False)
399+
)
400+
@click.option(
401+
"--apiKey",
402+
"api_key",
403+
)
404+
def get_deployments_list(api_key=None, **kwargs):
405+
deployments_api = client.API(config.CONFIG_HOST, api_key=api_key)
406+
command = deployments_commands.ListDeploymentsCommand(api=deployments_api)
407+
command.execute(kwargs)
408+
409+
410+
@deployments.command("update")
411+
@click.option(
412+
"--id",
413+
"id",
414+
required=True,
415+
)
416+
@click.option(
417+
"--modelId",
418+
"modelId",
419+
)
420+
@click.option(
421+
"--name",
422+
"name",
423+
)
424+
@click.option(
425+
"--machineType",
426+
"machineType",
427+
)
428+
@click.option(
429+
"--imageUrl",
430+
"imageUrl",
431+
)
432+
@click.option(
433+
"--instanceCount",
434+
"instanceCount",
435+
)
436+
@click.option(
437+
"--apiKey",
438+
"api_key",
439+
)
440+
def update_deployment_model(id=None, api_key=None, **kwargs):
441+
del_if_value_is_none(kwargs)
442+
deployments_api = client.API(config.CONFIG_HOST, api_key=api_key)
443+
command = deployments_commands.UpdateModelCommand(api=deployments_api)
444+
command.execute(id, kwargs)
445+
446+
447+
@deployments.command("start")
448+
@click.option(
449+
"--id",
450+
"id",
451+
required=True,
452+
)
453+
@click.option(
454+
"--apiKey",
455+
"api_key",
456+
)
457+
def start_deployment(id, api_key=None):
458+
deployments_api = client.API(config.CONFIG_HOST, api_key=api_key)
459+
command = deployments_commands.StartDeploymentCommand(api=deployments_api)
460+
command.execute(id)
461+
462+
463+
@deployments.command("delete")
464+
@click.option(
465+
"--id",
466+
"id",
467+
required=True,
468+
)
469+
@click.option(
470+
"--apiKey",
471+
"api_key",
472+
)
473+
def delete_deployment(id, api_key=None):
474+
deployments_api = client.API(config.CONFIG_HOST, api_key=api_key)
475+
command = deployments_commands.DeleteDeploymentCommand(api=deployments_api)
476+
command.execute(id)

paperspace/client.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,23 +31,23 @@ def get_path(self, url):
3131
def post(self, url, json=None, params=None):
3232
path = self.get_path(url)
3333
response = requests.post(path, json=json, params=params, headers=self.headers)
34-
logger.debug("POST request sent to: {} with headers: {}".format(response.url, self.headers))
34+
logger.debug("POST request sent to: {} \n\theaders: {}\n\tjson: {}".format(response.url, self.headers, json))
3535
logger.debug("Response status code: {}".format(response.status_code))
3636
logger.debug("Response content: {}".format(response.content))
3737
return response
3838

39-
def put(self, url):
39+
def put(self, url, json=None, params=None):
4040
path = self.get_path(url)
41-
response = requests.put(path, headers=self.headers)
42-
logger.debug("PUT request sent to: {} with headers: {}".format(response.url, self.headers))
41+
response = requests.put(path, json=json, params=params, headers=self.headers)
42+
logger.debug("PUT request sent to: {} \n\theaders: {}\n\tjson: {}".format(response.url, self.headers, json))
4343
logger.debug("Response status code: {}".format(response.status_code))
4444
logger.debug("Response content: {}".format(response.content))
4545
return response
4646

47-
def get(self, url, params=None):
47+
def get(self, url, json=None, params=None):
4848
path = self.get_path(url)
49-
response = requests.get(path, params=params, headers=self.headers)
50-
logger.debug("GET request sent to: {} with headers: {}".format(response.url, self.headers))
49+
response = requests.get(path, params=params, headers=self.headers, json=json)
50+
logger.debug("GET request sent to: {} \n\theaders: {}\n\tjson: {}".format(response.url, self.headers, json))
5151
logger.debug("Response status code: {}".format(response.status_code))
5252
logger.debug("Response content: {}".format(response.content))
5353
return response

paperspace/commands/deployments.py

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
import pydoc
2+
3+
import terminaltables
4+
5+
from paperspace import config, version, client, logger
6+
from paperspace.utils import get_terminal_lines
7+
8+
default_headers = {"X-API-Key": config.PAPERSPACE_API_KEY,
9+
"ps_client_name": "paperspace-python",
10+
"ps_client_version": version.version}
11+
deployments_api = client.API(config.CONFIG_HOST, headers=default_headers)
12+
13+
14+
class _DeploymentCommandBase(object):
15+
def __init__(self, api=deployments_api, logger_=logger):
16+
self.api = api
17+
self.logger = logger_
18+
19+
def _log_message(self, response, success_msg_template, error_msg):
20+
if response.ok:
21+
try:
22+
j = response.json()
23+
handle = j["deployment"]
24+
except (ValueError, KeyError):
25+
self.logger.log(success_msg_template)
26+
else:
27+
msg = success_msg_template.format(**handle)
28+
self.logger.log(msg)
29+
else:
30+
try:
31+
data = response.json()
32+
self.logger.log_error_response(data)
33+
except ValueError:
34+
self.logger.log(error_msg)
35+
36+
37+
class CreateDeploymentCommand(_DeploymentCommandBase):
38+
def execute(self, kwargs):
39+
response = self.api.post("/deployments/createDeployment/", json=kwargs)
40+
self._log_message(response,
41+
"New deployment created with id: {id}",
42+
"Unknown error during deployment")
43+
44+
45+
class ListDeploymentsCommand(_DeploymentCommandBase):
46+
def execute(self, kwargs):
47+
json_ = self._get_request_json(kwargs)
48+
response = self.api.get("/deployments/getDeploymentList/", json=json_)
49+
50+
try:
51+
deployments = self._get_deployments_list(response)
52+
except (ValueError, KeyError) as e:
53+
self.logger.log("Error while parsing response data: {}".format(e))
54+
else:
55+
self._log_deployments_list(deployments)
56+
57+
@staticmethod
58+
def _get_request_json(kwargs):
59+
state = kwargs.get("state")
60+
if not state:
61+
return None
62+
63+
params = {"filter": {"where": {"and": [{"state": state}]}}}
64+
return params
65+
66+
@staticmethod
67+
def _get_deployments_list(response):
68+
if not response.ok:
69+
raise ValueError("Unknown error")
70+
71+
data = response.json()["deploymentList"]
72+
logger.debug(data)
73+
return data
74+
75+
def _log_deployments_list(self, deployments):
76+
if not deployments:
77+
self.logger.log("No deployments found")
78+
else:
79+
table_str = self._make_deployments_list_table(deployments)
80+
if len(table_str.splitlines()) > get_terminal_lines():
81+
pydoc.pager(table_str)
82+
else:
83+
self.logger.log(table_str)
84+
85+
@staticmethod
86+
def _make_deployments_list_table(deployments):
87+
data = [("Name", "ID", "Endpoint", "Api Type", "Deployment Type")]
88+
for deployment in deployments:
89+
name = deployment.get("name")
90+
id_ = deployment.get("id")
91+
endpoint = deployment.get("endpoint")
92+
api_type = deployment.get("apiType")
93+
deployment_type = deployment.get("deploymentType")
94+
data.append((name, id_, endpoint, api_type, deployment_type))
95+
96+
ascii_table = terminaltables.AsciiTable(data)
97+
table_string = ascii_table.table
98+
return table_string
99+
100+
101+
class UpdateModelCommand(_DeploymentCommandBase):
102+
def execute(self, model_id, kwargs):
103+
if not kwargs:
104+
self.logger.log("No parameters to update were given. Use --help for more information.")
105+
return
106+
107+
json_ = {"id": model_id,
108+
"upd": kwargs}
109+
response = self.api.post("/deployments/updateDeployment/", json=json_)
110+
self._log_message(response,
111+
"Deployment model updated.",
112+
"Unknown error occurred.")
113+
114+
115+
class StartDeploymentCommand(_DeploymentCommandBase):
116+
def execute(self, model_id):
117+
json_ = {"id": model_id,
118+
"isRunning": True}
119+
response = self.api.post("/deployments/updateDeployment/", json=json_)
120+
self._log_message(response,
121+
"Deployment started",
122+
"Unknown error occurred.")
123+
124+
125+
class DeleteDeploymentCommand(_DeploymentCommandBase):
126+
def execute(self, model_id):
127+
json_ = {"id": model_id,
128+
"upd": {"isDeleted": True}}
129+
response = self.api.post("/deployments/updateDeployment/", json=json_)
130+
self._log_message(response,
131+
"Deployment deleted.",
132+
"Unknown error occurred.")

0 commit comments

Comments
 (0)