Skip to content

Commit 72565a6

Browse files
authored
✨ Webserver reports copy progress (ITISFoundation#3287)
1 parent 07c1174 commit 72565a6

File tree

30 files changed

+750
-312
lines changed

30 files changed

+750
-312
lines changed

.github/workflows/ci-testing-deploy.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1076,7 +1076,7 @@ jobs:
10761076
path: codeclimate.${{ github.job }}_coverage.json
10771077

10781078
unit-test-storage:
1079-
timeout-minutes: 18 # if this timeout gets too small, then split the tests
1079+
timeout-minutes: 25 # if this timeout gets too small, then split the tests
10801080
name: "[unit] storage"
10811081
runs-on: ${{ matrix.os }}
10821082
strategy:

api/specs/storage/openapi.yaml

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,19 @@ servers:
2323
enum:
2424
- v0
2525
default: v0
26+
tags:
27+
- name: maintenance
28+
- name: location
29+
- name: dataset
30+
- name: file
31+
- name: tasks
2632
paths:
2733
/:
2834
get:
2935
summary: Service health-check endpoint
3036
description: Some general information on the API and state of the service behind
37+
tags:
38+
- maintenance
3139
operationId: health_check
3240
responses:
3341
"200":
@@ -43,6 +51,8 @@ paths:
4351
get:
4452
summary: checks status of self and connected services
4553
operationId: get_status
54+
tags:
55+
- maintenance
4656
responses:
4757
"200":
4858
description: returns app status check
@@ -51,6 +61,8 @@ paths:
5161
get:
5262
summary: Lists available storage locations
5363
operationId: get_storage_locations
64+
tags:
65+
- location
5466
parameters:
5567
- name: user_id
5668
in: query
@@ -71,6 +83,8 @@ paths:
7183
post:
7284
summary: Manually triggers the synchronisation of the file meta data table in the database
7385
operationId: synchronise_meta_data_table
86+
tags:
87+
- location
7488
parameters:
7589
- name: location_id
7690
in: path
@@ -103,6 +117,8 @@ paths:
103117
get:
104118
summary: Lists all dataset's metadata
105119
operationId: get_datasets_metadata
120+
tags:
121+
- dataset
106122
parameters:
107123
- name: location_id
108124
in: path
@@ -128,6 +144,8 @@ paths:
128144
get:
129145
summary: Lists all file's metadata
130146
operationId: get_files_metadata
147+
tags:
148+
- file
131149
parameters:
132150
- name: location_id
133151
in: path
@@ -158,6 +176,8 @@ paths:
158176
get:
159177
summary: Get dataset metadata
160178
operationId: get_files_metadata_dataset
179+
tags:
180+
- dataset
161181
parameters:
162182
- name: location_id
163183
in: path
@@ -188,6 +208,8 @@ paths:
188208
get:
189209
summary: Get file metadata
190210
operationId: get_file_metadata
211+
tags:
212+
- file
191213
parameters:
192214
- name: file_id
193215
in: path
@@ -218,6 +240,8 @@ paths:
218240
get:
219241
summary: Gets download link for file at location
220242
operationId: download_file
243+
tags:
244+
- file
221245
parameters:
222246
- name: file_id
223247
in: path
@@ -256,6 +280,8 @@ paths:
256280
put:
257281
summary: Returns upload object
258282
operationId: upload_file
283+
tags:
284+
- file
259285
parameters:
260286
- name: file_id
261287
in: path
@@ -316,6 +342,8 @@ paths:
316342
delete:
317343
summary: Deletes file
318344
operationId: delete_file
345+
tags:
346+
- file
319347
parameters:
320348
- name: file_id
321349
in: path
@@ -342,6 +370,8 @@ paths:
342370
post:
343371
summary: Asks the server to abort the upload and revert to the last valid version if any
344372
operationId: abort_upload_file
373+
tags:
374+
- file
345375
parameters:
346376
- name: file_id
347377
in: path
@@ -368,6 +398,8 @@ paths:
368398
post:
369399
summary: Asks the server to complete the upload
370400
operationId: complete_upload_file
401+
tags:
402+
- file
371403
parameters:
372404
- name: file_id
373405
in: path
@@ -427,6 +459,8 @@ paths:
427459
post:
428460
summary: Check for upload completion
429461
operationId: is_completed_upload_file
462+
tags:
463+
- file
430464
parameters:
431465
- name: future_id
432466
in: path
@@ -462,6 +496,8 @@ paths:
462496
post:
463497
summary: Returns the temporary access credentials for the user storage space
464498
operationId: get_or_create_temporary_s3_access
499+
tags:
500+
- file
465501
parameters:
466502
- name: user_id
467503
in: query
@@ -482,6 +518,8 @@ paths:
482518
post:
483519
summary: Returns metadata for all files matching a pattern
484520
operationId: search_files_starting_with
521+
tags:
522+
- file
485523
parameters:
486524
- name: user_id
487525
in: query
@@ -508,6 +546,8 @@ paths:
508546
post:
509547
summary: Deep copies of all data from source to destination project in s3
510548
operationId: copy_folders_from_project
549+
tags:
550+
- file
511551
parameters:
512552
- name: user_id
513553
in: query
@@ -543,6 +583,8 @@ paths:
543583
delete:
544584
summary: Deletes all objects within a node_id or within a project_id if node_id is omitted
545585
operationId: delete_folders_of_project
586+
tags:
587+
- file
546588
parameters:
547589
- name: folder_id
548590
in: path
@@ -567,6 +609,8 @@ paths:
567609
post:
568610
summary: Copy as soft link
569611
operationId: copy_as_soft_link
612+
tags:
613+
- file
570614
parameters:
571615
- name: file_id
572616
in: path
@@ -597,6 +641,68 @@ paths:
597641
$ref: "#/components/schemas/FileMetaEnvelope"
598642
default:
599643
$ref: "#/components/responses/DefaultErrorResponse"
644+
645+
/tasks:
646+
get:
647+
operationId: list_tasks
648+
tags:
649+
- tasks
650+
responses:
651+
"200":
652+
description: Returns the list of active tasks (running and/or done)
653+
content:
654+
application/json:
655+
schema:
656+
type: array
657+
items:
658+
$ref: "../common/schemas/task.yaml#/TaskEnveloped"
659+
/tasks/{task_id}:
660+
parameters:
661+
- name: task_id
662+
in: path
663+
required: true
664+
schema:
665+
type: string
666+
get:
667+
operationId: get_task_status
668+
tags:
669+
- tasks
670+
responses:
671+
"200":
672+
description: Returns the task status
673+
content:
674+
application/json:
675+
schema:
676+
$ref: "../common/schemas/task.yaml#/TaskStatusEnveloped"
677+
default:
678+
$ref: "#/components/responses/DefaultErrorResponse"
679+
delete:
680+
operationId: cancel_and_delete_task
681+
description: Aborts and remove the task
682+
tags:
683+
- tasks
684+
responses:
685+
"204":
686+
description: Task was successfully aborted
687+
default:
688+
$ref: "#/components/responses/DefaultErrorResponse"
689+
690+
/tasks/{task_id}/result:
691+
parameters:
692+
- name: task_id
693+
in: path
694+
required: true
695+
schema:
696+
type: string
697+
get:
698+
operationId: get_task_result
699+
tags:
700+
- tasks
701+
responses:
702+
"2XX":
703+
description: Retrieve the task result and returns directly its HTTP code
704+
default:
705+
$ref: "#/components/responses/DefaultErrorResponse"
600706
components:
601707
schemas:
602708
HealthCheckEnveloped:

api/specs/webserver/openapi-projects.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ paths:
8585
content:
8686
application/json:
8787
schema:
88-
$ref: "./components/schemas/task.yaml#/TaskEnveloped"
88+
$ref: "../common/schemas/task.yaml#/TaskEnveloped"
8989
links:
9090
CreationStatus:
9191
operationId: get_task_status

api/specs/webserver/openapi-tasks.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ paths:
1212
schema:
1313
type: array
1414
items:
15-
$ref: "./components/schemas/task.yaml#/TaskEnveloped"
15+
$ref: "../common/schemas/task.yaml#/TaskEnveloped"
1616

1717
/tasks/{task_id}:
1818
parameters:
@@ -31,7 +31,7 @@ paths:
3131
content:
3232
application/json:
3333
schema:
34-
$ref: "./components/schemas/task.yaml#/TaskStatusEnveloped"
34+
$ref: "../common/schemas/task.yaml#/TaskStatusEnveloped"
3535
default:
3636
$ref: "#/components/responses/DefaultErrorResponse"
3737
delete:

packages/models-library/src/models_library/projects_nodes_io.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,12 @@
1919
NodeID = UUID
2020

2121

22-
class NodeIDStr(ConstrainedStr):
22+
class UUIDStr(ConstrainedStr):
2323
regex: Optional[Pattern[str]] = re.compile(UUID_RE)
2424

2525

26+
NodeIDStr = UUIDStr
27+
2628
LocationID = int
2729
LocationName = str
2830

packages/service-library/src/servicelib/aiohttp/long_running_tasks/client.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
_DEFAULT_AIOHTTP_RETRY_POLICY = dict(
2323
retry=retry_if_exception_type(ClientConnectionError),
2424
wait=wait_random_exponential(max=20),
25-
stop=stop_after_delay(30),
25+
stop=stop_after_delay(60),
2626
reraise=True,
2727
)
2828

@@ -45,12 +45,12 @@ async def _wait_for_completion(
4545
session: ClientSession,
4646
task_id: TaskId,
4747
status_url: URL,
48-
wait_timeout_s: int,
48+
client_timeout: int,
4949
) -> AsyncGenerator[TaskProgress, None]:
5050
try:
5151

5252
async for attempt in AsyncRetrying(
53-
stop=stop_after_delay(wait_timeout_s),
53+
stop=stop_after_delay(client_timeout),
5454
reraise=True,
5555
retry=retry_if_exception_type(TryAgain),
5656
):
@@ -78,7 +78,7 @@ async def _wait_for_completion(
7878
except TryAgain as exc:
7979
# this is a timeout
8080
raise asyncio.TimeoutError(
81-
f"Long running task {task_id}, calling to {status_url} timed-out after {wait_timeout_s} seconds"
81+
f"Long running task {task_id}, calling to {status_url} timed-out after {client_timeout} seconds"
8282
) from exc
8383

8484

packages/service-library/src/servicelib/aiohttp/long_running_tasks/server.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
The server only has to return a `TaskId` in the handler creating the long
66
running task.
77
"""
8+
from ...long_running_tasks._models import ProgressMessage, ProgressPercent
89
from ...long_running_tasks._task import (
910
TaskAlreadyRunningError,
1011
TaskCancelledError,
@@ -21,6 +22,8 @@
2122
__all__: tuple[str, ...] = (
2223
"create_task_name_from_request",
2324
"get_tasks_manager",
25+
"ProgressMessage",
26+
"ProgressPercent",
2427
"setup",
2528
"start_long_running_task",
2629
"TaskAlreadyRunningError",

packages/service-library/src/servicelib/long_running_tasks/_models.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,14 @@
44
from datetime import datetime
55
from typing import Any, Awaitable, Callable, Coroutine, Optional
66

7-
from pydantic import BaseModel, Field, PositiveFloat, confloat, validator
7+
from pydantic import (
8+
BaseModel,
9+
Field,
10+
PositiveFloat,
11+
confloat,
12+
validate_arguments,
13+
validator,
14+
)
815

916
logger = logging.getLogger(__name__)
1017

@@ -30,6 +37,7 @@ class TaskProgress(BaseModel):
3037
message: ProgressMessage = Field(default="")
3138
percent: ProgressPercent = Field(default=0.0)
3239

40+
@validate_arguments
3341
def update(
3442
self,
3543
*,
@@ -50,6 +58,11 @@ def update(
5058
def create(cls) -> "TaskProgress":
5159
return cls.parse_obj(dict(message="", percent=0.0))
5260

61+
@validator("percent")
62+
@classmethod
63+
def round_value_to_3_digit(cls, v):
64+
return round(v, 3)
65+
5366

5467
class TrackedTask(BaseModel):
5568
task_id: str
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from servicelib.long_running_tasks._models import TaskProgress
2+
3+
4+
def test_progress_has_no_more_than_3_digits():
5+
progress = TaskProgress(percent=0.45646)
6+
assert progress.percent == 0.456

0 commit comments

Comments
 (0)