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 |