| # Copyright (c) 2013 The Chromium Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import datetime |
| import json |
| |
| import webapp2 |
| from google.appengine.ext import ndb |
| |
| |
| DEFAULT_NAMESPACE = 'default' |
| DEFAULT_JOBS_TO_SERVE = 20 |
| |
| |
| class Job(ndb.Model): |
| """Represents a single build job description. |
| |
| Attributes: |
| description: A JSON blob representing the job itself. |
| created: A timestamp for when this job was posted. |
| last_served: A timestamp for the last time this job was served, usually |
| to a polling buildbot. Default is epoch 0, so new jobs are "old". |
| taken: A boolean signalling that this job has been successfully picked |
| up by a poller and can be dropped. |
| """ |
| description = ndb.JsonProperty() |
| last_served = ndb.DateTimeProperty( |
| default=datetime.datetime.utcfromtimestamp(0)) |
| taken = ndb.BooleanProperty(default=False) |
| |
| |
| class MainHandler(webapp2.RequestHandler): |
| |
| def get(self): |
| self.response.write(""" |
| <html> |
| <body> |
| <form action="/default/push" method="post"> |
| <div><textarea name="job" rows="3" cols="60"></textarea></div> |
| <div><input type="submit" value="Add Job"></div> |
| </form> |
| <form action="/default/accept" method="post"> |
| <div><textarea name="job" rows="1" cols="60"></textarea></div> |
| <div><input type="submit" value="Accept Job"></div> |
| </form> |
| </body> |
| </html> |
| """) |
| |
| |
| class PushHandler(webapp2.RequestHandler): |
| |
| def post(self, project): |
| job = Job(description=self.request.get('job'), namespace=project) |
| job.put() |
| |
| |
| class PullHandler(webapp2.RequestHandler): |
| |
| def post(self, project): |
| # Get the jobs we'd like to serve. |
| time_threshold = datetime.datetime.utcnow() - datetime.timedelta(seconds=30) |
| query = Job.query(namespace=project) |
| query = query.filter(Job.last_served < time_threshold) |
| query = query.filter(Job.taken == False) |
| query = query.order(Job.last_served) |
| jobs = query.fetch(DEFAULT_JOBS_TO_SERVE) |
| |
| # Mark them as served. |
| for job in jobs: |
| job.last_served = datetime.datetime.utcnow() |
| job.put() |
| |
| # Serve them. |
| result = [] |
| for job in jobs: |
| job_blob = json.loads(job.description) |
| job_blob.update({'job_key': job.key.urlsafe()}) |
| result.append(job_blob) |
| self.response.headers['Content-Type'] = 'application/json' |
| self.response.write(json.dumps(result)) |
| |
| |
| class PeekHandler(webapp2.RequestHandler): |
| |
| def get(self, project, job): |
| # Get the jobs we'd like to serve. |
| if job: |
| job_key = ndb.Key(urlsafe=job, namespace=project) |
| job = job_key.get() |
| jobs = [job] |
| else: |
| time_threshold = (datetime.datetime.utcnow() - |
| datetime.timedelta(seconds=30)) |
| query = Job.query(namespace=project) |
| query = query.filter(Job.last_served < time_threshold) |
| query = query.filter(Job.taken == False) |
| query = query.order(Job.last_served) |
| jobs = query.fetch(DEFAULT_JOBS_TO_SERVE) |
| |
| # Serve them. |
| result = [] |
| for job in jobs: |
| job_blob = json.loads(job.description) |
| job_blob.update({'job_key': job.key.urlsafe()}) |
| result.append(job_blob) |
| self.response.headers['Content-Type'] = 'application/json' |
| self.response.write(json.dumps(result)) |
| |
| |
| class AcceptHandler(webapp2.RequestHandler): |
| |
| def post(self, project, job): |
| job_key = ndb.Key(urlsafe=job, namespace=project) |
| job = job_key.get() |
| if job.taken: |
| # This job has been previously accepted by someone else. |
| self.response.set_status(409) |
| job.taken = True |
| job.put() |
| |
| |
| app = webapp2.WSGIApplication([ |
| ('/', MainHandler), |
| ('/(.*)/push', PushHandler), |
| ('/(.*)/pull', PullHandler), |
| ('/(.*)/peek/?(.*)', PeekHandler), |
| ('/(.*)/accept/(.*)', AcceptHandler), |
| ]) |