You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@submarine.apache.org by GitBox <gi...@apache.org> on 2021/12/14 10:07:20 UTC

[GitHub] [submarine] KUAN-HSUN-LI commented on a change in pull request #831: SUBMARINE-1117. Connect API for CLI Experiments

KUAN-HSUN-LI commented on a change in pull request #831:
URL: https://github.com/apache/submarine/pull/831#discussion_r768487079



##########
File path: submarine-sdk/pysubmarine/submarine/cli/experiment/command.py
##########
@@ -15,24 +15,115 @@
  under the License.
 """
 
+import json
+from time import sleep
+
 import click
+from rich.console import Console
+from rich.json import JSON as richJSON
+from rich.panel import Panel
+from rich.table import Table
+
+from submarine.experiment.api.experiment_client import ExperimentClient
+from submarine.experiment.exceptions import ApiException
+
+experimentClient = ExperimentClient("http://localhost:8080")
 
 
 @click.command("experiment")
 def list_experiment():
     """List experiments"""
-    click.echo("list experiment!")
+    COLS_TO_SHOW = ["Name", "Id", "Tags", "Finished Time", "Created Time", "Running Time", "Status"]
+    console = Console()
+    try:
+        thread = experimentClient.list_experiments_async()
+        with console.status("[bold green] Fetching Experiments..."):
+            while not thread.ready():
+                sleep(1)

Review comment:
       Also, set a timeout value, maybe 10 secs or 30 secs?

##########
File path: submarine-sdk/pysubmarine/submarine/cli/experiment/command.py
##########
@@ -15,24 +15,115 @@
  under the License.
 """
 
+import json
+from time import sleep
+
 import click
+from rich.console import Console
+from rich.json import JSON as richJSON
+from rich.panel import Panel
+from rich.table import Table
+
+from submarine.experiment.api.experiment_client import ExperimentClient
+from submarine.experiment.exceptions import ApiException
+
+experimentClient = ExperimentClient("http://localhost:8080")
 
 
 @click.command("experiment")
 def list_experiment():
     """List experiments"""
-    click.echo("list experiment!")
+    COLS_TO_SHOW = ["Name", "Id", "Tags", "Finished Time", "Created Time", "Running Time", "Status"]
+    console = Console()
+    try:
+        thread = experimentClient.list_experiments_async()
+        with console.status("[bold green] Fetching Experiments..."):
+            while not thread.ready():
+                sleep(1)
+
+        result = thread.get()
+        results = result.result
+
+        results = list(
+            map(
+                lambda r: [
+                    r["spec"]["meta"]["name"],
+                    r["experimentId"],
+                    ",".join(r["spec"]["meta"]["tags"]),
+                    r["finishedTime"],
+                    r["createdTime"],
+                    r["runningTime"],
+                    r["status"],
+                ],
+                results,
+            )
+        )
+
+        table = Table(title="List of Experiments")
+
+        for col in COLS_TO_SHOW:
+            table.add_column(col, overflow="fold")
+        for res in results:
+            table.add_row(*res)
+
+        console.print(table)
+
+    except ApiException as err:
+        if err.body is not None:
+            errbody = json.loads(err.body)
+            click.echo("[Api Error] {}".format(errbody["message"]))
+        else:
+            click.echo("[Api Error] {}".format(err))
 
 
 @click.command("experiment")
 @click.argument("id")
 def get_experiment(id):
     """Get experiments"""
-    click.echo("get experiment! id={}".format(id))
+    console = Console()
+    try:
+        thread = experimentClient.get_experiment_async(id)
+        with console.status("[bold green] Fetching Experiment(id = {} )...".format(id)):
+            while not thread.ready():
+                sleep(1)

Review comment:
       same as above

##########
File path: submarine-sdk/pysubmarine/tests/cli/test_experiment.py
##########
@@ -13,29 +13,58 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+import pytest
 from click.testing import CliRunner
 
+import submarine
 from submarine.cli import main
+from submarine.experiment.models.code_spec import CodeSpec
+from submarine.experiment.models.environment_spec import EnvironmentSpec
+from submarine.experiment.models.experiment_meta import ExperimentMeta
+from submarine.experiment.models.experiment_spec import ExperimentSpec
+from submarine.experiment.models.experiment_task_spec import ExperimentTaskSpec
 
 
-def test_list_experiment():
-    runner = CliRunner()
-    result = runner.invoke(main.entry_point, ["list", "experiment"])
-    assert result.exit_code == 0
-    assert "list experiment!" in result.output
+@pytest.mark.e2e
+def test_all_experiment_e2e():
+    submarine_client = submarine.ExperimentClient(host="http://localhost:8080")
+    environment = EnvironmentSpec(image="apache/submarine:tf-dist-mnist-test-1.0")
+    experiment_meta = ExperimentMeta(
+        name="mnist-dist",
+        namespace="default",
+        framework="Tensorflow",
+        cmd="python /var/tf_dist_mnist/dist_mnist.py --train_steps=100",
+        env_vars={"ENV1": "ENV1"},
+    )
 
+    worker_spec = ExperimentTaskSpec(resources="cpu=1,memory=1024M", replicas=1)
+    ps_spec = ExperimentTaskSpec(resources="cpu=1,memory=1024M", replicas=1)
 
-def test_get_experiment():
-    mock_experiment_id = "0"
-    runner = CliRunner()
-    result = runner.invoke(main.entry_point, ["get", "experiment", mock_experiment_id])
-    assert result.exit_code == 0
-    assert "get experiment! id={}".format(mock_experiment_id) in result.output
+    code_spec = CodeSpec(sync_mode="git", url="https://github.com/apache/submarine.git")
+
+    experiment_spec = ExperimentSpec(
+        meta=experiment_meta,
+        environment=environment,
+        code=code_spec,
+        spec={"Ps": ps_spec, "Worker": worker_spec},
+    )
 
+    experiment = submarine_client.create_experiment(experiment_spec=experiment_spec)
 
-def test_delete_experiment():
-    mock_experiment_id = "0"
-    runner = CliRunner()
-    result = runner.invoke(main.entry_point, ["delete", "experiment", mock_experiment_id])
+    # set env to display full table
+    runner = CliRunner(env={"COLUMNS": "191"})
+    # test list experiment
+    result = runner.invoke(main.entry_point, ["list", "experiment"])
     assert result.exit_code == 0
-    assert "delete experiment! id={}".format(mock_experiment_id) in result.output
+    assert "List of Experiments" in result.output
+    assert experiment["experimentId"] in result.output
+    assert experiment["spec"]["meta"]["name"] in result.output
+
+    # test get experiment
+    result = runner.invoke(main.entry_point, ["get", "experiment", experiment["experimentId"]])
+    assert "Experiment(id = {} )".format(experiment["experimentId"]) in result.output
+    assert experiment["spec"]["environment"]["image"] in result.output
+
+    # test delete experiment
+    result = runner.invoke(main.entry_point, ["delete", "experiment", experiment["experimentId"]])
+    assert "Experiment(id = {} ) deleted".format(experiment["experimentId"]) in result.output

Review comment:
       Also, test the get fails after the experiment is deleted.

##########
File path: submarine-sdk/pysubmarine/tests/cli/test_experiment.py
##########
@@ -13,29 +13,58 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+import pytest
 from click.testing import CliRunner
 
+import submarine
 from submarine.cli import main
+from submarine.experiment.models.code_spec import CodeSpec
+from submarine.experiment.models.environment_spec import EnvironmentSpec
+from submarine.experiment.models.experiment_meta import ExperimentMeta
+from submarine.experiment.models.experiment_spec import ExperimentSpec
+from submarine.experiment.models.experiment_task_spec import ExperimentTaskSpec
 
 
-def test_list_experiment():
-    runner = CliRunner()
-    result = runner.invoke(main.entry_point, ["list", "experiment"])
-    assert result.exit_code == 0
-    assert "list experiment!" in result.output
+@pytest.mark.e2e
+def test_all_experiment_e2e():
+    submarine_client = submarine.ExperimentClient(host="http://localhost:8080")
+    environment = EnvironmentSpec(image="apache/submarine:tf-dist-mnist-test-1.0")
+    experiment_meta = ExperimentMeta(
+        name="mnist-dist",
+        namespace="default",
+        framework="Tensorflow",
+        cmd="python /var/tf_dist_mnist/dist_mnist.py --train_steps=100",
+        env_vars={"ENV1": "ENV1"},
+    )
 
+    worker_spec = ExperimentTaskSpec(resources="cpu=1,memory=1024M", replicas=1)
+    ps_spec = ExperimentTaskSpec(resources="cpu=1,memory=1024M", replicas=1)
 
-def test_get_experiment():
-    mock_experiment_id = "0"
-    runner = CliRunner()
-    result = runner.invoke(main.entry_point, ["get", "experiment", mock_experiment_id])
-    assert result.exit_code == 0
-    assert "get experiment! id={}".format(mock_experiment_id) in result.output
+    code_spec = CodeSpec(sync_mode="git", url="https://github.com/apache/submarine.git")
+
+    experiment_spec = ExperimentSpec(
+        meta=experiment_meta,
+        environment=environment,
+        code=code_spec,
+        spec={"Ps": ps_spec, "Worker": worker_spec},
+    )
 
+    experiment = submarine_client.create_experiment(experiment_spec=experiment_spec)
 
-def test_delete_experiment():
-    mock_experiment_id = "0"
-    runner = CliRunner()
-    result = runner.invoke(main.entry_point, ["delete", "experiment", mock_experiment_id])
+    # set env to display full table
+    runner = CliRunner(env={"COLUMNS": "191"})
+    # test list experiment
+    result = runner.invoke(main.entry_point, ["list", "experiment"])
     assert result.exit_code == 0
-    assert "delete experiment! id={}".format(mock_experiment_id) in result.output
+    assert "List of Experiments" in result.output
+    assert experiment["experimentId"] in result.output
+    assert experiment["spec"]["meta"]["name"] in result.output
+

Review comment:
       Also, check the "finishedTime", "createdTime", "runningTime" and status are not empty.

##########
File path: submarine-sdk/pysubmarine/tests/cli/test_experiment.py
##########
@@ -13,29 +13,58 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+import pytest
 from click.testing import CliRunner
 
+import submarine
 from submarine.cli import main
+from submarine.experiment.models.code_spec import CodeSpec
+from submarine.experiment.models.environment_spec import EnvironmentSpec
+from submarine.experiment.models.experiment_meta import ExperimentMeta
+from submarine.experiment.models.experiment_spec import ExperimentSpec
+from submarine.experiment.models.experiment_task_spec import ExperimentTaskSpec
 
 
-def test_list_experiment():
-    runner = CliRunner()
-    result = runner.invoke(main.entry_point, ["list", "experiment"])
-    assert result.exit_code == 0
-    assert "list experiment!" in result.output
+@pytest.mark.e2e
+def test_all_experiment_e2e():
+    submarine_client = submarine.ExperimentClient(host="http://localhost:8080")
+    environment = EnvironmentSpec(image="apache/submarine:tf-dist-mnist-test-1.0")
+    experiment_meta = ExperimentMeta(
+        name="mnist-dist",
+        namespace="default",
+        framework="Tensorflow",
+        cmd="python /var/tf_dist_mnist/dist_mnist.py --train_steps=100",
+        env_vars={"ENV1": "ENV1"},
+    )
 
+    worker_spec = ExperimentTaskSpec(resources="cpu=1,memory=1024M", replicas=1)
+    ps_spec = ExperimentTaskSpec(resources="cpu=1,memory=1024M", replicas=1)
 
-def test_get_experiment():
-    mock_experiment_id = "0"
-    runner = CliRunner()
-    result = runner.invoke(main.entry_point, ["get", "experiment", mock_experiment_id])
-    assert result.exit_code == 0
-    assert "get experiment! id={}".format(mock_experiment_id) in result.output
+    code_spec = CodeSpec(sync_mode="git", url="https://github.com/apache/submarine.git")
+
+    experiment_spec = ExperimentSpec(
+        meta=experiment_meta,
+        environment=environment,
+        code=code_spec,
+        spec={"Ps": ps_spec, "Worker": worker_spec},
+    )
 
+    experiment = submarine_client.create_experiment(experiment_spec=experiment_spec)
 
-def test_delete_experiment():
-    mock_experiment_id = "0"
-    runner = CliRunner()
-    result = runner.invoke(main.entry_point, ["delete", "experiment", mock_experiment_id])
+    # set env to display full table
+    runner = CliRunner(env={"COLUMNS": "191"})

Review comment:
       what is "191" mean? column width?
   If it is a column width make it a const variable

##########
File path: submarine-sdk/pysubmarine/submarine/cli/experiment/command.py
##########
@@ -15,24 +15,115 @@
  under the License.
 """
 
+import json
+from time import sleep
+
 import click
+from rich.console import Console
+from rich.json import JSON as richJSON
+from rich.panel import Panel
+from rich.table import Table
+
+from submarine.experiment.api.experiment_client import ExperimentClient
+from submarine.experiment.exceptions import ApiException
+
+experimentClient = ExperimentClient("http://localhost:8080")
 
 
 @click.command("experiment")
 def list_experiment():
     """List experiments"""
-    click.echo("list experiment!")
+    COLS_TO_SHOW = ["Name", "Id", "Tags", "Finished Time", "Created Time", "Running Time", "Status"]
+    console = Console()
+    try:
+        thread = experimentClient.list_experiments_async()
+        with console.status("[bold green] Fetching Experiments..."):
+            while not thread.ready():
+                sleep(1)
+
+        result = thread.get()
+        results = result.result
+
+        results = list(
+            map(
+                lambda r: [
+                    r["spec"]["meta"]["name"],
+                    r["experimentId"],
+                    ",".join(r["spec"]["meta"]["tags"]),
+                    r["finishedTime"],
+                    r["createdTime"],
+                    r["runningTime"],
+                    r["status"],
+                ],
+                results,
+            )
+        )
+
+        table = Table(title="List of Experiments")
+
+        for col in COLS_TO_SHOW:
+            table.add_column(col, overflow="fold")
+        for res in results:
+            table.add_row(*res)
+
+        console.print(table)
+
+    except ApiException as err:
+        if err.body is not None:
+            errbody = json.loads(err.body)
+            click.echo("[Api Error] {}".format(errbody["message"]))
+        else:
+            click.echo("[Api Error] {}".format(err))
 
 
 @click.command("experiment")
 @click.argument("id")
 def get_experiment(id):
     """Get experiments"""
-    click.echo("get experiment! id={}".format(id))
+    console = Console()
+    try:
+        thread = experimentClient.get_experiment_async(id)
+        with console.status("[bold green] Fetching Experiment(id = {} )...".format(id)):
+            while not thread.ready():
+                sleep(1)
+
+        result = thread.get()
+        result = result.result
+
+        json_data = richJSON.from_data(result)
+        console.print(Panel(json_data, title="Experiment(id = {} )".format(id)))
+    except ApiException as err:
+        if err.body is not None:
+            errbody = json.loads(err.body)
+            click.echo("[Api Error] {}".format(errbody["message"]))
+        else:
+            click.echo("[Api Error] {}".format(err))
 
 
 @click.command("experiment")
 @click.argument("id")
 def delete_experiment(id):

Review comment:
       @pingsutw, Do you think it is necessary to block the cli until the resources in k8s are correctly deleted?




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@submarine.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org