Skip to content

Commit e0dc06d

Browse files
authored
pbench-results-push: logging fixes and better reporting (#3348)
* Do not use the file logger at all in python commands. The logger only uses the default Streamhandler now which outputs to stderr. * - copy_result_tb: return the response to the PUT request copy_result_tb returns the response from the server. The callers are responsible for interpreting it. However, it can still raise an exception (e.g. on connection error). - push.py: construct a reasonable message out of the response and check if the HTTP status is 201: we exit with 0 if so. Otherwise, if the status is OK (i.e. < 400), then we exit with 0 but print a message with a reasonably self-explanatory status on stderr. We expect that we will only ever get two OK responses: a 201 which indicates a successful PUT with a newly created dataset and 200 which indicates a duplicate dataset. In all non-OK cases, we output the message on stderr and exit with 1. - move.py: check the response - if not OK (>= 400) raise exception. * Fix the monkeypatching in test_copy_result_tb.py Monkeypatching pathlib components (.exists() and .open()) causes pytest to blow up. Limit the scope by using `monkeypatch.context()' so that it is only applied to the objects under test (and not e.g. to what is used in checking assertions). * Use symbolic constant instead of explicit 201 * Parametrize the "normal" test This is just the first step - see issue #3374 for some more work along these lines.
1 parent 0745bb5 commit e0dc06d

File tree

8 files changed

+221
-113
lines changed

8 files changed

+221
-113
lines changed

agent/config/pbench-agent-default.cfg

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,6 @@ pbench_log = %(pbench_run)s/pbench.log
99
# RPM requirement mode: strict vs relaxed
1010
rpm_requirement_mode = strict
1111

12-
[logging]
13-
logger_type = file
14-
# # "log_dir" is only considered when "logger_type" is set to "file"; And by
15-
# # default the log file directory is the "pbench_run" directory.
16-
# log_dir =
17-
1812
[results]
1913
user = pbench
2014
host_info_uri = pbench-results-host-info.versioned/pbench-results-host-info.URL002

lib/pbench/agent/__init__.py

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -61,13 +61,6 @@ def __init__(self, cfg_name):
6161
)
6262
self.pbench_lib_dir = self.pbench_install_dir / "lib"
6363

64-
if self.logger_type == "file" and self.log_dir is None:
65-
# The configuration file has a logging section configured to use
66-
# "file" logging, but no log directory is set. We'll set the log
67-
# directory to be the directory of the legacy ${pbench_log} value
68-
# determined above.
69-
self.log_dir = str(self.pbench_log.parent)
70-
7164
try:
7265
self.ssh_opts = self.get("results", "ssh_opts", fallback=DEFAULT_SSH_OPTS)
7366
except (NoOptionError, NoSectionError):

lib/pbench/agent/base.py

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,6 @@ def __init__(self, context):
5454
)
5555
click.get_current_context().exit(1)
5656

57-
# log file - N.B. not a directory
58-
self.pbench_log = self.config.pbench_log
59-
if self.pbench_log is None:
60-
self.pbench_log = self.pbench_run / "pbench.log"
61-
6257
self.pbench_install_dir = self.config.pbench_install_dir
6358
if self.pbench_install_dir is None:
6459
self.pbench_install_dir = "/opt/pbench-agent"
@@ -71,7 +66,7 @@ def __init__(self, context):
7166
self.pbench_bspp_dir = self.pbench_install_dir / "bench-scripts" / "postprocess"
7267
self.pbench_lib_dir = self.pbench_install_dir / "lib"
7368

74-
self.logger = setup_logging(debug=False, logfile=self.pbench_log)
69+
self.logger = setup_logging(debug=False, logfile=None)
7570

7671
self.ssh_opts = os.environ.get("ssh_opts", self.config.ssh_opts)
7772
self.scp_opts = os.environ.get("scp_opts", self.config.scp_opts)

lib/pbench/agent/results.py

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -313,7 +313,7 @@ def __init__(
313313

314314
def copy_result_tb(
315315
self, token: str, access: Optional[str] = None, metadata: Optional[List] = None
316-
) -> None:
316+
) -> requests.Response:
317317
"""Copies the tar ball from the agent to the configured server using upload
318318
API.
319319
@@ -327,6 +327,9 @@ def copy_result_tb(
327327
metadata: list of metadata keys to be sent on PUT. (Optional)
328328
Format: key:value
329329
330+
Returns:
331+
response from the PUT request
332+
330333
Raises:
331334
RuntimeError if a connection to the server fails
332335
FileUploadError if the tar ball failed to upload properly
@@ -367,17 +370,12 @@ def copy_result_tb(
367370
)
368371
)
369372

370-
response = requests.Session().send(request)
371-
response.raise_for_status()
372-
self.logger.info("File uploaded successfully")
373+
return requests.Session().send(request)
373374
except requests.exceptions.ConnectionError as exc:
374375
raise RuntimeError(f"Cannot connect to '{self.upload_url}': {exc}")
375376
except Exception as exc:
376377
raise self.FileUploadError(
377-
"There was something wrong with file upload request: "
378+
"There was something wrong with the file upload request: "
378379
f"file: '{self.tarball}', URL: '{self.upload_url}';"
379380
f" error: '{exc}'"
380381
)
381-
assert (
382-
response.ok
383-
), f"Logic error! Unexpected error response, '{response.reason}' ({response.status_code})"

lib/pbench/cli/agent/commands/results/move.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from typing import List
66

77
import click
8+
import requests
89

910
from pbench.agent.base import BaseCommand
1011
from pbench.agent.results import CopyResultTb, MakeResultTb
@@ -104,9 +105,15 @@ def execute(self, single_threaded: bool, delete: bool = True) -> int:
104105
self.config,
105106
self.logger,
106107
)
107-
crt.copy_result_tb(
108+
res = crt.copy_result_tb(
108109
self.context.token, self.context.access, self.context.metadata
109110
)
111+
if not res.ok:
112+
try:
113+
msg = res.json()["message"]
114+
except requests.exceptions.JSONDecodeError:
115+
msg = res.text if res.text else res.reason
116+
raise CopyResultTb.FileUploadError(msg)
110117
except Exception as exc:
111118
if isinstance(exc, (CopyResultTb.FileUploadError, RuntimeError)):
112119
msg = "Error uploading file"

lib/pbench/cli/agent/commands/results/push.py

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
from http import HTTPStatus
12
from pathlib import Path
23
from typing import List
34

45
import click
6+
import requests
57

68
from pbench.agent.base import BaseCommand
79
from pbench.agent.results import CopyResultTb
@@ -24,10 +26,29 @@ def execute(self) -> int:
2426
self.config,
2527
self.logger,
2628
)
27-
crt.copy_result_tb(
29+
res = crt.copy_result_tb(
2830
self.context.token, self.context.access, self.context.metadata
2931
)
30-
return 0
32+
33+
# success
34+
if res.status_code == HTTPStatus.CREATED:
35+
return 0
36+
37+
try:
38+
msg = res.json()["message"]
39+
except requests.exceptions.JSONDecodeError:
40+
msg = res.text if res.text else res.reason
41+
42+
# dup or other unexpected but non-error status
43+
if res.ok:
44+
click.echo(msg, err=True)
45+
return 0
46+
47+
click.echo(
48+
f"HTTP Error status: {res.status_code}, message: {msg}",
49+
err=True,
50+
)
51+
return 1
3152

3253

3354
@sort_click_command_parameters

lib/pbench/test/unit/agent/task/test_copy_result_tb.py

Lines changed: 80 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -42,18 +42,20 @@ def test_tarball_nonexistent(self, monkeypatch, agent_logger):
4242
bad_tarball_name = "nonexistent-tarball.tar.xz"
4343
expected_error_message = f"Tar ball '{bad_tarball_name}' does not exist"
4444

45-
monkeypatch.setattr(
46-
Path, "exists", self.get_path_exists_mock(bad_tarball_name, False)
47-
)
48-
49-
with pytest.raises(FileNotFoundError) as excinfo:
50-
CopyResultTb(
51-
bad_tarball_name,
52-
0,
53-
"ignoremd5",
54-
self.config,
55-
agent_logger,
45+
with monkeypatch.context() as m:
46+
m.setattr(
47+
Path, "exists", self.get_path_exists_mock(bad_tarball_name, False)
5648
)
49+
50+
with pytest.raises(FileNotFoundError) as excinfo:
51+
CopyResultTb(
52+
bad_tarball_name,
53+
0,
54+
"ignoremd5",
55+
self.config,
56+
agent_logger,
57+
)
58+
5759
assert str(excinfo.value).endswith(
5860
expected_error_message
5961
), f"expected='...{expected_error_message}', found='{str(excinfo.value)}'"
@@ -70,30 +72,33 @@ def request_callback(request: requests.Request):
7072
else:
7173
assert "access" in request.params
7274
assert request.params["access"] == access
73-
return HTTPStatus.OK, {}, ""
75+
return HTTPStatus.CREATED, {}, ""
7476

7577
responses.add_callback(
7678
responses.PUT,
7779
f"{self.config.get('results', 'server_rest_url')}/upload/{tb_name}",
7880
callback=request_callback,
7981
)
8082

81-
monkeypatch.setattr(Path, "exists", self.get_path_exists_mock(tb_name, True))
82-
monkeypatch.setattr(
83-
Path, "open", self.get_path_open_mock(tb_name, io.StringIO(tb_contents))
84-
)
83+
with monkeypatch.context() as m:
84+
m.setattr(Path, "exists", self.get_path_exists_mock(tb_name, True))
85+
m.setattr(
86+
Path, "open", self.get_path_open_mock(tb_name, io.StringIO(tb_contents))
87+
)
8588

86-
crt = CopyResultTb(
87-
tb_name,
88-
len(tb_contents),
89-
"someMD5",
90-
self.config,
91-
agent_logger,
92-
)
93-
if access is None:
94-
crt.copy_result_tb("token")
95-
else:
96-
crt.copy_result_tb("token", access)
89+
crt = CopyResultTb(
90+
tb_name,
91+
len(tb_contents),
92+
"someMD5",
93+
self.config,
94+
agent_logger,
95+
)
96+
if access is None:
97+
res = crt.copy_result_tb("token")
98+
else:
99+
res = crt.copy_result_tb("token", access)
100+
101+
assert res.status_code == HTTPStatus.CREATED
97102
# If we got this far without an exception, then the test passes.
98103

99104
@responses.activate
@@ -111,30 +116,33 @@ def request_callback(request: requests.Request):
111116
assert request.params["metadata"] == metadata
112117
else:
113118
assert "metadata" not in request.params
114-
return HTTPStatus.OK, {}, ""
119+
return HTTPStatus.CREATED, {}, ""
115120

116121
responses.add_callback(
117122
responses.PUT,
118123
f"{self.config.get('results', 'server_rest_url')}/upload/{tb_name}",
119124
callback=request_callback,
120125
)
121126

122-
monkeypatch.setattr(Path, "exists", self.get_path_exists_mock(tb_name, True))
123-
monkeypatch.setattr(
124-
Path, "open", self.get_path_open_mock(tb_name, io.StringIO(tb_contents))
125-
)
127+
with monkeypatch.context() as m:
128+
m.setattr(Path, "exists", self.get_path_exists_mock(tb_name, True))
129+
m.setattr(
130+
Path, "open", self.get_path_open_mock(tb_name, io.StringIO(tb_contents))
131+
)
126132

127-
crt = CopyResultTb(
128-
tb_name,
129-
len(tb_contents),
130-
"someMD5",
131-
self.config,
132-
agent_logger,
133-
)
134-
if metadata is None:
135-
crt.copy_result_tb("token")
136-
else:
137-
crt.copy_result_tb("token", access, metadata)
133+
crt = CopyResultTb(
134+
tb_name,
135+
len(tb_contents),
136+
"someMD5",
137+
self.config,
138+
agent_logger,
139+
)
140+
if metadata is None:
141+
res = crt.copy_result_tb("token")
142+
else:
143+
res = crt.copy_result_tb("token", access, metadata)
144+
145+
assert res.status_code == HTTPStatus.CREATED
138146
# If we got this far without an exception, then the test passes.
139147

140148
@responses.activate
@@ -147,20 +155,21 @@ def test_connection_error(self, monkeypatch, agent_logger):
147155
responses.PUT, upload_url, body=requests.exceptions.ConnectionError("uh-oh")
148156
)
149157

150-
monkeypatch.setattr(Path, "exists", self.get_path_exists_mock(tb_name, True))
151-
monkeypatch.setattr(
152-
Path, "open", self.get_path_open_mock(tb_name, io.StringIO(tb_contents))
153-
)
154-
155-
with pytest.raises(RuntimeError) as excinfo:
156-
crt = CopyResultTb(
157-
tb_name,
158-
len(tb_contents),
159-
"someMD5",
160-
self.config,
161-
agent_logger,
158+
with monkeypatch.context() as m:
159+
m.setattr(Path, "exists", self.get_path_exists_mock(tb_name, True))
160+
m.setattr(
161+
Path, "open", self.get_path_open_mock(tb_name, io.StringIO(tb_contents))
162162
)
163-
crt.copy_result_tb("token")
163+
164+
with pytest.raises(RuntimeError) as excinfo:
165+
crt = CopyResultTb(
166+
tb_name,
167+
len(tb_contents),
168+
"someMD5",
169+
self.config,
170+
agent_logger,
171+
)
172+
crt.copy_result_tb("token")
164173

165174
assert str(excinfo.value).startswith(
166175
expected_error_message
@@ -174,18 +183,20 @@ def test_unexpected_error(self, monkeypatch, agent_logger):
174183

175184
responses.add(responses.PUT, upload_url, body=RuntimeError("uh-oh"))
176185

177-
monkeypatch.setattr(Path, "exists", self.get_path_exists_mock(tb_name, True))
178-
monkeypatch.setattr(
179-
Path, "open", self.get_path_open_mock(tb_name, io.StringIO(tb_contents))
180-
)
181-
182-
with pytest.raises(CopyResultTb.FileUploadError) as excinfo:
183-
crt = CopyResultTb(
184-
tb_name,
185-
len(tb_contents),
186-
"someMD5",
187-
self.config,
188-
agent_logger,
186+
with monkeypatch.context() as m:
187+
m.setattr(Path, "exists", self.get_path_exists_mock(tb_name, True))
188+
m.setattr(
189+
Path, "open", self.get_path_open_mock(tb_name, io.StringIO(tb_contents))
189190
)
190-
crt.copy_result_tb("token")
191+
192+
with pytest.raises(CopyResultTb.FileUploadError) as excinfo:
193+
crt = CopyResultTb(
194+
tb_name,
195+
len(tb_contents),
196+
"someMD5",
197+
self.config,
198+
agent_logger,
199+
)
200+
crt.copy_result_tb("token")
201+
191202
assert "something wrong" in str(excinfo.value)

0 commit comments

Comments
 (0)