Belle II Software  release-05-02-19
clustercontrolbase.py
1 #!/usr/bin/env python3
2 # -*- coding: utf-8 -*-
3 
4 # std
5 import logging
6 import os
7 import stat
8 from abc import abstractmethod
9 
10 # ours
11 from validationscript import Script
12 
13 
15  """
16  Base class which provides basic functionality to wrap basf2 into a shell
17  script setting up the environment and checking for completion of script
18  """
19 
20  def __init__(self):
21  """!
22  The default constructor.
23  """
24 
26  self.path = os.getcwd()
27 
28 
32  self.logger = logging.getLogger('validate_basf2')
33 
34  # We need to set up the same environment on the cluster like on the
35  # local machine. The information can be extracted from $BELLE2_TOOLS,
36  # $BELLE2_RELEASE_DIR and $BELLE2_LOCAL_DIR
37 
38 
39  self.tools = self.adjust_path(os.environ['BELLE2_TOOLS'])
40  belle2_release_dir = os.environ.get('BELLE2_RELEASE_DIR', None)
41  belle2_local_dir = os.environ.get('BELLE2_LOCAL_DIR', None)
42 
43 
44  self.b2setup = 'b2setup'
45  if belle2_release_dir is not None:
46  self.b2setup += ' ' + belle2_release_dir.split('/')[-1]
47  if belle2_local_dir is not None:
48  self.b2setup = 'MY_BELLE2_DIR=' + \
49  self.adjust_path(belle2_local_dir) + ' ' + self.b2setup
50  if os.environ.get('BELLE2_OPTION') != 'debug':
51  self.b2setup += '; b2code-option ' + os.environ.get('BELLE2_OPTION')
52 
53  # Write to log which revision we are using
54  self.logger.debug(f'Setting up the following release: {self.b2setup}')
55 
56  # Define the folder in which the log of the cluster messages will be
57  # stored (same folder like the log for validate_basf2.py)
58  clusterlog_dir = './html/logs/__general__/'
59  if not os.path.exists(clusterlog_dir):
60  os.makedirs(clusterlog_dir)
61 
62 
63  self.clusterlog = open(clusterlog_dir + 'clusterlog.log', 'w+')
64 
65  def createDoneFileName(self, job: Script) -> str:
66  """!
67  Generate the file name used for the done output
68  """
69  return f"{self.path}/script_{job.name}.done"
70 
71  def prepareSubmission(self, job: Script, options, tag):
72  """!
73  Setup output folders and create the wrapping shell script. Will return
74  the full file name of the generated wrapper script.
75  """
76 
77  # Define the folder in which the results (= the ROOT files) should be
78  # created. This is where the files containing plots will end up. By
79  # convention, data files will be stored in the parent dir.
80  # Then make sure the folder exists (create if it does not exist) and
81  # change to cwd to this folder.
82  output_dir = os.path.abspath(f'./results/{tag}/{job.package}')
83  if not os.path.exists(output_dir):
84  os.makedirs(output_dir)
85 
86  # Path where log file is supposed to be created
87  log_file = output_dir + '/' + os.path.basename(job.path) + '.log'
88 
89  # Remove any left over done files
90  donefile_path = self.createDoneFileName(job)
91  if os.path.isfile(donefile_path):
92  os.remove(donefile_path)
93 
94  # Now we need to distinguish between .py and .C files:
95  extension = os.path.splitext(job.path)[1]
96  if extension == '.C':
97  # .c files are executed with root
98  command = 'root -b -q ' + job.path
99  else:
100  # .py files are executed with basf2
101  # 'options' contains an option-string for basf2, e.g. '-n 100'
102  command = f'basf2 {job.path} {options}'
103 
104  # Create a helpfile-shellscript, which contains all the commands that
105  # need to be executed by the cluster.
106  # First, set up the basf2 tools and perform b2setup with the correct
107  # revision. The execute the command (i.e. run basf2 or ROOT on a
108  # steering file). Write the return code of that into a *.done file.
109  # Delete the helpfile-shellscript.
110  tmp_name = self.path + '/' + 'script_' + job.name + '.sh'
111  with open(tmp_name, 'w+') as tmp_file:
112  tmp_file.write('#!/bin/bash \n\n' +
113  'BELLE2_NO_TOOLS_CHECK=1 \n' +
114  'source {0}/b2setup \n'.format(self.tools) +
115  'cd {0} \n'.format(self.adjust_path(output_dir)) +
116  '{0} \n'.format(command) +
117  'echo $? > {0}/script_{1}.done \n'
118  .format(self.path, job.name) +
119  'rm {0} \n'.format(tmp_name))
120 
121  # Make the helpfile-shellscript executable
122  st = os.stat(tmp_name)
123  os.chmod(tmp_name, st.st_mode | stat.S_IEXEC)
124 
125  return tmp_name
126 
127  def checkDoneFile(self, job):
128  """!
129  Checks whether the '.done'-file has been created for a job. If so, it
130  returns True, else it returns False in the first part of the tuple.
131  Also deletes the .done-File it if exists.
132  The second entry in the tuple will be the exit code read from the done file
133  """
134 
135  # If there is a file indicating the job is done, that is its name:
136  donefile_path = self.createDoneFileName(job)
137 
138  donefile_exists = False
139  # Check if such a file exists. If so, this means that the job has
140  # finished.
141  if os.path.isfile(donefile_path):
142 
143  # Read the returncode/exit_status for the job from the *.done-file
144  with open(donefile_path) as f:
145  try:
146  returncode = int(f.read().strip())
147  except ValueError:
148  returncode = -666
149 
150  print(f"donefile found with return code {returncode}")
151  donefile_exists = True
152  os.remove(donefile_path)
153  else:
154  print("no donefile found")
155  returncode = -555
156 
157  return [donefile_exists, returncode]
158 
159  def terminate(self, job: Script):
160  """! Terminate running job.
161  """
162  self.logger.error("Script termination not supported.")
163 
164  @abstractmethod
165  def adjust_path(self, path):
166  """!
167  This method can be used if path names are different on submission
168  and execution hosts.
169  @param path: The past that needs to be adjusted
170  @return: The adjusted path
171  """
172  pass
clustercontrolbase.ClusterBase.logger
logger
Contains a reference to the logger-object from validate_basf2 Set up the logging functionality for th...
Definition: clustercontrolbase.py:32
clustercontrolbase.ClusterBase.createDoneFileName
str createDoneFileName(self, Script job)
Generate the file name used for the done output.
Definition: clustercontrolbase.py:65
clustercontrolbase.ClusterBase
Definition: clustercontrolbase.py:14
clustercontrolbase.ClusterBase.checkDoneFile
def checkDoneFile(self, job)
Checks whether the '.done'-file has been created for a job.
Definition: clustercontrolbase.py:127
clustercontrolbase.ClusterBase.path
path
The path, where the help files are being created Maybe there should be a special subfolder for them?
Definition: clustercontrolbase.py:26
clustercontrolbase.ClusterBase.prepareSubmission
def prepareSubmission(self, Script job, options, tag)
Setup output folders and create the wrapping shell script.
Definition: clustercontrolbase.py:71
clustercontrolbase.ClusterBase.tools
tools
Path to the basf2 tools and central/local release.
Definition: clustercontrolbase.py:39
clustercontrolbase.ClusterBase.clusterlog
clusterlog
The file object to which all cluster messages will be written.
Definition: clustercontrolbase.py:63
clustercontrolbase.ClusterBase.__init__
def __init__(self)
The default constructor.
Definition: clustercontrolbase.py:20
clustercontrolbase.ClusterBase.terminate
def terminate(self, Script job)
Terminate running job.
Definition: clustercontrolbase.py:159
clustercontrolbase.ClusterBase.adjust_path
def adjust_path(self, path)
This method can be used if path names are different on submission and execution hosts.
Definition: clustercontrolbase.py:165
clustercontrolbase.ClusterBase.b2setup
b2setup
The command for b2setup (and b2code-option)
Definition: clustercontrolbase.py:44