Skip to content

Commit 7e7f278

Browse files
authored
Run ID is now accessible even after fail and thus can be sent via ema… (#601)
* Run ID is now accessible even after fail and thus can be sent via email; Refactorings for private attributes * Changing access to private * TEst fix * __folder left from merge; pylint fixes
1 parent ad03a9f commit 7e7f278

File tree

5 files changed

+81
-79
lines changed

5 files changed

+81
-79
lines changed

lib/email_helpers.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,14 +45,17 @@ def send_error_email(receiver_email, error, run_id=None, name=None, machine=None
4545
Name: {name}
4646
Run Id: {run_id}
4747
Machine: {machine}
48-
Link: {url}/stats.html?id={run_id}
48+
Link: {link}
4949
5050
{errors}
5151
5252
--
5353
{url}"""
5454

5555
config = GlobalConfig().config
56+
link = 'No link available'
57+
if run_id is not None:
58+
link = f"Link: {config['cluster']['metrics_url']}/stats.html?id={run_id}"
5659
message = message.format(
5760
receiver_email=receiver_email,
5861
errors=error,
@@ -61,11 +64,12 @@ def send_error_email(receiver_email, error, run_id=None, name=None, machine=None
6164
bcc_email=config['admin']['bcc_email'],
6265
url=config['cluster']['metrics_url'],
6366
run_id=run_id,
67+
link=link,
6468
smtp_sender=config['smtp']['sender'])
6569
send_email(message, [receiver_email, config['admin']['bcc_email']])
6670

6771

68-
def send_report_email(receiver_email, report_id, name, machine=None):
72+
def send_report_email(receiver_email, run_id, name, machine=None):
6973
message = """\
7074
From: {smtp_sender}
7175
To: {receiver_email}
@@ -75,15 +79,15 @@ def send_report_email(receiver_email, report_id, name, machine=None):
7579
Run Name: {name}
7680
Machine: {machine}
7781
78-
Your report is now accessible under the URL: {url}/stats.html?id={report_id}
82+
Your report is now accessible under the URL: {url}/stats.html?id={run_id}
7983
8084
--
8185
{url}"""
8286

8387
config = GlobalConfig().config
8488
message = message.format(
8589
receiver_email=receiver_email,
86-
report_id=report_id,
90+
run_id=run_id,
8791
machine=machine,
8892
name=name,
8993
bcc_email=config['admin']['bcc_email'],
@@ -97,8 +101,8 @@ def send_report_email(receiver_email, report_id, name, machine=None):
97101

98102
parser = argparse.ArgumentParser()
99103
parser.add_argument('receiver_email', help='Please supply a receiver_email to send the email to')
100-
parser.add_argument('report_id', help='Please supply a report_id to include in the email')
104+
parser.add_argument('run_id', help='Please supply a run_id to include in the email')
101105

102106
args = parser.parse_args() # script will exit if arguments is not present
103107

104-
send_report_email(args.receiver_email, args.report_id, "My custom name")
108+
send_report_email(args.receiver_email, args.run_id, "My custom name")

runner.py

Lines changed: 41 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -116,8 +116,11 @@ def __init__(self,
116116
self._sci = {'R_d': None, 'R': 0}
117117
self._job_id = job_id
118118
self._arguments = locals()
119+
self._folder = f"{self._tmp_folder}/repo" # default if not changed in checkout_repository
120+
self._run_id = None
119121
self._commit_hash = None
120122
self._commit_timestamp = None
123+
121124
del self._arguments['self'] # self is not needed and also cannot be serialzed. We remove it
122125

123126

@@ -137,8 +140,6 @@ def __init__(self,
137140
self.__services_to_pause_phase = {}
138141
self.__join_default_network = False
139142
self.__docker_params = []
140-
self.__folder = f"{self._tmp_folder}/repo" # default if not changed in checkout_repository
141-
self.__run_id = None
142143

143144
# we currently do not use this variable
144145
# self.__filename = self._original_filename # this can be changed later if working directory changes
@@ -152,21 +153,20 @@ def initialize_run(self):
152153
# We issue a fetch_one() instead of a query() here, cause we want to get the RUN_ID
153154

154155
# we also update the branch here again, as this might not be main in case of local filesystem
155-
156-
self.__run_id = DB().fetch_one("""
156+
self._run_id = DB().fetch_one("""
157157
INSERT INTO runs (job_id, name, uri, email, branch, filename, commit_hash, commit_timestamp, runner_arguments, created_at)
158158
VALUES (%s, %s, %s, 'manual', %s, %s, %s, %s, %s, NOW())
159159
RETURNING id
160160
""", params=(self._job_id, self._name, self._uri, self._branch, self._original_filename, self._commit_hash, self._commit_timestamp, json.dumps(self._arguments)))[0]
161-
return self.__run_id
161+
return self._run_id
162162

163163
def initialize_folder(self, path):
164164
shutil.rmtree(path, ignore_errors=True)
165165
Path(path).mkdir(parents=True, exist_ok=True)
166166

167167
def save_notes_runner(self):
168168
print(TerminalColors.HEADER, '\nSaving notes: ', TerminalColors.ENDC, self.__notes_helper.get_notes())
169-
self.__notes_helper.save_to_db(self.__run_id)
169+
self.__notes_helper.save_to_db(self._run_id)
170170

171171
def check_system(self, mode='start'):
172172
if self._skip_system_checks:
@@ -198,7 +198,7 @@ def checkout_repository(self):
198198
'--recurse-submodules',
199199
'--shallow-submodules',
200200
self._uri,
201-
self.__folder
201+
self._folder
202202
],
203203
check=True,
204204
capture_output=True,
@@ -214,7 +214,7 @@ def checkout_repository(self):
214214
'--recurse-submodules',
215215
'--shallow-submodules',
216216
self._uri,
217-
self.__folder
217+
self._folder
218218
],
219219
check=True,
220220
capture_output=True,
@@ -225,9 +225,9 @@ def checkout_repository(self):
225225
if self._branch:
226226
# we never want to checkout a local directory to a different branch as this might also be the GMT directory itself and might confuse the tool
227227
raise RuntimeError('Specified --branch but using local URI. Did you mean to specify a github url?')
228-
self.__folder = self._uri
228+
self._folder = self._uri
229229

230-
self._branch = subprocess.check_output(['git', 'branch', '--show-current'], cwd=self.__folder, encoding='UTF-8').strip()
230+
self._branch = subprocess.check_output(['git', 'branch', '--show-current'], cwd=self._folder, encoding='UTF-8').strip()
231231

232232
# we can safely do this, even with problematic folders, as the folder can only be a local unsafe one when
233233
# running in CLI mode
@@ -236,7 +236,7 @@ def checkout_repository(self):
236236
check=True,
237237
capture_output=True,
238238
encoding='UTF-8',
239-
cwd=self.__folder
239+
cwd=self._folder
240240
)
241241
self._commit_hash = self._commit_hash.stdout.strip("\n")
242242

@@ -245,8 +245,9 @@ def checkout_repository(self):
245245
check=True,
246246
capture_output=True,
247247
encoding='UTF-8',
248-
cwd=self.__folder
248+
cwd=self._folder
249249
)
250+
250251
self._commit_timestamp = self._commit_timestamp.stdout.strip("\n")
251252
self._commit_timestamp = datetime.strptime(self._commit_timestamp, "%Y-%m-%d %H:%M:%S %z")
252253

@@ -295,13 +296,13 @@ def recursive_lookup(k, d):
295296

296297
Loader.add_constructor('!include', Loader.include)
297298

298-
usage_scenario_file = join_paths(self.__folder, self._original_filename, 'file')
299+
usage_scenario_file = join_paths(self._folder, self._original_filename, 'file')
299300

300301
# We set the working folder now to the actual location of the usage_scenario
301302
if '/' in self._original_filename:
302-
self.__folder = usage_scenario_file.rsplit('/', 1)[0]
303+
self._folder = usage_scenario_file.rsplit('/', 1)[0]
303304
#self.__filename = usage_scenario_file.rsplit('/', 1)[1] # we currently do not use this variable
304-
print("Working folder changed to ", self.__folder)
305+
print("Working folder changed to ", self._folder)
305306

306307

307308
with open(usage_scenario_file, 'r', encoding='utf-8') as fp:
@@ -472,7 +473,7 @@ def update_and_insert_specs(self):
472473
json.dumps(measurement_config),
473474
escape(json.dumps(self._usage_scenario), quote=False),
474475
gmt_hash,
475-
self.__run_id)
476+
self._run_id)
476477
)
477478

478479
def import_metric_providers(self):
@@ -573,11 +574,11 @@ def build_docker_images(self):
573574
self.__notes_helper.add_note({'note': f"Building {service['image']}", 'detail_name': '[NOTES]', 'timestamp': int(time.time_ns() / 1_000)})
574575

575576
# Make sure the context docker file exists and is not trying to escape some root. We don't need the returns
576-
context_path = join_paths(self.__folder, context, 'dir')
577+
context_path = join_paths(self._folder, context, 'dir')
577578
join_paths(context_path, dockerfile, 'file')
578579

579580
docker_build_command = ['docker', 'run', '--rm',
580-
'-v', f"{self.__folder}:/workspace:ro", # this is the folder where the usage_scenario is!
581+
'-v', f"{self._folder}:/workspace:ro", # this is the folder where the usage_scenario is!
581582
'-v', f"{temp_dir}:/output",
582583
'gcr.io/kaniko-project/executor:latest',
583584
f"--dockerfile=/workspace/{context}/{dockerfile}",
@@ -706,9 +707,9 @@ def setup_services(self):
706707

707708
docker_run_string.append('-v')
708709
if 'folder-destination' in service:
709-
docker_run_string.append(f"{self.__folder}:{service['folder-destination']}:ro")
710+
docker_run_string.append(f"{self._folder}:{service['folder-destination']}:ro")
710711
else:
711-
docker_run_string.append(f"{self.__folder}:/tmp/repo:ro")
712+
docker_run_string.append(f"{self._folder}:/tmp/repo:ro")
712713

713714
if self.__docker_params:
714715
docker_run_string[2:2] = self.__docker_params
@@ -727,7 +728,7 @@ def setup_services(self):
727728
docker_run_string.append('-v')
728729
if volume.startswith('./'): # we have a bind-mount with relative path
729730
vol = volume.split(':',1) # there might be an :ro etc at the end, so only split once
730-
path = os.path.realpath(os.path.join(self.__folder, vol[0]))
731+
path = os.path.realpath(os.path.join(self._folder, vol[0]))
731732
if not os.path.exists(path):
732733
raise RuntimeError(f"Service '{service_name}' volume path does not exist: {path}")
733734
docker_run_string.append(f"{path}:{vol[1]}")
@@ -740,16 +741,16 @@ def setup_services(self):
740741
vol = volume.split(':')
741742
# We always assume the format to be ./dir:dir:[flag] as if we allow none bind mounts people
742743
# could create volumes that would linger on our system.
743-
path = os.path.realpath(os.path.join(self.__folder, vol[0]))
744+
path = os.path.realpath(os.path.join(self._folder, vol[0]))
744745
if not os.path.exists(path):
745746
raise RuntimeError(f"Service '{service_name}' volume path does not exist: {path}")
746747

747-
# Check that the path starts with self.__folder
748-
if not path.startswith(self.__folder):
748+
# Check that the path starts with self._folder
749+
if not path.startswith(self._folder):
749750
raise RuntimeError(f"Service '{service_name}' trying to escape folder: {path}")
750751

751752
# To double check we also check if it is in the files allow list
752-
if path not in [str(item) for item in Path(self.__folder).rglob("*")]:
753+
if path not in [str(item) for item in Path(self._folder).rglob("*")]:
753754
raise RuntimeError(f"Service '{service_name}' volume '{path}' not in allowed file list")
754755

755756
if len(vol) == 3:
@@ -1100,7 +1101,7 @@ def stop_metric_providers(self):
11001101

11011102
metric_provider.stop_profiling()
11021103

1103-
df = metric_provider.read_metrics(self.__run_id, self.__containers)
1104+
df = metric_provider.read_metrics(self._run_id, self.__containers)
11041105
if isinstance(df, int):
11051106
print('Imported', TerminalColors.HEADER, df, TerminalColors.ENDC, 'metrics from ', metric_provider.__class__.__name__)
11061107
# If df returns an int the data has already been committed to the db
@@ -1169,7 +1170,7 @@ def update_start_and_end_times(self):
11691170
UPDATE runs
11701171
SET start_measurement=%s, end_measurement=%s
11711172
WHERE id = %s
1172-
""", params=(self.__start_measurement, self.__end_measurement, self.__run_id))
1173+
""", params=(self.__start_measurement, self.__end_measurement, self._run_id))
11731174

11741175
def store_phases(self):
11751176
print(TerminalColors.HEADER, '\nUpdating phases in DB', TerminalColors.ENDC)
@@ -1181,7 +1182,7 @@ def store_phases(self):
11811182
UPDATE runs
11821183
SET phases=%s
11831184
WHERE id = %s
1184-
""", params=(json.dumps(self.__phases), self.__run_id))
1185+
""", params=(json.dumps(self.__phases), self._run_id))
11851186

11861187
def read_container_logs(self):
11871188
print(TerminalColors.HEADER, '\nCapturing container logs', TerminalColors.ENDC)
@@ -1226,7 +1227,7 @@ def save_stdout_logs(self):
12261227
UPDATE runs
12271228
SET logs=%s
12281229
WHERE id = %s
1229-
""", params=(logs_as_str, self.__run_id))
1230+
""", params=(logs_as_str, self._run_id))
12301231

12311232

12321233
def cleanup(self):
@@ -1266,8 +1267,6 @@ def cleanup(self):
12661267
self.__end_measurement = None
12671268
self.__join_default_network = False
12681269
#self.__filename = self._original_filename # # we currently do not use this variable
1269-
self.__folder = f"{self._tmp_folder}/repo"
1270-
self.__run_id = None
12711270

12721271
def run(self):
12731272
'''
@@ -1280,13 +1279,12 @@ def run(self):
12801279
Methods thus will behave differently given the runner was instantiated with different arguments.
12811280
12821281
'''
1283-
return_run_id = None
12841282
try:
12851283
config = GlobalConfig().config
12861284
self.check_system('start')
12871285
self.initialize_folder(self._tmp_folder)
12881286
self.checkout_repository()
1289-
return_run_id = self.initialize_run()
1287+
self.initialize_run()
12901288
self.initial_parse()
12911289
self.import_metric_providers()
12921290
self.populate_image_names()
@@ -1399,7 +1397,7 @@ def run(self):
13991397

14001398
print(TerminalColors.OKGREEN, arrows('MEASUREMENT SUCCESSFULLY COMPLETED'), TerminalColors.ENDC)
14011399

1402-
return return_run_id # we cannot return self.__run_id as this is reset in cleanup()
1400+
return self._run_id
14031401

14041402
if __name__ == '__main__':
14051403
import argparse
@@ -1475,7 +1473,6 @@ def run(self):
14751473
sys.exit(1)
14761474
GlobalConfig(config_name=args.config_override)
14771475

1478-
successful_run_id = None
14791476
runner = Runner(name=args.name, uri=args.uri, uri_type=run_type, filename=args.filename,
14801477
branch=args.branch, debug_mode=args.debug, allow_unsafe=args.allow_unsafe,
14811478
no_file_cleanup=args.no_file_cleanup, skip_system_checks=args.skip_system_checks,
@@ -1486,7 +1483,7 @@ def run(self):
14861483
# Using a very broad exception makes sense in this case as we have excepted all the specific ones before
14871484
#pylint: disable=broad-except
14881485
try:
1489-
successful_run_id = runner.run() # Start main code
1486+
runner.run() # Start main code
14901487

14911488
# this code should live at a different position.
14921489
# From a user perspective it makes perfect sense to run both jobs directly after each other
@@ -1499,24 +1496,24 @@ def run(self):
14991496
# loop over them issueing separate queries to the DB
15001497
from tools.phase_stats import build_and_store_phase_stats
15011498

1502-
print("Run id is", successful_run_id)
1503-
build_and_store_phase_stats(successful_run_id, runner._sci)
1499+
print("Run id is", runner._run_id)
1500+
build_and_store_phase_stats(runner._run_id, runner._sci)
15041501

15051502

15061503
print(TerminalColors.OKGREEN,'\n\n####################################################################################')
1507-
print(f"Please access your report on the URL {GlobalConfig().config['cluster']['metrics_url']}/stats.html?id={successful_run_id}")
1504+
print(f"Please access your report on the URL {GlobalConfig().config['cluster']['metrics_url']}/stats.html?id={runner._run_id}")
15081505
print('####################################################################################\n\n', TerminalColors.ENDC)
15091506

15101507
except FileNotFoundError as e:
1511-
error_helpers.log_error('Docker command failed.', e, successful_run_id)
1508+
error_helpers.log_error('Docker command failed.', e, runner._run_id)
15121509
except subprocess.CalledProcessError as e:
1513-
error_helpers.log_error('Docker command failed', 'Stdout:', e.stdout, 'Stderr:', e.stderr, successful_run_id)
1510+
error_helpers.log_error('Docker command failed', 'Stdout:', e.stdout, 'Stderr:', e.stderr, runner._run_id)
15141511
except KeyError as e:
1515-
error_helpers.log_error('Was expecting a value inside the usage_scenario.yml file, but value was missing: ', e, successful_run_id)
1512+
error_helpers.log_error('Was expecting a value inside the usage_scenario.yml file, but value was missing: ', e, runner._run_id)
15161513
except RuntimeError as e:
1517-
error_helpers.log_error('RuntimeError occured in runner.py: ', e, successful_run_id)
1514+
error_helpers.log_error('RuntimeError occured in runner.py: ', e, runner._run_id)
15181515
except BaseException as e:
1519-
error_helpers.log_error('Base exception occured in runner.py: ', e, successful_run_id)
1516+
error_helpers.log_error('Base exception occured in runner.py: ', e, runner._run_id)
15201517
finally:
15211518
if args.print_logs:
15221519
for container_id_outer, std_out in runner.get_logs().items():

tests/tools/test_jobs.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ def test_insert_job():
6868
job_id = Job.insert('Test Name', 'Test URL', 'Test Email', 'Test Branch', 'Test filename', 1)
6969
assert job_id is not None
7070
job = Job.get_job('run')
71-
assert job.state == 'WAITING'
71+
assert job._state == 'WAITING'
7272

7373
def test_simple_run_job():
7474
name = utils.randomword(12)

tools/client.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,14 @@ def set_status(status_code, data=None, run_id=None):
3838
set_status('job_no')
3939
time.sleep(GlobalConfig().config['client']['sleep_time_no_job'])
4040
else:
41-
set_status('job_start', '', job.run_id)
41+
set_status('job_start', '', job._run_id)
4242
try:
4343
job.process(docker_prune=True)
4444
except Exception as exc:
45-
set_status('job_error', str(exc), job.run_id)
45+
set_status('job_error', str(exc), job._run_id)
4646
handle_job_exception(exc, job)
4747
else:
48-
set_status('job_end', '', job.run_id)
48+
set_status('job_end', '', job._run_id)
4949

5050
set_status('cleanup_start')
5151

0 commit comments

Comments
 (0)