Skip to content
This repository was archived by the owner on Aug 4, 2021. It is now read-only.

Commit 2fd631c

Browse files
authored
Merge pull request #21 from CSCfi/feature/chunked-segments
Feature/chunked segments
2 parents b7bd4ed + 9e06178 commit 2fd631c

File tree

13 files changed

+387
-662
lines changed

13 files changed

+387
-662
lines changed

.github/workflows/style.yml

+3-1
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,6 @@ jobs:
2727
- name: Test mypy typing with tox
2828
run: tox -e mypy
2929
- name: bandit static check
30-
run: tox -e bandit
30+
run: tox -e bandit
31+
- name: black style check
32+
run : tox -e black

requirements.txt

+2
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,5 @@ python-swiftclient==3.11.1
44
keystoneauth1==4.3.1
55
git+https://github.com/cscfi/swift-browser-ui.git
66
certifi==2020.12.05
7+
gunicorn>=20.0.1
8+
uvloop==0.15.2

setup.py

+32-27
Original file line numberDiff line numberDiff line change
@@ -5,48 +5,53 @@
55
setuptools.setup(
66
name=__name__,
77
version=__version__,
8-
description='Swift upload / download runner for swift-browser-ui',
8+
description="Swift upload / download runner for swift-browser-ui",
99
author=__author__,
1010
author_email="[email protected]",
1111
project_urls={
12-
'Source': 'https://github.com/CSCfi/swift-upload-runner',
12+
"Source": "https://github.com/CSCfi/swift-upload-runner",
1313
},
14-
license='MIT',
14+
license="MIT",
1515
install_requires=[
16-
'aiohttp',
17-
'python-swiftclient',
18-
'keystoneauth1',
19-
'gunicorn',
20-
'certifi',
21-
'uvloop',
22-
"swift-browser-ui"
23-
"@ git+https://github.com/cscfi/swift-browser-ui.git"
16+
"aiohttp==3.7.4.post0",
17+
"requests==2.25.1",
18+
"python-swiftclient==3.11.1",
19+
"keystoneauth1==4.3.1",
20+
"gunicorn>=20.0.1",
21+
"uvloop==0.15.2",
22+
"certifi==2020.12.05",
23+
"swift-browser-ui" "@ git+https://github.com/cscfi/swift-browser-ui.git",
2424
],
2525
extras_require={
26-
'test': ['tox', 'pytest', 'pytest-cov', 'coverage',
27-
'tox', 'flake8', 'flake8-docstrings', 'pytest-aiohttp',
28-
'asynctest'],
26+
"test": [
27+
"tox==3.23.1",
28+
"pytest==6.2.4",
29+
"pytest-cov==2.12.1",
30+
"coverage==5.5",
31+
"flake8==3.9.2",
32+
"flake8-docstrings==1.6.0",
33+
"asynctest==0.13.0",
34+
"black==21.6b0",
35+
"pytest-aiohttp==0.3.0",
36+
],
2937
},
3038
packages=[__name__],
3139
include_package_data=True,
32-
platforms='any',
40+
platforms="any",
3341
entry_points={
34-
'console_scripts': [
35-
'swift-upload-runner=swift_upload_runner.server:main',
42+
"console_scripts": [
43+
"swift-upload-runner=swift_upload_runner.server:main",
3644
]
3745
},
3846
classifiers=[
39-
'Development Status :: 4 - Beta',
40-
47+
"Development Status :: 4 - Beta",
4148
# Indicate who your project is intended for
42-
'Intended Audience :: Developers',
43-
'Intended Audience :: Information Technology',
44-
'Topic :: Internet :: WWW/HTTP :: HTTP Servers',
45-
49+
"Intended Audience :: Developers",
50+
"Intended Audience :: Information Technology",
51+
"Topic :: Internet :: WWW/HTTP :: HTTP Servers",
4652
# Pick your license as you wish
47-
'License :: OSI Approved :: MIT License',
48-
49-
'Programming Language :: Python :: 3.7',
50-
'Programming Language :: Python :: 3.8',
53+
"License :: OSI Approved :: MIT License",
54+
"Programming Language :: Python :: 3.7",
55+
"Programming Language :: Python :: 3.8",
5156
],
5257
)

swift_upload_runner/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""Runner for swift-browser-ui upload and replication operations."""
22

33
__name__ = "swift_upload_runner"
4-
__version__ = "0.1.8"
4+
__version__ = "0.2.0"
55
__author__ = "CSC Developers"
66
__license__ = "MIT License"

swift_upload_runner/api.py

+21-57
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,7 @@
1010
from .replicate import ObjectReplicationProxy
1111

1212

13-
async def handle_get_object(
14-
request: aiohttp.web.Request
15-
) -> aiohttp.web.StreamResponse:
13+
async def handle_get_object(request: aiohttp.web.Request) -> aiohttp.web.StreamResponse:
1614
"""Handle a request for getting object content."""
1715
auth = get_auth_instance(request)
1816

@@ -21,7 +19,7 @@ async def handle_get_object(
2119
await download.a_begin_download(
2220
request.match_info["project"],
2321
request.match_info["container"],
24-
request.match_info["object_name"]
22+
request.match_info["object_name"],
2523
)
2624

2725
resp = aiohttp.web.StreamResponse()
@@ -39,7 +37,7 @@ async def handle_get_object(
3937

4038

4139
async def handle_replicate_container(
42-
request: aiohttp.web.Request
40+
request: aiohttp.web.Request,
4341
) -> aiohttp.web.Response:
4442
"""Handle request to replicating a container from a source."""
4543
auth = get_auth_instance(request)
@@ -51,22 +49,15 @@ async def handle_replicate_container(
5149
source_container = request.query["from_container"]
5250

5351
replicator = ObjectReplicationProxy(
54-
auth,
55-
request.app["client"],
56-
project,
57-
container,
58-
source_project,
59-
source_container
52+
auth, request.app["client"], project, container, source_project, source_container
6053
)
6154

6255
asyncio.ensure_future(replicator.a_copy_from_container())
6356

6457
return aiohttp.web.Response(status=202)
6558

6659

67-
async def handle_replicate_object(
68-
request: aiohttp.web.Request
69-
) -> aiohttp.web.Response:
60+
async def handle_replicate_object(request: aiohttp.web.Request) -> aiohttp.web.Response:
7061
"""Handle a request to replicating an object from a source."""
7162
auth = get_auth_instance(request)
7263

@@ -78,22 +69,15 @@ async def handle_replicate_object(
7869
source_object = request.query["from_object"]
7970

8071
replicator = ObjectReplicationProxy(
81-
auth,
82-
request.app["client"],
83-
project,
84-
container,
85-
source_project,
86-
source_container
72+
auth, request.app["client"], project, container, source_project, source_container
8773
)
8874

8975
asyncio.ensure_future(replicator.a_copy_object(source_object))
9076

9177
return aiohttp.web.Response(status=202)
9278

9379

94-
async def handle_post_object_chunk(
95-
request: aiohttp.web.Request
96-
) -> aiohttp.web.Response:
80+
async def handle_post_object_chunk(request: aiohttp.web.Request) -> aiohttp.web.Response:
9781
"""Handle a request for posting an object chunk."""
9882
if "from_object" in request.query.keys():
9983
return await handle_replicate_object(request)
@@ -105,22 +89,12 @@ async def handle_post_object_chunk(
10589

10690
query, data = await parse_multipart_in(request)
10791

108-
upload_session = await get_upload_instance(
109-
request,
110-
project,
111-
container,
112-
p_query=query
113-
)
92+
upload_session = await get_upload_instance(request, project, container, p_query=query)
11493

115-
return await upload_session.a_add_chunk(
116-
query,
117-
data
118-
)
94+
return await upload_session.a_add_chunk(query, data)
11995

12096

121-
async def handle_get_object_chunk(
122-
request: aiohttp.web.Request
123-
) -> aiohttp.web.Response:
97+
async def handle_get_object_chunk(request: aiohttp.web.Request) -> aiohttp.web.Response:
12498
"""Handle a request for checking if a chunk exists."""
12599
get_auth_instance(request)
126100

@@ -134,19 +108,13 @@ async def handle_get_object_chunk(
134108
except KeyError:
135109
raise aiohttp.web.HTTPBadRequest(reason="Malformed query string")
136110

137-
upload_session = await get_upload_instance(
138-
request,
139-
project,
140-
container
141-
)
111+
upload_session = await get_upload_instance(request, project, container)
142112

143-
return await upload_session.a_check_segment(
144-
chunk_number
145-
)
113+
return await upload_session.a_check_segment(chunk_number)
146114

147115

148116
async def handle_post_object_options(
149-
request: aiohttp.web.Request
117+
request: aiohttp.web.Request,
150118
) -> aiohttp.web.Response:
151119
"""Handle options request for posting the object chunk."""
152120
resp = aiohttp.web.Response(
@@ -160,7 +128,7 @@ async def handle_post_object_options(
160128

161129

162130
async def handle_get_container(
163-
request: aiohttp.web.Request
131+
request: aiohttp.web.Request,
164132
) -> aiohttp.web.StreamResponse:
165133
"""Handle a request for getting container contents as an archive."""
166134
if "resumableChunkNumber" in request.query.keys():
@@ -183,11 +151,7 @@ async def handle_get_container(
183151

184152
await resp.prepare(request)
185153

186-
download = ContainerArchiveDownloadProxy(
187-
auth,
188-
project,
189-
container
190-
)
154+
download = ContainerArchiveDownloadProxy(auth, project, container)
191155

192156
await download.a_begin_container_download()
193157

@@ -197,13 +161,13 @@ async def handle_get_container(
197161
return resp
198162

199163

200-
async def handle_health_check(
201-
request: aiohttp.web.Request
202-
) -> aiohttp.web.Response:
164+
async def handle_health_check(request: aiohttp.web.Request) -> aiohttp.web.Response:
203165
"""Answer a service health check."""
204166
# Case degraded
205167

206168
# Case nominal
207-
return aiohttp.web.json_response({
208-
"status": "Ok",
209-
})
169+
return aiohttp.web.json_response(
170+
{
171+
"status": "Ok",
172+
}
173+
)

swift_upload_runner/auth.py

+18-51
Original file line numberDiff line numberDiff line change
@@ -10,23 +10,15 @@
1010

1111
import aiohttp.web
1212

13-
from swift_browser_ui._convenience import (
14-
initiate_os_session
15-
)
13+
from swift_browser_ui._convenience import initiate_os_session
1614

1715
AiohttpHandler = typing.Callable[
1816
[aiohttp.web.Request],
19-
typing.Coroutine[
20-
typing.Awaitable,
21-
typing.Any,
22-
aiohttp.web.Response
23-
]
17+
typing.Coroutine[typing.Awaitable, typing.Any, aiohttp.web.Response],
2418
]
2519

2620

27-
async def handle_login(
28-
request: aiohttp.web.Request
29-
) -> aiohttp.web.StreamResponse:
21+
async def handle_login(request: aiohttp.web.Request) -> aiohttp.web.StreamResponse:
3022
"""Begin a new session for the upload process."""
3123
session_key = hashlib.sha1(os.urandom(128)).hexdigest() # nosec
3224

@@ -36,67 +28,47 @@ async def handle_login(
3628
token = login_form["token"]
3729
request.app[session_key] = {}
3830
request.app[session_key]["uploads"] = {}
39-
request.app[session_key]["auth"] = initiate_os_session(
40-
token,
41-
project
42-
)
31+
request.app[session_key]["auth"] = initiate_os_session(token, project)
4332

44-
resp = aiohttp.web.Response(
45-
status=200,
46-
body='OK'
47-
)
33+
resp = aiohttp.web.Response(status=200, body="OK")
4834
resp.cookies["RUNNER_SESSION_ID"] = session_key
4935

5036
return resp
5137

5238
except KeyError:
53-
raise aiohttp.web.HTTPUnauthorized(
54-
reason="Login token or project missing"
55-
)
39+
raise aiohttp.web.HTTPUnauthorized(reason="Login token or project missing")
5640

5741

58-
async def read_in_keys(
59-
app: aiohttp.web.Application
60-
) -> None:
42+
async def read_in_keys(app: aiohttp.web.Application) -> None:
6143
"""Read in keys to the application."""
6244
keys = os.environ.get("SWIFT_UI_API_AUTH_TOKENS", None)
6345
app["tokens"] = keys.split(",") if keys is not None else []
6446
if app["tokens"]:
65-
app["tokens"] = [
66-
token.encode("utf-8") for token in app["tokens"]
67-
]
47+
app["tokens"] = [token.encode("utf-8") for token in app["tokens"]]
6848

6949

7050
async def test_signature(
71-
tokens: typing.List[bytes],
72-
signature: str,
73-
message: str,
74-
validity: str,
51+
tokens: typing.List[bytes],
52+
signature: str,
53+
message: str,
54+
validity: str,
7555
) -> bool:
7656
"""Validate signature against the given tokens."""
7757
# Check signature expiration
7858
if int(validity) < time.time():
79-
raise aiohttp.web.HTTPUnauthorized(
80-
reason="Signature expired"
81-
)
59+
raise aiohttp.web.HTTPUnauthorized(reason="Signature expired")
8260
byte_message = message.encode("utf-8")
8361
for token in tokens:
84-
digest = hmac.new(
85-
token,
86-
byte_message,
87-
digestmod="sha256"
88-
).hexdigest()
62+
digest = hmac.new(token, byte_message, digestmod="sha256").hexdigest()
8963
if secrets.compare_digest(digest, signature):
9064
return True
91-
raise aiohttp.web.HTTPUnauthorized(
92-
reason="Missing valid query signature"
93-
)
65+
raise aiohttp.web.HTTPUnauthorized(reason="Missing valid query signature")
9466

9567

9668
@aiohttp.web.middleware
9769
async def handle_validate_authentication(
98-
request: aiohttp.web.Request,
99-
handler: AiohttpHandler,
70+
request: aiohttp.web.Request,
71+
handler: AiohttpHandler,
10072
) -> aiohttp.web.Response:
10173
"""Handle the authentication of a response as a middleware function."""
10274
try:
@@ -108,11 +80,6 @@ async def handle_validate_authentication(
10880
reason="Query string missing validity or signature"
10981
)
11082

111-
await test_signature(
112-
request.app["tokens"],
113-
signature,
114-
validity + path,
115-
validity
116-
)
83+
await test_signature(request.app["tokens"], signature, validity + path, validity)
11784

11885
return await handler(request)

0 commit comments

Comments
 (0)