Add Waiting state on the backend and frontend (#839)
* operator: add waiting state - add pods as related objects - inspect pod status, set crawl status to 'waiting' if no pods are running frontend: - frontend support for 'waiting' state - show waiting icon from mocks --------- Co-authored-by: Henry Wilkinson <henry@wilkinson.graphics>
This commit is contained in:
parent
70319594c2
commit
2cae065c46
@ -32,6 +32,7 @@ from .crawls import (
|
|||||||
STS = "StatefulSet.apps/v1"
|
STS = "StatefulSet.apps/v1"
|
||||||
CMAP = "ConfigMap.v1"
|
CMAP = "ConfigMap.v1"
|
||||||
PVC = "PersistentVolumeClaim.v1"
|
PVC = "PersistentVolumeClaim.v1"
|
||||||
|
POD = "Pod.v1"
|
||||||
|
|
||||||
DEFAULT_TTL = 30
|
DEFAULT_TTL = 30
|
||||||
|
|
||||||
@ -186,9 +187,10 @@ class BtrixOperator(K8sAPI):
|
|||||||
crawl_sts = f"crawl-{crawl_id}"
|
crawl_sts = f"crawl-{crawl_id}"
|
||||||
redis_sts = f"redis-{crawl_id}"
|
redis_sts = f"redis-{crawl_id}"
|
||||||
|
|
||||||
has_crawl_children = STS in data.children and crawl_sts in data.children[STS]
|
has_crawl_children = crawl_sts in data.children[STS]
|
||||||
if has_crawl_children:
|
if has_crawl_children:
|
||||||
status = await self.sync_crawl_state(redis_url, crawl, status)
|
pods = data.related[POD]
|
||||||
|
status = await self.sync_crawl_state(redis_url, crawl, status, pods)
|
||||||
elif not status.finished:
|
elif not status.finished:
|
||||||
status.state = "starting"
|
status.state = "starting"
|
||||||
|
|
||||||
@ -220,7 +222,7 @@ class BtrixOperator(K8sAPI):
|
|||||||
"spec"
|
"spec"
|
||||||
]["volumeClaimTemplates"]
|
]["volumeClaimTemplates"]
|
||||||
|
|
||||||
has_redis_children = STS in data.children and redis_sts in data.children[STS]
|
has_redis_children = redis_sts in data.children[STS]
|
||||||
if has_redis_children:
|
if has_redis_children:
|
||||||
children[2]["spec"]["volumeClaimTemplates"] = data.children[STS][redis_sts][
|
children[2]["spec"]["volumeClaimTemplates"] = data.children[STS][redis_sts][
|
||||||
"spec"
|
"spec"
|
||||||
@ -251,6 +253,13 @@ class BtrixOperator(K8sAPI):
|
|||||||
"resource": "persistentvolumeclaims",
|
"resource": "persistentvolumeclaims",
|
||||||
"labelSelector": {"matchLabels": {"crawl": crawl_id}},
|
"labelSelector": {"matchLabels": {"crawl": crawl_id}},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"resource": "pods",
|
||||||
|
"labelSelector": {
|
||||||
|
"matchLabels": {"crawl": crawl_id, "role": "crawler"}
|
||||||
|
},
|
||||||
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -328,7 +337,7 @@ class BtrixOperator(K8sAPI):
|
|||||||
except:
|
except:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def sync_crawl_state(self, redis_url, crawl, status):
|
async def sync_crawl_state(self, redis_url, crawl, status, pods):
|
||||||
"""sync crawl state for running crawl"""
|
"""sync crawl state for running crawl"""
|
||||||
redis = await self._get_redis(redis_url)
|
redis = await self._get_redis(redis_url)
|
||||||
if not redis:
|
if not redis:
|
||||||
@ -354,7 +363,7 @@ class BtrixOperator(K8sAPI):
|
|||||||
status.filesAdded = int(await redis.get("filesAdded") or 0)
|
status.filesAdded = int(await redis.get("filesAdded") or 0)
|
||||||
|
|
||||||
# update stats and get status
|
# update stats and get status
|
||||||
return await self.update_crawl_state(redis, crawl, status)
|
return await self.update_crawl_state(redis, crawl, status, pods)
|
||||||
|
|
||||||
# pylint: disable=broad-except
|
# pylint: disable=broad-except
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
@ -362,6 +371,20 @@ class BtrixOperator(K8sAPI):
|
|||||||
print(f"Crawl get failed: {exc}, will try again")
|
print(f"Crawl get failed: {exc}, will try again")
|
||||||
return status
|
return status
|
||||||
|
|
||||||
|
async def check_if_pods_running(self, pods):
|
||||||
|
"""check if at least one crawler pod has started"""
|
||||||
|
try:
|
||||||
|
for pod in pods.values():
|
||||||
|
print("Phase", pod["status"]["phase"])
|
||||||
|
if pod["status"]["phase"] == "Running":
|
||||||
|
return True
|
||||||
|
# pylint: disable=bare-except
|
||||||
|
except:
|
||||||
|
# assume no valid pod found
|
||||||
|
pass
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
async def add_file_to_crawl(self, cc_data, crawl):
|
async def add_file_to_crawl(self, cc_data, crawl):
|
||||||
"""Handle finished CrawlFile to db"""
|
"""Handle finished CrawlFile to db"""
|
||||||
|
|
||||||
@ -386,7 +409,7 @@ class BtrixOperator(K8sAPI):
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def update_crawl_state(self, redis, crawl, status):
|
async def update_crawl_state(self, redis, crawl, status, pods):
|
||||||
"""update crawl state and check if crawl is now done"""
|
"""update crawl state and check if crawl is now done"""
|
||||||
results = await redis.hvals(f"{crawl.id}:status")
|
results = await redis.hvals(f"{crawl.id}:status")
|
||||||
stats = await get_redis_crawl_stats(redis, crawl.id)
|
stats = await get_redis_crawl_stats(redis, crawl.id)
|
||||||
@ -405,6 +428,15 @@ class BtrixOperator(K8sAPI):
|
|||||||
# backwards compatibility with older crawler
|
# backwards compatibility with older crawler
|
||||||
await redis.set("crawl-stop", "1")
|
await redis.set("crawl-stop", "1")
|
||||||
|
|
||||||
|
# check if at least one pod started running
|
||||||
|
# otherwise, mark as 'waiting' and return
|
||||||
|
if not await self.check_if_pods_running(pods):
|
||||||
|
if status.state not in ("waiting", "canceled"):
|
||||||
|
await update_crawl(self.crawls, crawl.id, state="waiting")
|
||||||
|
status.state = "waiting"
|
||||||
|
|
||||||
|
return status
|
||||||
|
|
||||||
# optimization: don't update db once crawl is already running
|
# optimization: don't update db once crawl is already running
|
||||||
# will set stats at when crawl is finished, otherwise can read
|
# will set stats at when crawl is finished, otherwise can read
|
||||||
# directly from redis
|
# directly from redis
|
||||||
|
@ -61,7 +61,7 @@ def test_wait_for_complete(admin_auth_headers, default_org_id, admin_crawl_id):
|
|||||||
|
|
||||||
# ensure filename matches specified pattern
|
# ensure filename matches specified pattern
|
||||||
# set in default_crawl_filename_template
|
# set in default_crawl_filename_template
|
||||||
assert re.search('/[\\d]+-testing-[\\w-]+\.wacz', data["resources"][0]["path"])
|
assert re.search("/[\\d]+-testing-[\\w-]+\\.wacz", data["resources"][0]["path"])
|
||||||
|
|
||||||
assert data["tags"] == ["wr-test-1", "wr-test-2"]
|
assert data["tags"] == ["wr-test-1", "wr-test-2"]
|
||||||
|
|
||||||
|
@ -76,6 +76,17 @@ export class CrawlStatus extends LitElement {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case "waiting": {
|
||||||
|
icon = html`<sl-icon
|
||||||
|
name="hourglass-split"
|
||||||
|
class="animatePulse"
|
||||||
|
slot="prefix"
|
||||||
|
style="color: var(--sl-color-purple-600)"
|
||||||
|
></sl-icon>`;
|
||||||
|
label = msg("Waiting");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case "running": {
|
case "running": {
|
||||||
icon = html`<sl-icon
|
icon = html`<sl-icon
|
||||||
name="dot"
|
name="dot"
|
||||||
|
@ -90,6 +90,7 @@ export class CrawlDetail extends LiteElement {
|
|||||||
return (
|
return (
|
||||||
this.crawl.state === "running" ||
|
this.crawl.state === "running" ||
|
||||||
this.crawl.state === "starting" ||
|
this.crawl.state === "starting" ||
|
||||||
|
this.crawl.state === "waiting" ||
|
||||||
this.crawl.state === "stopping"
|
this.crawl.state === "stopping"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -794,15 +794,16 @@ export class WorkflowDetail extends LiteElement {
|
|||||||
if (!this.authState || !this.workflow?.currCrawlState) return "";
|
if (!this.authState || !this.workflow?.currCrawlState) return "";
|
||||||
|
|
||||||
const isStarting = this.workflow.currCrawlState === "starting";
|
const isStarting = this.workflow.currCrawlState === "starting";
|
||||||
|
const isWaiting = this.workflow.currCrawlState === "waiting";
|
||||||
const isRunning = this.workflow.currCrawlState === "running";
|
const isRunning = this.workflow.currCrawlState === "running";
|
||||||
const isStopping = this.workflow.currCrawlState === "stopping";
|
const isStopping = this.workflow.currCrawlState === "stopping";
|
||||||
const authToken = this.authState.headers.Authorization.split(" ")[1];
|
const authToken = this.authState.headers.Authorization.split(" ")[1];
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
${isStarting
|
${isStarting || isWaiting
|
||||||
? html`<div class="rounded border p-3">
|
? html`<div class="rounded border p-3">
|
||||||
<p class="text-sm text-neutral-600 motion-safe:animate-pulse">
|
<p class="text-sm text-neutral-600 motion-safe:animate-pulse">
|
||||||
${msg("Crawl starting...")}
|
${isStarting ? msg("Crawl starting...") : msg("Crawl waiting for available resources before it can start...")}
|
||||||
</p>
|
</p>
|
||||||
</div>`
|
</div>`
|
||||||
: isActive(this.workflow.currCrawlState)
|
: isActive(this.workflow.currCrawlState)
|
||||||
|
@ -90,6 +90,7 @@ export type Profile = {
|
|||||||
|
|
||||||
export type CrawlState =
|
export type CrawlState =
|
||||||
| "starting"
|
| "starting"
|
||||||
|
| "waiting"
|
||||||
| "running"
|
| "running"
|
||||||
| "complete"
|
| "complete"
|
||||||
| "failed"
|
| "failed"
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import type { CrawlState } from "../types/crawler";
|
import type { CrawlState } from "../types/crawler";
|
||||||
export const activeCrawlStates: CrawlState[] = [
|
export const activeCrawlStates: CrawlState[] = [
|
||||||
"starting",
|
"starting",
|
||||||
|
"waiting",
|
||||||
"running",
|
"running",
|
||||||
"stopping",
|
"stopping",
|
||||||
];
|
];
|
||||||
|
Loading…
Reference in New Issue
Block a user