{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "# Tutorial: Hello, Workload!\n",
        "\n",
        "The shortest path from zero to a running container on DataRobot. In about five minutes you'll deploy the `containous/whoami` image as a **draft Workload**, hit its endpoint, and tear it down.\n",
        "\n",
        "A **draft Workload** is the hello-world equivalent for the Workload API: one `POST /workloads` call, no artifact registration ceremony, auto-cleanup after 8 hours.\n",
        "\n",
        "## Tutorial format\n",
        "\n",
        "When appropriate, steps below show the call using `curl`, followed by a code cell that runs the same call via `requests`. The `check(resp)` helper raises on non-2xx and prints the API's response body so any validation `detail` is visible alongside the traceback."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## DataRobotに接続する\n",
        "\n",
        "To connect to DataRobot, you need the following:\n",
        "\n",
        "- DataRobot API endpoint, `DATAROBOT_ENDPOINT`.\n",
        "- DataRobot API token, `DATAROBOT_API_TOKEN`.\n",
        "- For the cURL examples, a terminal with `curl` and `jq`.\n",
        "- For the Python Notebook examples, Python `requests` and `datarobot`.\n",
        "\n",
        "The connection details are set automatically inside this DataRobot Notebook. To run the cURL commands directly, export `DATAROBOT_ENDPOINT` and `DATAROBOT_API_TOKEN`:\n",
        "\n",
        "```bash\n",
        "export DATAROBOT_ENDPOINT=https://app.datarobot.com/api/v2\n",
        "export DATAROBOT_API_TOKEN=<your-api-token>\n",
        "```\n",
        "\n",
        "The cell below connects this notebook to DataRobot and sets up the `check(resp)` helper."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "import json\n",
        "import time\n",
        "\n",
        "import datarobot as dr\n",
        "import requests\n",
        "\n",
        "client = dr.Client()\n",
        "# client = dr.Client(endpoint=\"https://app.datarobot.com/api/v2\", token=\"YOUR_API_TOKEN\")\n",
        "\n",
        "API_BASE = client.endpoint.rstrip(\"/\")\n",
        "HEADERS = {\n",
        "    \"Authorization\": f\"Bearer {client.token}\",\n",
        "    \"Content-Type\": \"application/json\",\n",
        "}\n",
        "\n",
        "\n",
        "def check(resp):\n",
        "    if not resp.ok:\n",
        "        print(\"Status:\", resp.status_code, resp.reason)\n",
        "        try:\n",
        "            print(\"Body:\", json.dumps(resp.json(), indent=2))\n",
        "        except ValueError:\n",
        "            print(\"Body:\", resp.text)\n",
        "        resp.raise_for_status()\n",
        "    return resp\n",
        "\n",
        "\n",
        "print(\"Connected:\", API_BASE)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Deploy whoami\n",
        "\n",
        "Create a Workload with an inline draft artifact. [`containous/whoami`](https://hub.docker.com/r/containous/whoami) is a tiny HTTP server that echoes request information, perfect for confirming traffic flows end-to-end.\n",
        "\n",
        "```bash\n",
        "curl -s -X POST \"${DATAROBOT_ENDPOINT}/workloads\" \\\n",
        "  -H \"Authorization: Bearer ${DATAROBOT_API_TOKEN}\" \\\n",
        "  -H \"Content-Type: application/json\" \\\n",
        "  -d '{\n",
        "    \"name\": \"hello-whoami\",\n",
        "    \"artifact\": {\n",
        "      \"name\": \"whoami-artifact\",\n",
        "      \"type\": \"service\",\n",
        "      \"spec\": {\n",
        "        \"containerGroups\": [{\n",
        "          \"name\": \"default\",\n",
        "          \"containers\": [{\n",
        "            \"name\": \"whoami\",\n",
        "            \"imageUri\": \"containous/whoami:latest\",\n",
        "            \"port\": 8080,\n",
        "            \"primary\": true,\n",
        "            \"entrypoint\": [\"/whoami\", \"--port\", \"8080\"],\n",
        "            \"readinessProbe\": {\"path\": \"/\", \"port\": 8080, \"initialDelaySeconds\": 5}\n",
        "          }]\n",
        "        }]\n",
        "      }\n",
        "    },\n",
        "    \"runtime\": {\n",
        "      \"containerGroups\": [{\n",
        "        \"name\": \"default\",\n",
        "        \"replicaCount\": 1,\n",
        "        \"containers\": [{\n",
        "          \"name\": \"whoami\",\n",
        "          \"resourceAllocation\": {\"cpu\": 1, \"memory\": \"512MB\"}\n",
        "        }]\n",
        "      }]\n",
        "    }\n",
        "  }'\n",
        "```\n",
        "\n",
        "The response is a `WorkloadFormatted` object. `workload_id` is all you need for the rest of the tutorial.\n",
        "\n",
        "The artifact describes the container topology: image, port, entrypoint, env vars, probes. Anything you'd want a different deployment of the same artifact to be able to override (replica count, CPU/memory, autoscaling, resource bundles) lives in `runtime.containerGroups[]`. Entries are looked up by group `name` (default group is `\"default\"`); per-container overrides are looked up by container `name`, which is why both objects need names when you're customizing resources."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "payload = {\n",
        "    \"name\": f\"hello-whoami-{int(time.time())}\",\n",
        "    \"artifact\": {\n",
        "        \"name\": f\"whoami-artifact-{int(time.time())}\",\n",
        "        \"type\": \"service\",\n",
        "        \"spec\": {\n",
        "            \"containerGroups\": [{\n",
        "                \"name\": \"default\",\n",
        "                \"containers\": [{\n",
        "                    \"name\": \"whoami\",\n",
        "                    \"imageUri\": \"containous/whoami:latest\",\n",
        "                    \"port\": 8080,\n",
        "                    \"primary\": True,\n",
        "                    \"entrypoint\": [\"/whoami\", \"--port\", \"8080\"],\n",
        "                    \"readinessProbe\": {\"path\": \"/\", \"port\": 8080, \"initialDelaySeconds\": 5},\n",
        "                }]\n",
        "            }]\n",
        "        },\n",
        "    },\n",
        "    \"runtime\": {\n",
        "        \"containerGroups\": [{\n",
        "            \"name\": \"default\",\n",
        "            \"replicaCount\": 1,\n",
        "            \"containers\": [{\n",
        "                \"name\": \"whoami\",\n",
        "                \"resourceAllocation\": {\"cpu\": 1, \"memory\": \"512MB\"},\n",
        "            }],\n",
        "        }]\n",
        "    },\n",
        "}\n",
        "\n",
        "resp = requests.post(f\"{API_BASE}/workloads\", headers=HEADERS, json=payload, timeout=120)\n",
        "check(resp)\n",
        "workload_id = resp.json()[\"id\"]\n",
        "print(\"Workload ID:\", workload_id)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Wait for running\n",
        "\n",
        "Poll the Workload's `status` until it reaches `running`. Expected happy-path progression: `submitted` → `provisioning` → `launching` → `running`.\n",
        "\n",
        "```bash\n",
        "curl -s \"${DATAROBOT_ENDPOINT}/workloads/${WORKLOAD_ID}\" \\\n",
        "  -H \"Authorization: Bearer ${DATAROBOT_API_TOKEN}\" | jq -r '.status'\n",
        "```\n",
        "\n",
        "`running` requires the readiness probe to pass. The platform polls `readinessProbe.path` (here, `/`) on the container's port. If you typo the path or your container doesn't serve a 2xx on it, the Workload sits in `launching` even though the container itself is up.\n",
        "\n",
        "If you see `errored`, the events endpoint tells you what container failed and why:\n",
        "\n",
        "```bash\n",
        "curl -s \"${DATAROBOT_ENDPOINT}/workloads/${WORKLOAD_ID}/events\" \\\n",
        "  -H \"Authorization: Bearer ${DATAROBOT_API_TOKEN}\" | jq '.'\n",
        "```"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "deadline = time.time() + 600\n",
        "while time.time() < deadline:\n",
        "    r = requests.get(f\"{API_BASE}/workloads/{workload_id}\", headers=HEADERS, timeout=60)\n",
        "    check(r)\n",
        "    status = r.json().get(\"status\")\n",
        "    print(\"status:\", status)\n",
        "    if status == \"running\":\n",
        "        break\n",
        "    if status == \"errored\":\n",
        "        ev = requests.get(f\"{API_BASE}/workloads/{workload_id}/events\", headers=HEADERS, timeout=60)\n",
        "        print(\"events:\", ev.text[:4000])\n",
        "        raise RuntimeError(\"Workload errored; see events above\")\n",
        "    time.sleep(5)\n",
        "else:\n",
        "    raise TimeoutError(\"Timed out waiting for running\")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Say hello\n",
        "\n",
        "Read the invoke URL from the Workload's `endpoint` field and call it. `whoami` will echo request headers and connection info. That's your hello world.\n",
        "\n",
        "```bash\n",
        "ENDPOINT=$(curl -s \"${DATAROBOT_ENDPOINT}/workloads/${WORKLOAD_ID}\" \\\n",
        "  -H \"Authorization: Bearer ${DATAROBOT_API_TOKEN}\" | jq -r '.endpoint')\n",
        "\n",
        "curl -H \"Authorization: Bearer ${DATAROBOT_API_TOKEN}\" \"${ENDPOINT}\"\n",
        "```\n",
        "\n",
        "> **401 error:** If the call returns `401` immediately after `running`, the new route isn't active yet; wait a few seconds and retry."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "r = requests.get(f\"{API_BASE}/workloads/{workload_id}\", headers=HEADERS, timeout=60)\n",
        "check(r)\n",
        "invoke_url = r.json()[\"endpoint\"]\n",
        "if invoke_url.startswith(\"http://\"):\n",
        "    invoke_url = \"https://\" + invoke_url.removeprefix(\"http://\")\n",
        "print(\"Invoke URL:\", invoke_url)\n",
        "\n",
        "hello = requests.get(invoke_url, headers={\"Authorization\": f\"Bearer {client.token}\"}, timeout=60)\n",
        "check(hello)\n",
        "print(hello.text)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## 削除\n",
        "\n",
        "`POST /workloads/{id}/stop` returns `202 Accepted` and stops the underlying proton. Draft Workloads also auto-terminate after 8 hours, so cleanup is optional, but good hygiene.\n",
        "\n",
        "```bash\n",
        "curl -X POST \"${DATAROBOT_ENDPOINT}/workloads/${WORKLOAD_ID}/stop\" \\\n",
        "  -H \"Authorization: Bearer ${DATAROBOT_API_TOKEN}\"\n",
        "```"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "resp = requests.post(f\"{API_BASE}/workloads/{workload_id}/stop\", headers=HEADERS, timeout=120)\n",
        "print(\"stop status:\", resp.status_code)\n",
        "if resp.text:\n",
        "    print(resp.text[:2000])"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## サマリー\n",
        "\n",
        "In this tutorial:\n",
        "\n",
        "- You created a **draft Workload** from an inline draft artifact (type `service`).\n",
        "- The platform built the container group, ran readiness probes, and assigned an invoke URL.\n",
        "- Because the artifact is `draft`, the Workload is short-lived: 8-hour TTL, one Workload per draft artifact, automatic cleanup."
      ]
    }
  ],
  "metadata": {
    "kernelspec": {
      "display_name": "Python 3",
      "language": "python",
      "name": "python3"
    },
    "language_info": {
      "name": "python",
      "version": "3.11"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 5
}