| OLD | NEW |
| 1 """The main job wrapper | 1 """The main job wrapper |
| 2 | 2 |
| 3 This is the core infrastructure. | 3 This is the core infrastructure. |
| 4 | 4 |
| 5 Copyright Andy Whitcroft, Martin J. Bligh 2006 | 5 Copyright Andy Whitcroft, Martin J. Bligh 2006 |
| 6 """ | 6 """ |
| 7 | 7 |
| 8 import copy, os, platform, re, shutil, sys, time, traceback, types, glob | 8 import copy, os, platform, re, shutil, sys, time, traceback, types, glob |
| 9 import logging, getpass, errno, weakref | 9 import logging, getpass, errno, weakref |
| 10 import cPickle as pickle | 10 import cPickle as pickle |
| (...skipping 117 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 128 @classmethod | 128 @classmethod |
| 129 def _find_base_directories(cls): | 129 def _find_base_directories(cls): |
| 130 """ | 130 """ |
| 131 Determine locations of autodir and clientdir (which are the same) | 131 Determine locations of autodir and clientdir (which are the same) |
| 132 using os.environ. Serverdir does not exist in this context. | 132 using os.environ. Serverdir does not exist in this context. |
| 133 """ | 133 """ |
| 134 autodir = clientdir = cls._get_environ_autodir() | 134 autodir = clientdir = cls._get_environ_autodir() |
| 135 return autodir, clientdir, None | 135 return autodir, clientdir, None |
| 136 | 136 |
| 137 | 137 |
| 138 @classmethod |
| 139 def _parse_args(cls, args): |
| 140 return re.findall("[^\s]*?['|\"].*?['|\"]|[^\s]+", args) |
| 141 |
| 142 |
| 138 def _find_resultdir(self, options): | 143 def _find_resultdir(self, options): |
| 139 """ | 144 """ |
| 140 Determine the directory for storing results. On a client this is | 145 Determine the directory for storing results. On a client this is |
| 141 always <autodir>/results/<tag>, where tag is passed in on the command | 146 always <autodir>/results/<tag>, where tag is passed in on the command |
| 142 line as an option. | 147 line as an option. |
| 143 """ | 148 """ |
| 144 return os.path.join(self.autodir, 'results', options.tag) | 149 return os.path.join(self.autodir, 'results', options.tag) |
| 145 | 150 |
| 146 | 151 |
| 147 def _get_status_logger(self): | 152 def _get_status_logger(self): |
| 148 """Return a reference to the status logger.""" | 153 """Return a reference to the status logger.""" |
| 149 return self._logger | 154 return self._logger |
| 150 | 155 |
| 151 | 156 |
| 152 def _pre_record_init(self, control, options): | 157 def _pre_record_init(self, control, options): |
| 153 """ | 158 """ |
| 154 Initialization function that should peform ONLY the required | 159 Initialization function that should peform ONLY the required |
| 155 setup so that the self.record() method works. | 160 setup so that the self.record() method works. |
| 156 | 161 |
| 157 As of now self.record() needs self.resultdir, self._group_level, | 162 As of now self.record() needs self.resultdir, self._group_level, |
| 158 self.harness and of course self._logger. | 163 self.harness and of course self._logger. |
| 159 """ | 164 """ |
| 160 if not options.cont: | 165 if not options.cont: |
| 161 self._cleanup_debugdir_files() | 166 self._cleanup_debugdir_files() |
| 162 self._cleanup_results_dir() | 167 self._cleanup_results_dir() |
| 163 | 168 |
| 164 logging_manager.configure_logging( | 169 logging_manager.configure_logging( |
| 165 client_logging_config.ClientLoggingConfig(), | 170 client_logging_config.ClientLoggingConfig(), |
| 166 results_dir=self.resultdir, | 171 results_dir=self.resultdir, |
| 167 verbose=options.verbose) | 172 verbose=options.verbose) |
| 168 logging.info('Writing results to %s', self.resultdir) | 173 logging.info('Writing results to %s', self.resultdir) |
| 169 | 174 |
| 170 # init_group_level needs the state | 175 # init_group_level needs the state |
| 171 self.control = os.path.realpath(control) | 176 self.control = os.path.realpath(control) |
| 172 self._is_continuation = options.cont | 177 self._is_continuation = options.cont |
| 173 self._current_step_ancestry = [] | 178 self._current_step_ancestry = [] |
| 174 self._next_step_index = 0 | 179 self._next_step_index = 0 |
| 175 self._load_state() | 180 self._load_state() |
| 176 | 181 |
| 177 self.harness = harness.select(options.harness, self) | 182 # harness is chosen by following rules: |
| 183 # 1. explicitly specified via command line |
| 184 # 2. harness stored in state file (if continuing job '-c') |
| 185 # 3. default harness |
| 186 selected_harness = None |
| 187 if options.harness: |
| 188 selected_harness = options.harness |
| 189 self._state.set('client', 'harness', selected_harness) |
| 190 else: |
| 191 stored_harness = self._state.get('client', 'harness', None) |
| 192 if stored_harness: |
| 193 selected_harness = stored_harness |
| 194 |
| 195 self.harness = harness.select(selected_harness, self) |
| 178 | 196 |
| 179 # set up the status logger | 197 # set up the status logger |
| 180 def client_job_record_hook(entry): | 198 def client_job_record_hook(entry): |
| 181 msg_tag = '' | 199 msg_tag = '' |
| 182 if '.' in self._logger.global_filename: | 200 if '.' in self._logger.global_filename: |
| 183 msg_tag = self._logger.global_filename.split('.', 1)[1] | 201 msg_tag = self._logger.global_filename.split('.', 1)[1] |
| 184 # send the entry to the job harness | 202 # send the entry to the job harness |
| 185 message = '\n'.join([entry.message] + entry.extra_message_lines) | 203 message = '\n'.join([entry.message] + entry.extra_message_lines) |
| 186 rendered_entry = self._logger.render_entry(entry) | 204 rendered_entry = self._logger.render_entry(entry) |
| 187 self.harness.test_status_detail(entry.status_code, entry.subdir, | 205 self.harness.test_status_detail(entry.status_code, entry.subdir, |
| 188 entry.operation, message, msg_tag) | 206 entry.operation, message, msg_tag) |
| 189 self.harness.test_status(rendered_entry, msg_tag) | 207 self.harness.test_status(rendered_entry, msg_tag) |
| 190 # send the entry to stdout, if it's enabled | 208 # send the entry to stdout, if it's enabled |
| 191 logging.info(rendered_entry) | 209 logging.info(rendered_entry) |
| 192 self._logger = base_job.status_logger( | 210 self._logger = base_job.status_logger( |
| 193 self, status_indenter(self), record_hook=client_job_record_hook) | 211 self, status_indenter(self), record_hook=client_job_record_hook, |
| 194 | 212 tap_writer=self._tap) |
| 195 | 213 |
| 196 def _post_record_init(self, control, options, drop_caches, | 214 def _post_record_init(self, control, options, drop_caches, |
| 197 extra_copy_cmdline): | 215 extra_copy_cmdline): |
| 198 """ | 216 """ |
| 199 Perform job initialization not required by self.record(). | 217 Perform job initialization not required by self.record(). |
| 200 """ | 218 """ |
| 201 self._init_drop_caches(drop_caches) | 219 self._init_drop_caches(drop_caches) |
| 202 | 220 |
| 203 self._init_packages() | 221 self._init_packages() |
| 204 | 222 |
| (...skipping 18 matching lines...) Expand all Loading... |
| 223 self.profilers = profilers.profilers(self) | 241 self.profilers = profilers.profilers(self) |
| 224 | 242 |
| 225 self._init_bootloader() | 243 self._init_bootloader() |
| 226 | 244 |
| 227 self.machines = [options.hostname] | 245 self.machines = [options.hostname] |
| 228 self.hosts = set([local_host.LocalHost(hostname=options.hostname, | 246 self.hosts = set([local_host.LocalHost(hostname=options.hostname, |
| 229 bootloader=self.bootloader)]) | 247 bootloader=self.bootloader)]) |
| 230 | 248 |
| 231 self.args = [] | 249 self.args = [] |
| 232 if options.args: | 250 if options.args: |
| 233 self.args = options.args.split() | 251 self.args = self._parse_args(options.args) |
| 234 | 252 |
| 235 if options.user: | 253 if options.user: |
| 236 self.user = options.user | 254 self.user = options.user |
| 237 else: | 255 else: |
| 238 self.user = getpass.getuser() | 256 self.user = getpass.getuser() |
| 239 | 257 |
| 240 self.sysinfo.log_per_reboot_data() | 258 self.sysinfo.log_per_reboot_data() |
| 241 | 259 |
| 242 if not options.cont: | 260 if not options.cont: |
| 243 self.record('START', None, None) | 261 self.record('START', None, None) |
| (...skipping 331 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 575 function: | 593 function: |
| 576 subroutine to run | 594 subroutine to run |
| 577 *args: | 595 *args: |
| 578 arguments for the function | 596 arguments for the function |
| 579 | 597 |
| 580 Returns the result of the passed in function | 598 Returns the result of the passed in function |
| 581 """ | 599 """ |
| 582 | 600 |
| 583 try: | 601 try: |
| 584 self.record('START', subdir, testname) | 602 self.record('START', subdir, testname) |
| 603 self._state.set('client', 'unexpected_reboot', (subdir, testname)) |
| 585 result = function(*args, **dargs) | 604 result = function(*args, **dargs) |
| 586 self.record('END GOOD', subdir, testname) | 605 self.record('END GOOD', subdir, testname) |
| 587 return result | 606 return result |
| 588 except error.TestBaseException, e: | 607 except error.TestBaseException, e: |
| 589 self.record('END %s' % e.exit_status, subdir, testname) | 608 self.record('END %s' % e.exit_status, subdir, testname) |
| 590 raise | 609 raise |
| 591 except error.JobError, e: | 610 except error.JobError, e: |
| 592 self.record('END ABORT', subdir, testname) | 611 self.record('END ABORT', subdir, testname) |
| 593 raise | 612 raise |
| 594 except Exception, e: | 613 except Exception, e: |
| 595 # This should only ever happen due to a bug in the given | 614 # This should only ever happen due to a bug in the given |
| 596 # function's code. The common case of being called by | 615 # function's code. The common case of being called by |
| 597 # run_test() will never reach this. If a control file called | 616 # run_test() will never reach this. If a control file called |
| 598 # run_group() itself, bugs in its function will be caught | 617 # run_group() itself, bugs in its function will be caught |
| 599 # here. | 618 # here. |
| 600 err_msg = str(e) + '\n' + traceback.format_exc() | 619 err_msg = str(e) + '\n' + traceback.format_exc() |
| 601 self.record('END ERROR', subdir, testname, err_msg) | 620 self.record('END ERROR', subdir, testname, err_msg) |
| 602 raise | 621 raise |
| 622 finally: |
| 623 self._state.discard('client', 'unexpected_reboot') |
| 603 | 624 |
| 604 | 625 |
| 605 def run_group(self, function, tag=None, **dargs): | 626 def run_group(self, function, tag=None, **dargs): |
| 606 """ | 627 """ |
| 607 Run a function nested within a group level. | 628 Run a function nested within a group level. |
| 608 | 629 |
| 609 function: | 630 function: |
| 610 Callable to run. | 631 Callable to run. |
| 611 tag: | 632 tag: |
| 612 An optional tag name for the group. If None (default) | 633 An optional tag name for the group. If None (default) |
| (...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 657 @param running_id: An optional running_id to include in the reboot | 678 @param running_id: An optional running_id to include in the reboot |
| 658 failure log message | 679 failure log message |
| 659 | 680 |
| 660 @raise JobError: Raised if the current configuration does not match the | 681 @raise JobError: Raised if the current configuration does not match the |
| 661 pre-reboot configuration. | 682 pre-reboot configuration. |
| 662 """ | 683 """ |
| 663 # check to see if any partitions have changed | 684 # check to see if any partitions have changed |
| 664 partition_list = partition_lib.get_partition_list(self, | 685 partition_list = partition_lib.get_partition_list(self, |
| 665 exclude_swap=False) | 686 exclude_swap=False) |
| 666 mount_info = partition_lib.get_mount_info(partition_list) | 687 mount_info = partition_lib.get_mount_info(partition_list) |
| 667 | |
| 668 old_mount_info = self._state.get('client', 'mount_info') | 688 old_mount_info = self._state.get('client', 'mount_info') |
| 669 if mount_info != old_mount_info: | 689 if mount_info != old_mount_info: |
| 670 new_entries = mount_info - old_mount_info | 690 new_entries = mount_info - old_mount_info |
| 671 old_entries = old_mount_info - mount_info | 691 old_entries = old_mount_info - mount_info |
| 672 description = ("mounted partitions are different after reboot " | 692 description = ("mounted partitions are different after reboot " |
| 673 "(old entries: %s, new entries: %s)" % | 693 "(old entries: %s, new entries: %s)" % |
| 674 (old_entries, new_entries)) | 694 (old_entries, new_entries)) |
| 675 self._record_reboot_failure(subdir, "reboot.verify_config", | 695 self._record_reboot_failure(subdir, "reboot.verify_config", |
| 676 description, running_id=running_id) | 696 description, running_id=running_id) |
| 677 raise error.JobError("Reboot failed: %s" % description) | 697 raise error.JobError("Reboot failed: %s" % description) |
| (...skipping 185 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 863 raise error.JobError(msg) | 883 raise error.JobError(msg) |
| 864 | 884 |
| 865 | 885 |
| 866 def quit(self): | 886 def quit(self): |
| 867 # XXX: should have a better name. | 887 # XXX: should have a better name. |
| 868 self.harness.run_pause() | 888 self.harness.run_pause() |
| 869 raise error.JobContinue("more to come") | 889 raise error.JobContinue("more to come") |
| 870 | 890 |
| 871 | 891 |
| 872 def complete(self, status): | 892 def complete(self, status): |
| 873 """Clean up and exit""" | 893 """Write pending TAP reports, clean up, and exit""" |
| 894 # write out TAP reports |
| 895 if self._tap.do_tap_report: |
| 896 self._tap.write() |
| 897 self._tap._write_tap_archive() |
| 898 |
| 874 # We are about to exit 'complete' so clean up the control file. | 899 # We are about to exit 'complete' so clean up the control file. |
| 875 dest = os.path.join(self.resultdir, os.path.basename(self._state_file)) | 900 dest = os.path.join(self.resultdir, os.path.basename(self._state_file)) |
| 876 shutil.move(self._state_file, dest) | 901 shutil.move(self._state_file, dest) |
| 877 | 902 |
| 878 self.harness.run_complete() | 903 self.harness.run_complete() |
| 879 self.disable_external_logging() | 904 self.disable_external_logging() |
| 880 sys.exit(status) | 905 sys.exit(status) |
| 881 | 906 |
| 882 | 907 |
| 883 def _load_state(self): | 908 def _load_state(self): |
| (...skipping 152 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1036 except Exception, detail: | 1061 except Exception, detail: |
| 1037 # Syntax errors or other general Python exceptions coming out of | 1062 # Syntax errors or other general Python exceptions coming out of |
| 1038 # the top level of the control file itself go through here. | 1063 # the top level of the control file itself go through here. |
| 1039 raise error.UnhandledJobError(detail) | 1064 raise error.UnhandledJobError(detail) |
| 1040 | 1065 |
| 1041 # If we loaded in a mid-job state file, then we presumably | 1066 # If we loaded in a mid-job state file, then we presumably |
| 1042 # know what steps we have yet to run. | 1067 # know what steps we have yet to run. |
| 1043 if not self._is_continuation: | 1068 if not self._is_continuation: |
| 1044 if 'step_init' in global_control_vars: | 1069 if 'step_init' in global_control_vars: |
| 1045 self.next_step(global_control_vars['step_init']) | 1070 self.next_step(global_control_vars['step_init']) |
| 1071 else: |
| 1072 # if last job failed due to unexpected reboot, record it as fail |
| 1073 # so harness gets called |
| 1074 last_job = self._state.get('client', 'unexpected_reboot', None) |
| 1075 if last_job: |
| 1076 subdir, testname = last_job |
| 1077 self.record('FAIL', subdir, testname, 'unexpected reboot') |
| 1078 self.record('END FAIL', subdir, testname) |
| 1046 | 1079 |
| 1047 # Iterate through the steps. If we reboot, we'll simply | 1080 # Iterate through the steps. If we reboot, we'll simply |
| 1048 # continue iterating on the next step. | 1081 # continue iterating on the next step. |
| 1049 while len(self._state.get('client', 'steps')) > 0: | 1082 while len(self._state.get('client', 'steps')) > 0: |
| 1050 steps = self._state.get('client', 'steps') | 1083 steps = self._state.get('client', 'steps') |
| 1051 (ancestry, fn_name, args, dargs) = steps.pop(0) | 1084 (ancestry, fn_name, args, dargs) = steps.pop(0) |
| 1052 self._state.set('client', 'steps', steps) | 1085 self._state.set('client', 'steps', steps) |
| 1053 | 1086 |
| 1054 self._next_step_index = 0 | 1087 self._next_step_index = 0 |
| 1055 ret = self._create_frame(global_control_vars, ancestry, fn_name) | 1088 ret = self._create_frame(global_control_vars, ancestry, fn_name) |
| (...skipping 119 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1175 # 2) define steps, and select the first via next_step() | 1208 # 2) define steps, and select the first via next_step() |
| 1176 myjob.step_engine() | 1209 myjob.step_engine() |
| 1177 | 1210 |
| 1178 except error.JobContinue: | 1211 except error.JobContinue: |
| 1179 sys.exit(5) | 1212 sys.exit(5) |
| 1180 | 1213 |
| 1181 except error.JobComplete: | 1214 except error.JobComplete: |
| 1182 sys.exit(1) | 1215 sys.exit(1) |
| 1183 | 1216 |
| 1184 except error.JobError, instance: | 1217 except error.JobError, instance: |
| 1185 logging.error("JOB ERROR: " + instance.args[0]) | 1218 logging.error("JOB ERROR: " + str(instance)) |
| 1186 if myjob: | 1219 if myjob: |
| 1187 command = None | 1220 command = None |
| 1188 if len(instance.args) > 1: | 1221 if len(instance.args) > 1: |
| 1189 command = instance.args[1] | 1222 command = instance.args[1] |
| 1190 myjob.record('ABORT', None, command, instance.args[0]) | 1223 myjob.record('ABORT', None, command, str(instance)) |
| 1191 myjob.record('END ABORT', None, None, instance.args[0]) | 1224 myjob.record('END ABORT', None, None, str(instance)) |
| 1192 assert myjob._record_indent == 0 | 1225 assert myjob._record_indent == 0 |
| 1193 myjob.complete(1) | 1226 myjob.complete(1) |
| 1194 else: | 1227 else: |
| 1195 sys.exit(1) | 1228 sys.exit(1) |
| 1196 | 1229 |
| 1197 except Exception, e: | 1230 except Exception, e: |
| 1198 # NOTE: job._run_step_fn and job.step_engine will turn things into | 1231 # NOTE: job._run_step_fn and job.step_engine will turn things into |
| 1199 # a JobError for us. If we get here, its likely an autotest bug. | 1232 # a JobError for us. If we get here, its likely an autotest bug. |
| 1200 msg = str(e) + '\n' + traceback.format_exc() | 1233 msg = str(e) + '\n' + traceback.format_exc() |
| 1201 logging.critical("JOB ERROR (autotest bug?): " + msg) | 1234 logging.critical("JOB ERROR (autotest bug?): " + msg) |
| 1202 if myjob: | 1235 if myjob: |
| 1203 myjob.record('END ABORT', None, None, msg) | 1236 myjob.record('END ABORT', None, None, msg) |
| 1204 assert myjob._record_indent == 0 | 1237 assert myjob._record_indent == 0 |
| 1205 myjob.complete(1) | 1238 myjob.complete(1) |
| 1206 else: | 1239 else: |
| 1207 sys.exit(1) | 1240 sys.exit(1) |
| 1208 | 1241 |
| 1209 # If we get here, then we assume the job is complete and good. | 1242 # If we get here, then we assume the job is complete and good. |
| 1210 myjob.record('END GOOD', None, None) | 1243 myjob.record('END GOOD', None, None) |
| 1211 assert myjob._record_indent == 0 | 1244 assert myjob._record_indent == 0 |
| 1212 | 1245 |
| 1213 myjob.complete(0) | 1246 myjob.complete(0) |
| 1214 | 1247 |
| 1215 | 1248 |
| 1216 site_job = utils.import_site_class( | 1249 site_job = utils.import_site_class( |
| 1217 __file__, "autotest_lib.client.bin.site_job", "site_job", base_client_job) | 1250 __file__, "autotest_lib.client.bin.site_job", "site_job", base_client_job) |
| 1218 | 1251 |
| 1219 class job(site_job): | 1252 class job(site_job): |
| 1220 pass | 1253 pass |
| OLD | NEW |