Skip to content

Commit c98008e

Browse files
authoredOct 20, 2023
Remove files list from status info; update README.md (#22)
Also, enhance the response from the server status API with additional value fields. PBENCH-1258
1 parent 0d3d1db commit c98008e

File tree

3 files changed

+46
-61
lines changed

3 files changed

+46
-61
lines changed
 

‎README.md

+5-8
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,8 @@ This utility currently offers five methods:
3333
- `GET /<server_id>`: return server status
3434
- `DELETE /<server_id>`: request server shutdown
3535

36-
There are a number of tweaks which are expected to be added:
37-
- The hash algorithm for resource names may be changed or made configurable
38-
- The underlying web server may be changed from the reference one to Gunicorn or other
39-
- The web server should be made able to accept SSL connections
40-
- The utility needs to be packaged, either as a Python package or a container (or both)
41-
- Figure out what the server status response _should_ contain -- currently, it provides
42-
a list of the available files, which undermines the "ya gotta know it's there" story.
43-
36+
There are a number of tweaks which should be considered:
37+
- Change the hash algorithm for resource names or make it configurable
38+
- Change the underlying web server from the reference one to Gunicorn or other
39+
- Make the web server able to accept SSL connections or place it behind a
40+
suitably-configured proxy inside the container.

‎src/relay/relay.py

+13-20
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
import os
77
from pathlib import Path
88
import shutil
9-
import subprocess
109
from typing import Callable
1110

1211
from bottle import Bottle, HTTPResponse, request, static_file
@@ -30,12 +29,16 @@
3029
app = Bottle()
3130

3231

33-
def get_disk_utilization_str(dir_path: Path) -> str:
32+
def get_disk_utilization(dir_path: Path) -> dict[str, int | str]:
3433
usage = shutil.disk_usage(dir_path)
35-
return "{:.3}% full, {} remaining".format(
36-
float(usage.used) / float(usage.total) * 100.0,
37-
humanize.naturalsize(usage.free),
38-
)
34+
return {
35+
"bytes_used": usage.used,
36+
"bytes_remaining": usage.free,
37+
"string": "{:.3}% full, {} remaining".format(
38+
float(usage.used) / float(usage.total) * 100.0,
39+
humanize.naturalsize(usage.free),
40+
),
41+
}
3942

4043

4144
def validate_server_id(func: Callable) -> Callable:
@@ -103,20 +106,10 @@ def relay_status(context: click.Context) -> HTTPResponse:
103106
104107
Returns:
105108
An HTTP response with a status of OK and a JSON payload containing
106-
status information (currently, the output from `ls` listing the files
107-
in the upload directory).
109+
status information.
108110
"""
109111
logging.info("request to report status")
110-
body = {"disk utilization": get_disk_utilization_str(context.meta[CTX_DIRECTORY])}
111-
112-
cp = subprocess.run(
113-
["ls", "-l"], cwd=context.meta[CTX_DIRECTORY], capture_output=True, text=True
114-
)
115-
if cp.returncode:
116-
body["error"] = cp.stderr.strip()
117-
else:
118-
body["files"] = cp.stdout.strip().split("\n")
119-
112+
body = {"disk utilization": get_disk_utilization(context.meta[CTX_DIRECTORY])}
120113
return HTTPResponse(status=HTTPStatus.OK, body=body)
121114

122115

@@ -182,7 +175,7 @@ def receive_file(context: click.Context, file_id: str) -> HTTPResponse:
182175
logging.info(
183176
'request to upload file id "%s", disk %s',
184177
file_id,
185-
get_disk_utilization_str(context.meta[CTX_DIRECTORY]),
178+
get_disk_utilization(context.meta[CTX_DIRECTORY])["string"],
186179
)
187180

188181
if not 0 < request.content_length <= FILE_MAX_SIZE:
@@ -255,7 +248,7 @@ def receive_file(context: click.Context, file_id: str) -> HTTPResponse:
255248
logging.info(
256249
'file id "%s" uploaded successfully, disk %s',
257250
file_id,
258-
get_disk_utilization_str(context.meta[CTX_DIRECTORY]),
251+
get_disk_utilization(context.meta[CTX_DIRECTORY])["string"],
259252
)
260253
else:
261254
logging.info(

‎tests/test_relay.py

+28-33
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@
55
import os
66
from pathlib import Path
77
import shutil
8-
import subprocess
9-
from subprocess import CompletedProcess
108
from typing import Any, Callable, IO, NamedTuple, Optional, Union
119

1210
from _pytest.monkeypatch import MonkeyPatch
@@ -98,7 +96,12 @@ class TestRelay:
9896
FILDIR_SWITCH = "--files-directory"
9997
BDEBUG_SWITCH = "--debug"
10098

101-
DISK_STR = "We've got disk!"
99+
FAKE_DISK_UTL = {
100+
"bytes_used": 1200000,
101+
"bytes_remaining": 80000000000,
102+
"string": "0.00001% full, 80Gb remaining",
103+
}
104+
102105
END_OF_MAIN = AssertionError("Unexpectedly reached the body of main()")
103106
SERVER_ID_TEXT = "ThisIsMyServerServerID"
104107

@@ -170,10 +173,10 @@ def do_setup(
170173
- pathlib.Path.open()
171174
- pathlib.Path.unlink()
172175
- the Path "/" operators
173-
- relay.get_disk_utilization_str()
176+
- relay.get_disk_utilization()
174177
175178
The mock classes seem to be necessary in order to intercept the
176-
respective member functions, possibly because these native
179+
respective member functions, possibly because these are native
177180
implementations instead of "pure Python" (or, maybe I just don't
178181
know what I'm doing).
179182
@@ -331,19 +334,19 @@ def unlink(self, *args, **kwargs):
331334
else:
332335
return self.path.unlink(*args, **kwargs)
333336

334-
def mock_get_disk_utilization_str(dir_path: Path) -> str:
335-
"""Mock for relay.get_disk_utilization_str()
337+
def mock_get_disk_utilization(dir_path: Path) -> dict[str, int | str]:
338+
"""Mock for relay.get_disk_utilization()
336339
337340
Returns a static string.
338341
339342
Note that if the assertion fails, the exception will be caught and
340343
reported as an INTERNAL_SERVER_ERROR. This will likely make the
341-
test fail, but only if it's checking the response....
344+
test fail, but only if it's actually checking the response....
342345
"""
343346
assert str(dir_path) == relay.DEFAULT_FILES_DIRECTORY
344-
return TestRelay.DISK_STR
347+
return TestRelay.FAKE_DISK_UTL
345348

346-
m.setattr(relay, "get_disk_utilization_str", mock_get_disk_utilization_str)
349+
m.setattr(relay, "get_disk_utilization", mock_get_disk_utilization)
347350
m.setattr(relay, "Path", MockPath)
348351
m.setattr(relay, "sha256", lambda *_args, **_kwargs: MockHash())
349352
m.setattr(relay.app, "run", func)
@@ -490,28 +493,13 @@ def test_command_with_server_error(monkeypatch: MonkeyPatch):
490493
TestRelay.check_result(result, exit_code=2, stderr="Error running the server")
491494

492495
@staticmethod
493-
@pytest.mark.parametrize(
494-
"files_str,returncode",
495-
(("We've got files!", 0), ("We've got NO files!", 1)),
496-
)
497-
def test_relay_status_operation(
498-
files_str: str, returncode: int, monkeypatch: MonkeyPatch
499-
):
496+
def test_relay_status_operation(monkeypatch: MonkeyPatch):
500497
"""Test GET /<server_id> method operation"""
501498

502-
def mock_run(args: Union[str, list[str]], *, cwd: str, **_kwargs):
503-
"""Mock for subprocess.run()"""
504-
assert str(cwd) == relay.DEFAULT_FILES_DIRECTORY
505-
key = "stdout" if returncode == 0 else "stderr"
506-
kwargs = {"args": args, "returncode": returncode, key: files_str}
507-
return CompletedProcess(**kwargs)
508-
509499
def validate_relay(response: HTTPResponse):
510500
"""Validate the response from the HTTP method call"""
511501
assert response.status_code == HTTPStatus.OK
512-
assert TestRelay.DISK_STR in response.body["disk utilization"]
513-
key = "files" if returncode == 0 else "error"
514-
assert files_str in response.body[key]
502+
assert response.body["disk utilization"] == TestRelay.FAKE_DISK_UTL
515503

516504
with monkeypatch.context() as m:
517505
mock = mock_app_method_call(
@@ -520,7 +508,6 @@ def validate_relay(response: HTTPResponse):
520508
method_args={"server_id": TestRelay.SERVER_ID_TEXT},
521509
)
522510
TestRelay.do_setup(m, func=mock)
523-
m.setattr(subprocess, "run", mock_run)
524511
result = TestRelay.invoke_main()
525512
TestRelay.check_result(result)
526513

@@ -853,8 +840,8 @@ def validate_receive_file(response: HTTPResponse):
853840
assert request.content_length == bytes_written[0]
854841

855842
@staticmethod
856-
def test_get_disk_utilization_str(monkeypatch: MonkeyPatch):
857-
"""Exercise get_disk_utilization_str()
843+
def test_get_disk_utilization(monkeypatch: MonkeyPatch):
844+
"""Exercise get_disk_utilization()
858845
859846
This is a (nearly) trivial function, but we test it so that the unit
860847
tests show 100% coverage of the CUT.
@@ -867,6 +854,7 @@ class DiskUsageData(NamedTuple):
867854

868855
expected_dir_path = Path("/mockdir")
869856
du = DiskUsageData()
857+
nv = "3.2 GB"
870858

871859
def mock_disk_usage(dir_path: Path) -> DiskUsageData:
872860
"""Mock shutil.disk_usage()"""
@@ -877,13 +865,20 @@ def mock_naturalsize(value: Union[float, str], *args):
877865
"""Mock humanize.naturalsize()"""
878866
assert len(args) == 0
879867
assert str(value) == str(du.free)
880-
return "3.2 GB"
868+
return nv
881869

882870
with monkeypatch.context() as m:
883871
m.setattr(shutil, "disk_usage", mock_disk_usage)
884872
m.setattr(humanize, "naturalsize", mock_naturalsize)
885-
expected = "40.0% full, 3.2 GB remaining"
886-
actual = relay.get_disk_utilization_str(expected_dir_path)
873+
expected = {
874+
"bytes_used": du.used,
875+
"bytes_remaining": du.free,
876+
"string": "{:.3}% full, {} remaining".format(
877+
float(du.used) / float(du.total) * 100.0,
878+
nv,
879+
),
880+
}
881+
actual = relay.get_disk_utilization(expected_dir_path)
887882
assert actual == expected
888883

889884
@staticmethod

0 commit comments

Comments
 (0)
Please sign in to comment.