data-syncmaster 0.1.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data_syncmaster-0.1.1.dist-info/LICENSE.txt +203 -0
- data_syncmaster-0.1.1.dist-info/METADATA +115 -0
- data_syncmaster-0.1.1.dist-info/RECORD +110 -0
- data_syncmaster-0.1.1.dist-info/WHEEL +4 -0
- syncmaster/__init__.py +6 -0
- syncmaster/backend/__init__.py +2 -0
- syncmaster/backend/api/__init__.py +2 -0
- syncmaster/backend/api/deps.py +20 -0
- syncmaster/backend/api/monitoring.py +10 -0
- syncmaster/backend/api/router.py +10 -0
- syncmaster/backend/api/v1/__init__.py +2 -0
- syncmaster/backend/api/v1/auth/__init__.py +2 -0
- syncmaster/backend/api/v1/auth/router.py +32 -0
- syncmaster/backend/api/v1/auth/utils.py +26 -0
- syncmaster/backend/api/v1/connections.py +300 -0
- syncmaster/backend/api/v1/groups.py +225 -0
- syncmaster/backend/api/v1/queue.py +148 -0
- syncmaster/backend/api/v1/router.py +18 -0
- syncmaster/backend/api/v1/transfers/__init__.py +2 -0
- syncmaster/backend/api/v1/transfers/router.py +469 -0
- syncmaster/backend/api/v1/transfers/utils.py +17 -0
- syncmaster/backend/api/v1/users.py +75 -0
- syncmaster/backend/export_openapi_schema.py +26 -0
- syncmaster/backend/handler.py +203 -0
- syncmaster/backend/logger.py +2 -0
- syncmaster/backend/main.py +63 -0
- syncmaster/backend/pre_start.py +94 -0
- syncmaster/backend/services/__init__.py +4 -0
- syncmaster/backend/services/auth.py +58 -0
- syncmaster/backend/services/unit_of_work.py +44 -0
- syncmaster/config.py +110 -0
- syncmaster/db/__init__.py +2 -0
- syncmaster/db/alembic.ini +41 -0
- syncmaster/db/base.py +28 -0
- syncmaster/db/factory.py +37 -0
- syncmaster/db/migrations/README +1 -0
- syncmaster/db/migrations/__init__.py +2 -0
- syncmaster/db/migrations/env.py +87 -0
- syncmaster/db/migrations/script.py.mako +24 -0
- syncmaster/db/migrations/versions/2023-11-23_478240cdad4b_init.py +242 -0
- syncmaster/db/migrations/versions/__init__.py +2 -0
- syncmaster/db/mixins.py +33 -0
- syncmaster/db/models.py +194 -0
- syncmaster/db/repositories/__init__.py +22 -0
- syncmaster/db/repositories/base.py +109 -0
- syncmaster/db/repositories/connection.py +138 -0
- syncmaster/db/repositories/credentials_repository.py +87 -0
- syncmaster/db/repositories/group.py +264 -0
- syncmaster/db/repositories/queue.py +195 -0
- syncmaster/db/repositories/repository_with_owner.py +115 -0
- syncmaster/db/repositories/run.py +78 -0
- syncmaster/db/repositories/transfer.py +202 -0
- syncmaster/db/repositories/user.py +72 -0
- syncmaster/db/repositories/utils.py +25 -0
- syncmaster/db/utils.py +31 -0
- syncmaster/dto/__init__.py +2 -0
- syncmaster/dto/connections.py +60 -0
- syncmaster/dto/transfers.py +46 -0
- syncmaster/exceptions/__init__.py +13 -0
- syncmaster/exceptions/base.py +12 -0
- syncmaster/exceptions/connection.py +28 -0
- syncmaster/exceptions/credentials.py +8 -0
- syncmaster/exceptions/group.py +27 -0
- syncmaster/exceptions/queue.py +16 -0
- syncmaster/exceptions/run.py +19 -0
- syncmaster/exceptions/transfer.py +39 -0
- syncmaster/exceptions/user.py +11 -0
- syncmaster/schemas/__init__.py +2 -0
- syncmaster/schemas/v1/__init__.py +54 -0
- syncmaster/schemas/v1/auth.py +12 -0
- syncmaster/schemas/v1/connection_types.py +9 -0
- syncmaster/schemas/v1/connections/__init__.py +2 -0
- syncmaster/schemas/v1/connections/connection.py +146 -0
- syncmaster/schemas/v1/connections/hdfs.py +40 -0
- syncmaster/schemas/v1/connections/hive.py +40 -0
- syncmaster/schemas/v1/connections/oracle.py +58 -0
- syncmaster/schemas/v1/connections/postgres.py +48 -0
- syncmaster/schemas/v1/connections/s3.py +66 -0
- syncmaster/schemas/v1/file_formats.py +7 -0
- syncmaster/schemas/v1/groups.py +39 -0
- syncmaster/schemas/v1/page.py +40 -0
- syncmaster/schemas/v1/queue.py +32 -0
- syncmaster/schemas/v1/status.py +16 -0
- syncmaster/schemas/v1/transfer_types.py +6 -0
- syncmaster/schemas/v1/transfers/__init__.py +172 -0
- syncmaster/schemas/v1/transfers/db.py +23 -0
- syncmaster/schemas/v1/transfers/file/__init__.py +2 -0
- syncmaster/schemas/v1/transfers/file/base.py +47 -0
- syncmaster/schemas/v1/transfers/file/hdfs.py +27 -0
- syncmaster/schemas/v1/transfers/file/s3.py +27 -0
- syncmaster/schemas/v1/transfers/file_format.py +29 -0
- syncmaster/schemas/v1/transfers/run.py +37 -0
- syncmaster/schemas/v1/transfers/strategy.py +15 -0
- syncmaster/schemas/v1/types.py +5 -0
- syncmaster/schemas/v1/users.py +83 -0
- syncmaster/worker/__init__.py +2 -0
- syncmaster/worker/base.py +14 -0
- syncmaster/worker/config.py +18 -0
- syncmaster/worker/controller.py +127 -0
- syncmaster/worker/handlers/__init__.py +2 -0
- syncmaster/worker/handlers/base.py +49 -0
- syncmaster/worker/handlers/file/__init__.py +2 -0
- syncmaster/worker/handlers/file/base.py +56 -0
- syncmaster/worker/handlers/file/hdfs.py +14 -0
- syncmaster/worker/handlers/file/s3.py +20 -0
- syncmaster/worker/handlers/hive.py +41 -0
- syncmaster/worker/handlers/oracle.py +48 -0
- syncmaster/worker/handlers/postgres.py +47 -0
- syncmaster/worker/spark.py +93 -0
- syncmaster/worker/transfer.py +85 -0
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2023-2024 MTS (Mobile Telesystems)
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
import asyncio
|
|
4
|
+
|
|
5
|
+
from fastapi import APIRouter, Depends, Query, status
|
|
6
|
+
from kombu.exceptions import KombuError
|
|
7
|
+
|
|
8
|
+
from syncmaster.backend.api.deps import UnitOfWorkMarker
|
|
9
|
+
from syncmaster.backend.api.v1.transfers.utils import (
|
|
10
|
+
process_file_transfer_directory_path,
|
|
11
|
+
)
|
|
12
|
+
from syncmaster.backend.services import UnitOfWork, get_user
|
|
13
|
+
from syncmaster.db.models import Status, User
|
|
14
|
+
from syncmaster.db.utils import Permission
|
|
15
|
+
from syncmaster.exceptions.base import ActionNotAllowedError
|
|
16
|
+
from syncmaster.exceptions.connection import ConnectionNotFoundError
|
|
17
|
+
from syncmaster.exceptions.group import GroupNotFoundError
|
|
18
|
+
from syncmaster.exceptions.queue import DifferentTransferAndQueueGroupError
|
|
19
|
+
from syncmaster.exceptions.run import CannotConnectToTaskQueueError
|
|
20
|
+
from syncmaster.exceptions.transfer import (
|
|
21
|
+
DifferentTransferAndConnectionsGroupsError,
|
|
22
|
+
DifferentTypeConnectionsAndParamsError,
|
|
23
|
+
TransferNotFoundError,
|
|
24
|
+
)
|
|
25
|
+
from syncmaster.schemas.v1.status import (
|
|
26
|
+
StatusCopyTransferResponseSchema,
|
|
27
|
+
StatusResponseSchema,
|
|
28
|
+
)
|
|
29
|
+
from syncmaster.schemas.v1.transfers import (
|
|
30
|
+
CopyTransferSchema,
|
|
31
|
+
CreateTransferSchema,
|
|
32
|
+
ReadTransferSchema,
|
|
33
|
+
TransferPageSchema,
|
|
34
|
+
UpdateTransferSchema,
|
|
35
|
+
)
|
|
36
|
+
from syncmaster.schemas.v1.transfers.run import (
|
|
37
|
+
CreateRunSchema,
|
|
38
|
+
ReadRunSchema,
|
|
39
|
+
RunPageSchema,
|
|
40
|
+
)
|
|
41
|
+
from syncmaster.worker.config import celery
|
|
42
|
+
|
|
43
|
+
router = APIRouter(tags=["Transfers"])
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@router.get("/transfers")
|
|
47
|
+
async def read_transfers(
|
|
48
|
+
group_id: int,
|
|
49
|
+
page: int = Query(gt=0, default=1),
|
|
50
|
+
page_size: int = Query(gt=0, le=200, default=20),
|
|
51
|
+
current_user: User = Depends(get_user(is_active=True)),
|
|
52
|
+
unit_of_work: UnitOfWork = Depends(UnitOfWorkMarker),
|
|
53
|
+
) -> TransferPageSchema:
|
|
54
|
+
"""Return transfers in page format"""
|
|
55
|
+
resource_role = await unit_of_work.transfer.get_group_permission(
|
|
56
|
+
user=current_user,
|
|
57
|
+
group_id=group_id,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
if resource_role == Permission.NONE:
|
|
61
|
+
raise GroupNotFoundError
|
|
62
|
+
|
|
63
|
+
pagination = await unit_of_work.transfer.paginate(
|
|
64
|
+
page=page,
|
|
65
|
+
page_size=page_size,
|
|
66
|
+
group_id=group_id,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
return TransferPageSchema.from_pagination(pagination=pagination)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@router.post("/transfers")
|
|
73
|
+
async def create_transfer(
|
|
74
|
+
transfer_data: CreateTransferSchema,
|
|
75
|
+
current_user: User = Depends(get_user(is_active=True)),
|
|
76
|
+
unit_of_work: UnitOfWork = Depends(UnitOfWorkMarker),
|
|
77
|
+
) -> ReadTransferSchema:
|
|
78
|
+
group_permission = await unit_of_work.transfer.get_group_permission(
|
|
79
|
+
user=current_user,
|
|
80
|
+
group_id=transfer_data.group_id,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
if group_permission < Permission.WRITE:
|
|
84
|
+
raise ActionNotAllowedError
|
|
85
|
+
|
|
86
|
+
target_connection = await unit_of_work.connection.read_by_id(
|
|
87
|
+
connection_id=transfer_data.target_connection_id,
|
|
88
|
+
)
|
|
89
|
+
source_connection = await unit_of_work.connection.read_by_id(
|
|
90
|
+
connection_id=transfer_data.source_connection_id,
|
|
91
|
+
)
|
|
92
|
+
queue = await unit_of_work.queue.read_by_id(transfer_data.queue_id)
|
|
93
|
+
|
|
94
|
+
if (
|
|
95
|
+
target_connection.group_id != source_connection.group_id
|
|
96
|
+
or target_connection.group_id != transfer_data.group_id
|
|
97
|
+
or source_connection.group_id != transfer_data.group_id
|
|
98
|
+
):
|
|
99
|
+
raise DifferentTransferAndConnectionsGroupsError
|
|
100
|
+
|
|
101
|
+
if target_connection.data["type"] != transfer_data.target_params.type:
|
|
102
|
+
raise DifferentTypeConnectionsAndParamsError(
|
|
103
|
+
connection_type=target_connection.data["type"],
|
|
104
|
+
conn="target",
|
|
105
|
+
params_type=transfer_data.target_params.type,
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
if source_connection.data["type"] != transfer_data.source_params.type:
|
|
109
|
+
raise DifferentTypeConnectionsAndParamsError(
|
|
110
|
+
connection_type=source_connection.data["type"],
|
|
111
|
+
conn="source",
|
|
112
|
+
params_type=transfer_data.source_params.type,
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
if transfer_data.group_id != queue.group_id:
|
|
116
|
+
raise DifferentTransferAndQueueGroupError
|
|
117
|
+
|
|
118
|
+
transfer_data = process_file_transfer_directory_path(transfer_data) # type: ignore
|
|
119
|
+
|
|
120
|
+
async with unit_of_work:
|
|
121
|
+
transfer = await unit_of_work.transfer.create(
|
|
122
|
+
group_id=transfer_data.group_id,
|
|
123
|
+
name=transfer_data.name,
|
|
124
|
+
description=transfer_data.description,
|
|
125
|
+
target_connection_id=transfer_data.target_connection_id,
|
|
126
|
+
source_connection_id=transfer_data.source_connection_id,
|
|
127
|
+
source_params=transfer_data.source_params.dict(),
|
|
128
|
+
target_params=transfer_data.target_params.dict(),
|
|
129
|
+
strategy_params=transfer_data.strategy_params.dict(),
|
|
130
|
+
queue_id=transfer_data.queue_id,
|
|
131
|
+
)
|
|
132
|
+
return ReadTransferSchema.from_orm(transfer)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
@router.get("/transfers/{transfer_id}")
|
|
136
|
+
async def read_transfer(
|
|
137
|
+
transfer_id: int,
|
|
138
|
+
current_user: User = Depends(get_user(is_active=True)),
|
|
139
|
+
unit_of_work: UnitOfWork = Depends(UnitOfWorkMarker),
|
|
140
|
+
) -> ReadTransferSchema:
|
|
141
|
+
"""Return transfer data by transfer ID"""
|
|
142
|
+
resource_role = await unit_of_work.transfer.get_resource_permission(
|
|
143
|
+
user=current_user,
|
|
144
|
+
resource_id=transfer_id,
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
if resource_role == Permission.NONE:
|
|
148
|
+
raise TransferNotFoundError
|
|
149
|
+
|
|
150
|
+
transfer = await unit_of_work.transfer.read_by_id(transfer_id=transfer_id)
|
|
151
|
+
return ReadTransferSchema.from_orm(transfer)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
@router.post("/transfers/{transfer_id}/copy_transfer")
|
|
155
|
+
async def copy_transfer(
|
|
156
|
+
transfer_id: int,
|
|
157
|
+
transfer_data: CopyTransferSchema,
|
|
158
|
+
current_user: User = Depends(get_user(is_active=True)),
|
|
159
|
+
unit_of_work: UnitOfWork = Depends(UnitOfWorkMarker),
|
|
160
|
+
) -> StatusCopyTransferResponseSchema:
|
|
161
|
+
# Check: user can copy transfer
|
|
162
|
+
target_source_transfer_rules = await asyncio.gather(
|
|
163
|
+
unit_of_work.transfer.get_resource_permission(
|
|
164
|
+
user=current_user,
|
|
165
|
+
resource_id=transfer_id,
|
|
166
|
+
),
|
|
167
|
+
unit_of_work.transfer.get_group_permission(
|
|
168
|
+
user=current_user,
|
|
169
|
+
group_id=transfer_data.new_group_id,
|
|
170
|
+
),
|
|
171
|
+
)
|
|
172
|
+
resource_role, target_group_role = target_source_transfer_rules
|
|
173
|
+
|
|
174
|
+
if resource_role == Permission.NONE:
|
|
175
|
+
raise TransferNotFoundError
|
|
176
|
+
|
|
177
|
+
if target_group_role < Permission.WRITE:
|
|
178
|
+
raise ActionNotAllowedError
|
|
179
|
+
|
|
180
|
+
# Check: user can delete transfer
|
|
181
|
+
if transfer_data.remove_source and resource_role < Permission.DELETE:
|
|
182
|
+
raise ActionNotAllowedError
|
|
183
|
+
|
|
184
|
+
transfer = await unit_of_work.transfer.read_by_id(transfer_id=transfer_id)
|
|
185
|
+
# Check: user can copy connection
|
|
186
|
+
target_source_connection_rules = await asyncio.gather(
|
|
187
|
+
unit_of_work.connection.get_resource_permission(
|
|
188
|
+
user=current_user,
|
|
189
|
+
resource_id=transfer.source_connection_id,
|
|
190
|
+
),
|
|
191
|
+
unit_of_work.connection.get_resource_permission(
|
|
192
|
+
user=current_user,
|
|
193
|
+
resource_id=transfer.target_connection_id,
|
|
194
|
+
),
|
|
195
|
+
)
|
|
196
|
+
source_connection_role, target_connection_role = target_source_connection_rules
|
|
197
|
+
|
|
198
|
+
if source_connection_role == Permission.NONE or target_connection_role == Permission.NONE:
|
|
199
|
+
raise ConnectionNotFoundError
|
|
200
|
+
|
|
201
|
+
# Check: new queue exists
|
|
202
|
+
new_queue = await unit_of_work.queue.read_by_id(queue_id=transfer_data.new_queue_id)
|
|
203
|
+
|
|
204
|
+
# Acheck: new_queue_id and new_group_id are similar
|
|
205
|
+
if new_queue.group_id != transfer_data.new_group_id:
|
|
206
|
+
raise DifferentTransferAndQueueGroupError
|
|
207
|
+
|
|
208
|
+
async with unit_of_work:
|
|
209
|
+
copied_source_connection = await unit_of_work.connection.copy(
|
|
210
|
+
connection_id=transfer.source_connection_id,
|
|
211
|
+
new_group_id=transfer_data.new_group_id,
|
|
212
|
+
new_name=transfer_data.new_source_connection_name,
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
copied_target_connection = copied_source_connection
|
|
216
|
+
|
|
217
|
+
if transfer.source_connection_id != transfer.target_connection_id: # Source and target are not the same
|
|
218
|
+
copied_target_connection = await unit_of_work.connection.copy(
|
|
219
|
+
connection_id=transfer.target_connection_id,
|
|
220
|
+
new_group_id=transfer_data.new_group_id,
|
|
221
|
+
new_name=transfer_data.new_target_connection_name,
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
copied_transfer = await unit_of_work.transfer.copy(
|
|
225
|
+
transfer_id=transfer_id,
|
|
226
|
+
new_group_id=transfer_data.new_group_id,
|
|
227
|
+
new_source_connection=copied_source_connection.id,
|
|
228
|
+
new_target_connection=copied_target_connection.id,
|
|
229
|
+
new_queue_id=transfer_data.new_queue_id,
|
|
230
|
+
new_name=transfer_data.new_name,
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
if transfer_data.remove_source:
|
|
234
|
+
await unit_of_work.transfer.delete(transfer_id=transfer_id)
|
|
235
|
+
|
|
236
|
+
return StatusCopyTransferResponseSchema(
|
|
237
|
+
ok=True,
|
|
238
|
+
status_code=status.HTTP_200_OK,
|
|
239
|
+
message="Transfer was copied.",
|
|
240
|
+
source_connection_id=copied_source_connection.id,
|
|
241
|
+
target_connection_id=copied_target_connection.id,
|
|
242
|
+
copied_transfer_id=copied_transfer.id,
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
@router.patch("/transfers/{transfer_id}")
|
|
247
|
+
async def update_transfer(
|
|
248
|
+
transfer_id: int,
|
|
249
|
+
transfer_data: UpdateTransferSchema,
|
|
250
|
+
current_user: User = Depends(get_user(is_active=True)),
|
|
251
|
+
unit_of_work: UnitOfWork = Depends(UnitOfWorkMarker),
|
|
252
|
+
) -> ReadTransferSchema:
|
|
253
|
+
# Check: user can update transfer
|
|
254
|
+
resource_role = await unit_of_work.transfer.get_resource_permission(
|
|
255
|
+
user=current_user,
|
|
256
|
+
resource_id=transfer_id,
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
if resource_role == Permission.NONE:
|
|
260
|
+
raise TransferNotFoundError
|
|
261
|
+
|
|
262
|
+
if resource_role < Permission.WRITE:
|
|
263
|
+
raise ActionNotAllowedError
|
|
264
|
+
|
|
265
|
+
transfer = await unit_of_work.transfer.read_by_id(
|
|
266
|
+
transfer_id=transfer_id,
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
target_connection = await unit_of_work.connection.read_by_id(
|
|
270
|
+
connection_id=transfer_data.target_connection_id or transfer.target_connection_id,
|
|
271
|
+
)
|
|
272
|
+
source_connection = await unit_of_work.connection.read_by_id(
|
|
273
|
+
connection_id=transfer_data.source_connection_id or transfer.source_connection_id,
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
queue = await unit_of_work.queue.read_by_id(
|
|
277
|
+
transfer_data.new_queue_id or transfer.queue_id,
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
# Check: user can read new connections
|
|
281
|
+
target_connection_resource_role = await unit_of_work.connection.get_resource_permission(
|
|
282
|
+
user=current_user,
|
|
283
|
+
resource_id=target_connection.id,
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
source_connection_resource_role = await unit_of_work.connection.get_resource_permission(
|
|
287
|
+
user=current_user,
|
|
288
|
+
resource_id=source_connection.id,
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
if source_connection_resource_role == Permission.NONE or target_connection_resource_role == Permission.NONE:
|
|
292
|
+
raise ConnectionNotFoundError
|
|
293
|
+
|
|
294
|
+
# Check: connections and transfer group
|
|
295
|
+
if (
|
|
296
|
+
target_connection.group_id != source_connection.group_id
|
|
297
|
+
or target_connection.group_id != transfer.group_id
|
|
298
|
+
or source_connection.group_id != transfer.group_id
|
|
299
|
+
):
|
|
300
|
+
raise DifferentTransferAndConnectionsGroupsError
|
|
301
|
+
|
|
302
|
+
if queue.group_id != transfer.group_id:
|
|
303
|
+
raise DifferentTransferAndQueueGroupError
|
|
304
|
+
|
|
305
|
+
if transfer_data.target_params and target_connection.data["type"] != transfer_data.target_params.type:
|
|
306
|
+
raise DifferentTypeConnectionsAndParamsError(
|
|
307
|
+
connection_type=target_connection.data["type"],
|
|
308
|
+
conn="target",
|
|
309
|
+
params_type=transfer_data.target_params.type,
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
if transfer_data.source_params and source_connection.data["type"] != transfer_data.source_params.type:
|
|
313
|
+
raise DifferentTypeConnectionsAndParamsError(
|
|
314
|
+
connection_type=source_connection.data["type"],
|
|
315
|
+
conn="source",
|
|
316
|
+
params_type=transfer_data.source_params.type,
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
transfer_data = process_file_transfer_directory_path(transfer_data) # type: ignore
|
|
320
|
+
|
|
321
|
+
async with unit_of_work:
|
|
322
|
+
transfer = await unit_of_work.transfer.update(
|
|
323
|
+
transfer=transfer,
|
|
324
|
+
name=transfer_data.name,
|
|
325
|
+
description=transfer_data.description,
|
|
326
|
+
target_connection_id=transfer_data.target_connection_id,
|
|
327
|
+
source_connection_id=transfer_data.source_connection_id,
|
|
328
|
+
source_params=transfer_data.source_params.dict() if transfer_data.source_params else {},
|
|
329
|
+
target_params=transfer_data.target_params.dict() if transfer_data.target_params else {},
|
|
330
|
+
strategy_params=transfer_data.strategy_params.dict() if transfer_data.strategy_params else {},
|
|
331
|
+
is_scheduled=transfer_data.is_scheduled,
|
|
332
|
+
schedule=transfer_data.schedule,
|
|
333
|
+
new_queue_id=transfer_data.new_queue_id,
|
|
334
|
+
)
|
|
335
|
+
return ReadTransferSchema.from_orm(transfer)
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
@router.delete("/transfers/{transfer_id}")
|
|
339
|
+
async def delete_transfer(
|
|
340
|
+
transfer_id: int,
|
|
341
|
+
current_user: User = Depends(get_user(is_active=True)),
|
|
342
|
+
unit_of_work: UnitOfWork = Depends(UnitOfWorkMarker),
|
|
343
|
+
) -> StatusResponseSchema:
|
|
344
|
+
resource_role = await unit_of_work.transfer.get_resource_permission(
|
|
345
|
+
user=current_user,
|
|
346
|
+
resource_id=transfer_id,
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
if resource_role == Permission.NONE:
|
|
350
|
+
raise TransferNotFoundError
|
|
351
|
+
|
|
352
|
+
if resource_role < Permission.DELETE:
|
|
353
|
+
raise ActionNotAllowedError
|
|
354
|
+
|
|
355
|
+
async with unit_of_work:
|
|
356
|
+
await unit_of_work.transfer.delete(
|
|
357
|
+
transfer_id=transfer_id,
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
return StatusResponseSchema(
|
|
361
|
+
ok=True,
|
|
362
|
+
status_code=status.HTTP_200_OK,
|
|
363
|
+
message="Transfer was deleted",
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
@router.get("/runs")
|
|
368
|
+
async def read_runs(
|
|
369
|
+
transfer_id: int,
|
|
370
|
+
page: int = Query(gt=0, default=1),
|
|
371
|
+
page_size: int = Query(gt=0, le=200, default=20),
|
|
372
|
+
current_user: User = Depends(get_user(is_active=True)),
|
|
373
|
+
unit_of_work: UnitOfWork = Depends(UnitOfWorkMarker),
|
|
374
|
+
) -> RunPageSchema:
|
|
375
|
+
"""Return runs of transfer with pagination"""
|
|
376
|
+
resource_rule = await unit_of_work.transfer.get_resource_permission(
|
|
377
|
+
user=current_user,
|
|
378
|
+
resource_id=transfer_id,
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
if resource_rule == Permission.NONE:
|
|
382
|
+
raise TransferNotFoundError
|
|
383
|
+
|
|
384
|
+
pagination = await unit_of_work.run.paginate(
|
|
385
|
+
transfer_id=transfer_id,
|
|
386
|
+
page=page,
|
|
387
|
+
page_size=page_size,
|
|
388
|
+
)
|
|
389
|
+
|
|
390
|
+
return RunPageSchema.from_pagination(pagination=pagination)
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
@router.get("/runs/{run_id}")
|
|
394
|
+
async def read_run(
|
|
395
|
+
run_id: int,
|
|
396
|
+
current_user: User = Depends(get_user(is_active=True)),
|
|
397
|
+
unit_of_work: UnitOfWork = Depends(UnitOfWorkMarker),
|
|
398
|
+
) -> ReadRunSchema:
|
|
399
|
+
run = await unit_of_work.run.read_by_id(run_id=run_id)
|
|
400
|
+
|
|
401
|
+
resource_role = await unit_of_work.transfer.get_resource_permission(
|
|
402
|
+
user=current_user,
|
|
403
|
+
resource_id=run.transfer_id,
|
|
404
|
+
)
|
|
405
|
+
|
|
406
|
+
if resource_role == Permission.NONE:
|
|
407
|
+
raise TransferNotFoundError
|
|
408
|
+
|
|
409
|
+
return ReadRunSchema.from_orm(run)
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
@router.post("/runs")
|
|
413
|
+
async def start_run(
|
|
414
|
+
create_run_data: CreateRunSchema,
|
|
415
|
+
current_user: User = Depends(get_user(is_active=True)),
|
|
416
|
+
unit_of_work: UnitOfWork = Depends(UnitOfWorkMarker),
|
|
417
|
+
) -> ReadRunSchema:
|
|
418
|
+
# Check: user can start transfer
|
|
419
|
+
resource_rule = await unit_of_work.transfer.get_resource_permission(
|
|
420
|
+
user=current_user,
|
|
421
|
+
resource_id=create_run_data.transfer_id,
|
|
422
|
+
)
|
|
423
|
+
|
|
424
|
+
if resource_rule == Permission.NONE:
|
|
425
|
+
raise TransferNotFoundError
|
|
426
|
+
|
|
427
|
+
if resource_rule < Permission.WRITE:
|
|
428
|
+
raise ActionNotAllowedError
|
|
429
|
+
|
|
430
|
+
transfer = await unit_of_work.transfer.read_by_id(transfer_id=create_run_data.transfer_id)
|
|
431
|
+
|
|
432
|
+
async with unit_of_work:
|
|
433
|
+
run = await unit_of_work.run.create(transfer_id=create_run_data.transfer_id)
|
|
434
|
+
try:
|
|
435
|
+
celery.send_task("run_transfer_task", kwargs={"run_id": run.id}, queue=transfer.queue.name)
|
|
436
|
+
except KombuError as e:
|
|
437
|
+
async with unit_of_work:
|
|
438
|
+
run = await unit_of_work.run.update(
|
|
439
|
+
run_id=run.id,
|
|
440
|
+
status=Status.FAILED,
|
|
441
|
+
)
|
|
442
|
+
raise CannotConnectToTaskQueueError(run_id=run.id) from e
|
|
443
|
+
return ReadRunSchema.from_orm(run)
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
@router.post("/runs/{run_id}/stop")
|
|
447
|
+
async def stop_run(
|
|
448
|
+
run_id: int,
|
|
449
|
+
current_user: User = Depends(get_user(is_active=True)),
|
|
450
|
+
unit_of_work: UnitOfWork = Depends(UnitOfWorkMarker),
|
|
451
|
+
) -> ReadRunSchema:
|
|
452
|
+
run = await unit_of_work.run.read_by_id(run_id=run_id)
|
|
453
|
+
|
|
454
|
+
# Check: user can stop transfer
|
|
455
|
+
resource_rule = await unit_of_work.transfer.get_resource_permission(
|
|
456
|
+
user=current_user,
|
|
457
|
+
resource_id=run.transfer_id,
|
|
458
|
+
)
|
|
459
|
+
|
|
460
|
+
if resource_rule == Permission.NONE:
|
|
461
|
+
raise TransferNotFoundError
|
|
462
|
+
|
|
463
|
+
if resource_rule < Permission.WRITE:
|
|
464
|
+
raise ActionNotAllowedError
|
|
465
|
+
|
|
466
|
+
async with unit_of_work:
|
|
467
|
+
run = await unit_of_work.run.stop(run_id=run_id)
|
|
468
|
+
# TODO: add immdiate stop transfer after stop Run
|
|
469
|
+
return ReadRunSchema.from_orm(run)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2023-2024 MTS (Mobile Telesystems)
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
from syncmaster.schemas.v1.transfers import CreateTransferSchema, UpdateTransferSchema
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def process_file_transfer_directory_path(
|
|
7
|
+
transfer_data: UpdateTransferSchema | CreateTransferSchema,
|
|
8
|
+
) -> UpdateTransferSchema | CreateTransferSchema:
|
|
9
|
+
if transfer_data.source_params is not None:
|
|
10
|
+
if hasattr(transfer_data.source_params, "directory_path"): # s3 or hdfs connection
|
|
11
|
+
transfer_data.source_params.directory_path = str(transfer_data.source_params.directory_path)
|
|
12
|
+
|
|
13
|
+
if transfer_data.target_params is not None:
|
|
14
|
+
if hasattr(transfer_data.source_params, "directory_path"): # s3 or hdfs connection
|
|
15
|
+
transfer_data.target_params.directory_path = str(transfer_data.target_params.directory_path) # type: ignore
|
|
16
|
+
|
|
17
|
+
return transfer_data
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2023-2024 MTS (Mobile Telesystems)
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
from fastapi import APIRouter, Depends, Query
|
|
6
|
+
|
|
7
|
+
from syncmaster.backend.api.deps import UnitOfWorkMarker
|
|
8
|
+
from syncmaster.backend.services import UnitOfWork, get_user
|
|
9
|
+
from syncmaster.db.models import User
|
|
10
|
+
from syncmaster.exceptions import ActionNotAllowedError
|
|
11
|
+
from syncmaster.schemas.v1.status import StatusResponseSchema
|
|
12
|
+
from syncmaster.schemas.v1.users import ReadUserSchema, UpdateUserSchema, UserPageSchema
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
router = APIRouter(tags=["Users"])
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@router.get("/users")
|
|
21
|
+
async def get_users(
|
|
22
|
+
page: int = Query(gt=0, default=1),
|
|
23
|
+
page_size: int = Query(gt=0, le=200, default=20),
|
|
24
|
+
current_user: User = Depends(get_user(is_active=True)),
|
|
25
|
+
unit_of_work: UnitOfWork = Depends(UnitOfWorkMarker),
|
|
26
|
+
) -> UserPageSchema:
|
|
27
|
+
pagination = await unit_of_work.user.paginate(
|
|
28
|
+
page=page, page_size=page_size, is_superuser=current_user.is_superuser
|
|
29
|
+
)
|
|
30
|
+
return UserPageSchema.from_pagination(pagination)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@router.get("/users/{user_id}", dependencies=[Depends(get_user(is_active=True))])
|
|
34
|
+
async def read_user(
|
|
35
|
+
user_id: int,
|
|
36
|
+
unit_of_work: UnitOfWork = Depends(UnitOfWorkMarker),
|
|
37
|
+
) -> ReadUserSchema:
|
|
38
|
+
user = await unit_of_work.user.read_by_id(user_id=user_id, is_active=True)
|
|
39
|
+
return ReadUserSchema.from_orm(user)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@router.patch("/users/{user_id}")
|
|
43
|
+
async def update_user(
|
|
44
|
+
user_id: int,
|
|
45
|
+
user_data: UpdateUserSchema,
|
|
46
|
+
current_user: User = Depends(get_user(is_active=True)),
|
|
47
|
+
unit_of_work: UnitOfWork = Depends(UnitOfWorkMarker),
|
|
48
|
+
) -> ReadUserSchema:
|
|
49
|
+
if user_id != current_user.id and not current_user.is_superuser:
|
|
50
|
+
raise ActionNotAllowedError
|
|
51
|
+
async with unit_of_work:
|
|
52
|
+
change_user = await unit_of_work.user.update(user_id=user_id, data=user_data.dict())
|
|
53
|
+
return ReadUserSchema.from_orm(change_user)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@router.post("/users/{user_id}/activate", dependencies=[Depends(get_user(is_superuser=True))])
|
|
57
|
+
async def activate_user(
|
|
58
|
+
user_id: int,
|
|
59
|
+
unit_of_work: UnitOfWork = Depends(UnitOfWorkMarker),
|
|
60
|
+
) -> StatusResponseSchema:
|
|
61
|
+
async with unit_of_work:
|
|
62
|
+
user = await unit_of_work.user.update(user_id=user_id, data={"is_active": True})
|
|
63
|
+
logger.info("User %s active=%s id=%d", user, user.is_active, user.id)
|
|
64
|
+
return StatusResponseSchema(ok=True, status_code=200, message="User was activated")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@router.post("/users/{user_id}/deactivate", dependencies=[Depends(get_user(is_superuser=True))])
|
|
68
|
+
async def deactivate_user(
|
|
69
|
+
user_id: int,
|
|
70
|
+
unit_of_work: UnitOfWork = Depends(UnitOfWorkMarker),
|
|
71
|
+
):
|
|
72
|
+
async with unit_of_work:
|
|
73
|
+
user = await unit_of_work.user.update(user_id=user_id, data={"is_active": False})
|
|
74
|
+
logger.info("User %s active=%s id=%d", user, user.is_active, user.id)
|
|
75
|
+
return StatusResponseSchema(ok=True, status_code=200, message="User was deactivated")
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/bin/env python3
|
|
2
|
+
# SPDX-FileCopyrightText: 2023-2024 MTS (Mobile Telesystems)
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
from fastapi import FastAPI
|
|
9
|
+
|
|
10
|
+
from syncmaster.backend.main import get_application
|
|
11
|
+
from syncmaster.config import Settings
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def get_openapi_schema(app: FastAPI) -> dict:
|
|
15
|
+
return app.openapi()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
if __name__ == "__main__":
|
|
19
|
+
settings = Settings()
|
|
20
|
+
app = get_application(settings)
|
|
21
|
+
schema = get_openapi_schema(app)
|
|
22
|
+
file_path = sys.argv[1]
|
|
23
|
+
if not file_path:
|
|
24
|
+
raise ValueError("File path not sent")
|
|
25
|
+
with open(file_path, "w") as file:
|
|
26
|
+
json.dump(schema, file)
|