blob: 742fa54911290580e91189774768cd19d2be5d63 [file] [log] [blame]
Joshua Heskethe76a0dd2014-01-16 17:57:45 +11001# Copyright 2013 Rackspace Australia
2#
3# Licensed under the Apache License, Version 2.0 (the "License"); you may
4# not use this file except in compliance with the License. You may obtain
5# a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12# License for the specific language governing permissions and limitations
13# under the License.
14
15
16import copy
17import json
18import logging
19import os
20
Joshua Hesketh62245542014-01-16 18:00:56 +110021from turbo_hipster.lib import common
Joshua Heskethe76a0dd2014-01-16 17:57:45 +110022from turbo_hipster.lib import utils
23
24
25class Task(object):
Joshua Hesketh9cd2f932014-03-05 16:49:22 +110026 """ A base object for running a job (aka Task) """
Joshua Heskethe76a0dd2014-01-16 17:57:45 +110027 log = logging.getLogger("lib.models.Task")
28
Joshua Hesketh96adb282014-03-25 16:26:45 +110029 def __init__(self, worker_server, plugin_config, job_name):
30 self.worker_server = worker_server
Joshua Heskethe76a0dd2014-01-16 17:57:45 +110031 self.plugin_config = plugin_config
32 self.job_name = job_name
Joshua Hesketh81f87ed2014-01-18 15:24:48 +110033 self._reset()
Joshua Heskethe76a0dd2014-01-16 17:57:45 +110034
Joshua Hesketh81f87ed2014-01-18 15:24:48 +110035 # Define the number of steps we will do to determine our progress.
36 self.total_steps = 0
37
38 def _reset(self):
Joshua Heskethe76a0dd2014-01-16 17:57:45 +110039 self.job = None
40 self.job_arguments = None
41 self.work_data = None
42 self.cancelled = False
Joshua Hesketh81f87ed2014-01-18 15:24:48 +110043 self.success = True
44 self.messages = []
Joshua Heskethe76a0dd2014-01-16 17:57:45 +110045 self.current_step = 0
Joshua Hesketh81f87ed2014-01-18 15:24:48 +110046
47 def start_job(self, job):
48 self._reset()
49 self.job = job
50
51 if self.job is not None:
52 try:
53 self.job_arguments = \
54 json.loads(self.job.arguments.decode('utf-8'))
55 self.log.debug("Got job from ZUUL %s" % self.job_arguments)
56
57 # Send an initial WORK_DATA and WORK_STATUS packets
58 self._send_work_data()
59
60 # Execute the job_steps
61 self.do_job_steps()
62
63 # Finally, send updated work data and completed packets
64 self._send_final_results()
65
66 except Exception as e:
67 self.log.exception('Exception handling log event.')
68 if not self.cancelled:
Joshua Hesketh6e20b162014-04-09 13:09:19 +100069 self.success = False
70 self.messages.append('Exception: %s' % e)
71 self._send_work_data()
Joshua Hesketh81f87ed2014-01-18 15:24:48 +110072 self.job.sendWorkException(str(e).encode('utf-8'))
Joshua Heskethe76a0dd2014-01-16 17:57:45 +110073
Joshua Hesketh38a17182014-03-05 14:19:38 +110074 def stop_working(self, number=None):
75 # Check the number is for this job instance (None will cancel all)
Joshua Heskethe76a0dd2014-01-16 17:57:45 +110076 # (makes it possible to run multiple workers with this task
77 # on this server)
Joshua Hesketh38a17182014-03-05 14:19:38 +110078 if number is None or number == self.job.unique:
Joshua Heskethe76a0dd2014-01-16 17:57:45 +110079 self.log.debug("We've been asked to stop by our gearman manager")
80 self.cancelled = True
81 # TODO: Work out how to kill current step
82
Joshua Heskethe76a0dd2014-01-16 17:57:45 +110083 def _get_work_data(self):
84 if self.work_data is None:
85 hostname = os.uname()[1]
86 self.work_data = dict(
87 name=self.job_name,
88 number=self.job.unique,
89 manager='turbo-hipster-manager-%s' % hostname,
90 url='http://localhost',
91 )
92 return self.work_data
93
94 def _send_work_data(self):
95 """ Send the WORK DATA in json format for job """
96 self.log.debug("Send the work data response: %s" %
97 json.dumps(self._get_work_data()))
Joshua Hesketh3cda79f2014-01-31 13:10:29 +110098 if self.success:
99 self.work_data['result'] = 'SUCCESS'
100 else:
101 self.work_data['result'] = '\n'.join(self.messages)
Joshua Heskethe76a0dd2014-01-16 17:57:45 +1100102 self.job.sendWorkData(json.dumps(self._get_work_data()))
103
Joshua Hesketh81f87ed2014-01-18 15:24:48 +1100104 def _send_final_results(self):
105 self._send_work_data()
106
Joshua Heskethb5f99b62014-01-30 16:03:19 +1100107 if self.success:
Joshua Hesketh81f87ed2014-01-18 15:24:48 +1100108 self.job.sendWorkComplete(
109 json.dumps(self._get_work_data()))
110 else:
111 self.job.sendWorkFail()
112
Joshua Heskethe76a0dd2014-01-16 17:57:45 +1100113 def _do_next_step(self):
114 """ Send a WORK_STATUS command to the gearman server.
115 This can provide a progress bar. """
116
117 # Each opportunity we should check if we need to stop
118 if self.cancelled:
119 self.work_data['result'] = "Failed: Job cancelled"
120 self.job.sendWorkStatus(self.current_step, self.total_steps)
121 self.job.sendWorkFail()
122 raise Exception('Job cancelled')
123
124 self.current_step += 1
125 self.job.sendWorkStatus(self.current_step, self.total_steps)
Joshua Hesketh91778762014-01-16 18:24:46 +1100126
127
128class ShellTask(Task):
129 log = logging.getLogger("lib.models.ShellTask")
130
Joshua Hesketh96adb282014-03-25 16:26:45 +1100131 def __init__(self, worker_server, plugin_config, job_name):
132 super(ShellTask, self).__init__(worker_server, plugin_config, job_name)
Joshua Hesketh91778762014-01-16 18:24:46 +1100133 # Define the number of steps we will do to determine our progress.
Joshua Hesketh96adb282014-03-25 16:26:45 +1100134 self.total_steps = 6
Joshua Hesketh91778762014-01-16 18:24:46 +1100135
Joshua Hesketh81f87ed2014-01-18 15:24:48 +1100136 def _reset(self):
137 super(ShellTask, self)._reset()
138 self.git_path = None
Joshua Heskethc73328c2014-01-18 16:09:54 +1100139 self.job_working_dir = None
140 self.shell_output_log = None
Joshua Hesketh91778762014-01-16 18:24:46 +1100141
Joshua Hesketh235b13d2014-01-30 15:14:04 +1100142 def do_job_steps(self):
Joshua Hesketh96adb282014-03-25 16:26:45 +1100143 self.log.info('Step 1: Prep job working dir')
Joshua Heskethc73328c2014-01-18 16:09:54 +1100144 self._prep_working_dir()
145
Joshua Hesketh96adb282014-03-25 16:26:45 +1100146 self.log.info('Step 2: Checkout updates from git')
Joshua Hesketh1f2d1a22014-01-30 15:41:21 +1100147 self._grab_patchset(self.job_arguments)
148
Joshua Hesketh96adb282014-03-25 16:26:45 +1100149 self.log.info('Step 3: Run shell script')
Joshua Hesketh81f87ed2014-01-18 15:24:48 +1100150 self._execute_script()
Joshua Hesketh91778762014-01-16 18:24:46 +1100151
Joshua Hesketh96adb282014-03-25 16:26:45 +1100152 self.log.info('Step 4: Analyse logs for errors')
Joshua Hesketh81f87ed2014-01-18 15:24:48 +1100153 self._parse_and_check_results()
Joshua Hesketh91778762014-01-16 18:24:46 +1100154
Joshua Hesketh96adb282014-03-25 16:26:45 +1100155 self.log.info('Step 5: handle the results (and upload etc)')
Joshua Hesketh81f87ed2014-01-18 15:24:48 +1100156 self._handle_results()
Joshua Hesketh91778762014-01-16 18:24:46 +1100157
Joshua Hesketh96adb282014-03-25 16:26:45 +1100158 self.log.info('Step 6: Handle extra actions such as shutting down')
159 self._handle_cleanup()
160
Joshua Hesketh81f87ed2014-01-18 15:24:48 +1100161 @common.task_step
Joshua Hesketh1f2d1a22014-01-30 15:41:21 +1100162 def _prep_working_dir(self):
Joshua Hesketh99005542014-01-30 16:34:36 +1100163 self.job_identifier = utils.determine_job_identifier(
164 self.job_arguments,
165 self.plugin_config['function'],
166 self.job.unique
167 )
Joshua Hesketh1f2d1a22014-01-30 15:41:21 +1100168 self.job_working_dir = os.path.join(
Joshua Hesketh96adb282014-03-25 16:26:45 +1100169 self.worker_server.config['jobs_working_dir'],
Joshua Hesketh99005542014-01-30 16:34:36 +1100170 self.job_identifier
Joshua Hesketh1f2d1a22014-01-30 15:41:21 +1100171 )
172 self.shell_output_log = os.path.join(
173 self.job_working_dir,
174 'shell_output.log'
175 )
176
177 if not os.path.isdir(os.path.dirname(self.shell_output_log)):
178 os.makedirs(os.path.dirname(self.shell_output_log))
179
180 @common.task_step
181 def _grab_patchset(self, job_args):
Joshua Hesketh81f87ed2014-01-18 15:24:48 +1100182 """ Checkout the reference into config['git_working_dir'] """
Joshua Hesketh91778762014-01-16 18:24:46 +1100183
Joshua Hesketh81f87ed2014-01-18 15:24:48 +1100184 self.log.debug("Grab the patchset we want to test against")
Joshua Hesketh96adb282014-03-25 16:26:45 +1100185 local_path = os.path.join(self.worker_server.config['git_working_dir'],
Joshua Hesketh81f87ed2014-01-18 15:24:48 +1100186 self.job_name, job_args['ZUUL_PROJECT'])
187 if not os.path.exists(local_path):
188 os.makedirs(local_path)
Joshua Hesketh91778762014-01-16 18:24:46 +1100189
Joshua Hesketh81f87ed2014-01-18 15:24:48 +1100190 git_args = copy.deepcopy(job_args)
Joshua Hesketh91778762014-01-16 18:24:46 +1100191
Joshua Hesketh81f87ed2014-01-18 15:24:48 +1100192 cmd = os.path.join(os.path.join(os.path.dirname(__file__),
193 'gerrit-git-prep.sh'))
Joshua Hesketh96adb282014-03-25 16:26:45 +1100194 cmd += ' ' + self.worker_server.config['zuul_server']['gerrit_site']
195 cmd += ' ' + self.worker_server.config['zuul_server']['zuul_site']
Joshua Hesketh1f2d1a22014-01-30 15:41:21 +1100196 utils.execute_to_log(cmd, self.shell_output_log, env=git_args,
Joshua Hesketh81f87ed2014-01-18 15:24:48 +1100197 cwd=local_path)
198 self.git_path = local_path
199 return local_path
200
201 @common.task_step
202 def _execute_script(self):
203 # Run script
Joshua Heskethc73328c2014-01-18 16:09:54 +1100204 cmd = self.plugin_config['shell_script']
205 cmd += (
206 (' %(git_path)s %(job_working_dir)s %(unique_id)s')
207 % {
208 'git_path': self.git_path,
209 'job_working_dir': self.job_working_dir,
210 'unique_id': self.job.unique
211 }
212 )
213 self.script_return_code = utils.execute_to_log(
214 cmd,
215 self.shell_output_log
216 )
Joshua Hesketh81f87ed2014-01-18 15:24:48 +1100217
218 @common.task_step
219 def _parse_and_check_results(self):
220 if self.script_return_code > 0:
221 self.success = False
222 self.messages.append('Return code from test script was non-zero '
223 '(%d)' % self.script_return_code)
224
225 @common.task_step
226 def _handle_results(self):
Joshua Hesketh6055f312014-01-22 15:04:54 +1100227 """Upload the contents of the working dir either using the instructions
228 provided by zuul and/or our configuration"""
Joshua Hesketh221ae742014-01-22 16:09:58 +1100229
Joshua Hesketh6055f312014-01-22 15:04:54 +1100230 self.log.debug("Process the resulting files (upload/push)")
Joshua Hesketh221ae742014-01-22 16:09:58 +1100231
Joshua Hesketh96adb282014-03-25 16:26:45 +1100232 if 'publish_logs' in self.worker_server.config:
233 index_url = utils.push_file(
234 self.job_identifier, self.shell_output_log,
235 self.worker_server.config['publish_logs'])
Joshua Hesketh221ae742014-01-22 16:09:58 +1100236 self.log.debug("Index URL found at %s" % index_url)
237 self.work_data['url'] = index_url
238
239 if 'ZUUL_EXTRA_SWIFT_URL' in self.job_arguments:
240 # Upload to zuul's url as instructed
241 utils.zuul_swift_upload(self.job_working_dir, self.job_arguments)
Joshua Hesketh99005542014-01-30 16:34:36 +1100242 self.work_data['url'] = self.job_identifier
Joshua Hesketh96adb282014-03-25 16:26:45 +1100243
244 @common.task_step
245 def _handle_cleanup(self):
246 """Handle and cleanup functions. Shutdown if requested to so that no
247 further jobs are ran if the environment is dirty."""
248 if ('shutdown-th' in self.plugin_config and
249 self.plugin_config['shutdown-th']):
250 self.worker_server.shutdown_gracefully()