{
 "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": [
    "## Connect to 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": [
    "## Clean up\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": [
    "## Summary\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
}
