A script receives a task identifier from Catalyst Center and immediately displays success. The controller may still be processing the request, or the task may later fail. Reliable automation needs to track both the request and the resulting work.
Lab status: This is a proposed exercise, not a completed device test or client case study. Code examples require validation against your image and dependency versions. Controller/fabric platform access is not confirmed; offline samples demonstrate report logic only. My documented background is NOC/data center operations, multi-vendor Netmiko workflows and Python/Flask integration.
Offline example checks: October 20, 2026: the added helper/demo block executed with local fixtures, and related negative cases were checked offline. No live device, controller, fabric or service behavior was validated.
How does an asynchronous task differ from the API request?
Some Catalyst Center APIs accept work and return a task reference. The HTTP response describes request acceptance; the task resource describes subsequent execution. Exact response codes, fields and endpoints depend on the API and controller version. Inspect the documented response rather than assuming every endpoint uses the same structure.
Task IDs identify accepted asynchronous work. isError: false while endTime is absent is not a terminal success. Schema types and task resource paths vary by release; this example follows the documented task-style response and rejects an unfamiliar schema.
Why should a dashboard distinguish accepted from completed?
Use explicit states: accepted, pending, failed, completed-pending-readback and unresolved. A local polling deadline stops observation, not necessarily the remote task. Persist the task ID and request correlation data so an operator can reconcile without submitting a second write.
Proposed controller lab and offline task-state exercise
Start with synthetic sequences: pending-to-completed, pending-to-failed, malformed JSON shape and deadline exhaustion. GNS3/EVE-NG supplies routers, not automatically Catalyst Center licensing or a supported appliance. The supplied polling test makes no controller requests.
def task_state(payload):
task = payload.get("response")
if not isinstance(task, dict):
return "unknown"
if task.get("isError") is True:
return "failed"
if task.get("isError") is False and task.get("endTime"):
return "completed"
return "pending"
sample = {"response": {"isError": False}}
print(task_state(sample)) # Synthetic input: pending
What should a bounded task-polling loop handle?
The complete loop below accepts a fetch(task_id, timeout_seconds) adapter and never submits a write. A live adapter must issue a TLS-verified, authenticated GET to the installed release’s task endpoint, reject redirects, call raise_for_status() and validate JSON. It must not follow arbitrary URLs from an untrusted response. Exceptions remain execution failures at the worker boundary; they are not converted into task completion.
Worked Python example and input contract
import re
import time
def poll_task(fetch, task_id, deadline_seconds=30, interval=2,
clock=time.monotonic, sleep=time.sleep):
if deadline_seconds <= 0 or interval <= 0:
raise ValueError('positive_polling_budget_and_interval_required')
if not re.fullmatch(r'[A-Za-z0-9-]{1,128}', task_id):
raise ValueError('invalid_task_id')
deadline = clock()+deadline_seconds
while clock() < deadline:
remaining = deadline-clock()
if remaining <= 0:
break
payload = fetch(task_id, min(5, remaining))
task = payload.get('response') if isinstance(payload, dict) else None
if not isinstance(task, dict) or not isinstance(task.get('isError'), bool):
return {'state': 'unknown_schema', 'task_id': task_id}
if task['isError']:
return {'state': 'failed', 'task_id': task_id}
end = task.get('endTime')
if end is not None and (isinstance(end, bool) or not isinstance(end, (int,float))):
return {'state': 'unknown_schema', 'task_id': task_id}
if end and end > 0:
return {'state': 'completed_requires_readback', 'task_id': task_id}
sleep(max(0, min(interval, deadline-clock())))
return {'state': 'unresolved_deadline', 'task_id': task_id}
samples = iter([{'response': {'isError': False}},
{'response': {'isError': False, 'endTime': 1}}])
def offline_fetch(task_id, timeout):
return next(samples)
print(poll_task(offline_fetch, 'synthetic-task', sleep=lambda seconds: None))
What device state should be checked after task completion?
Read the affected resource independently and compare required fields after completion. A controller task may complete while the service still lacks connectivity. Record the readback’s own timestamp and errors instead of inheriting the task’s status.
What should a task-tracking integration deliver?
A scoped tool can persist task identity, bounded observation history, readback evidence and ownership of unresolved work. Do not expose arbitrary controller writes through a dashboard. A mock-tested polling loop establishes local state handling only, not support for a particular controller deployment.
Related troubleshooting and implementation
Explore the network automation troubleshooting collection to connect this issue to adjacent workflows. The Netmiko, NETCONF and RESTCONF comparison helps when you are choosing an interface rather than fixing an existing implementation.