blob: c81383c59a981a0aa469e6607305b22eabcfabdd [file] [log] [blame]
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
"""HTTP interface to the Thermos TaskObserver
This modules provides an HTTP server which exposes information about Thermos tasks running on a
system. To do this, it relies heavily on the Thermos TaskObserver.
"""
import socket
from twitter.common import log
from twitter.common.http import HttpServer
from .file_browser import TaskObserverFileBrowser
from .json import TaskObserverJSONBindings
from .static_assets import StaticAssets
from .templating import HttpTemplate
class BottleObserver(HttpServer, StaticAssets, TaskObserverFileBrowser, TaskObserverJSONBindings):
"""
A bottle wrapper around a Thermos TaskObserver.
"""
def __init__(self, observer):
self._observer = observer
StaticAssets.__init__(self)
TaskObserverFileBrowser.__init__(self)
TaskObserverJSONBindings.__init__(self)
HttpServer.__init__(self)
@HttpServer.route("/")
@HttpServer.view(HttpTemplate.load('index'))
def handle_index(self):
return dict(hostname=socket.gethostname())
@HttpServer.route("/main")
@HttpServer.route("/main/:type")
@HttpServer.route("/main/:type/:offset")
@HttpServer.route("/main/:type/:offset/:num")
@HttpServer.mako_view(HttpTemplate.load('main'))
def handle_main(self, type=None, offset=None, num=None):
if type not in (None, 'all', 'finished', 'active'):
HttpServer.abort(404, 'Invalid task type: %s' % type)
if offset is not None:
try:
offset = int(offset)
except ValueError:
HttpServer.abort(404, 'Invalid offset: %s' % offset)
if num is not None:
try:
num = int(num)
except ValueError:
HttpServer.abort(404, 'Invalid count: %s' % num)
return self._observer.main(type, offset, num)
@HttpServer.route("/task/:task_id")
@HttpServer.mako_view(HttpTemplate.load('task'))
def handle_task(self, task_id):
task = self.get_task(task_id)
processes = self._observer.processes([task_id])
if not processes.get(task_id, None):
HttpServer.abort(404, 'Unknown task_id: %s' % task_id)
processes = processes[task_id]
state = self._observer.state(task_id)
return dict(
task_id=task_id,
task=task,
statuses=self._observer.task_statuses(task_id),
user=task['user'],
ports=task['ports'],
processes=processes,
chroot=state.get('sandbox', ''),
launch_time=state.get('launch_time', 0),
hostname=state.get('hostname', 'localhost'),
)
def get_task(self, task_id):
task = self._observer._task(task_id)
if not task:
HttpServer.abort(404, "Failed to find task %s. Try again shortly." % task_id)
return task
@HttpServer.route("/rawtask/:task_id")
@HttpServer.mako_view(HttpTemplate.load('rawtask'))
def handle_rawtask(self, task_id):
task = self.get_task(task_id)
state = self._observer.state(task_id)
return dict(
hostname=state.get('hostname', 'localhost'),
task_id=task_id,
task_struct=task['task_struct']
)
@HttpServer.route("/process/:task_id/:process_id")
@HttpServer.mako_view(HttpTemplate.load('process'))
def handle_process(self, task_id, process_id):
all_processes = {}
current_run = self._observer.process(task_id, process_id)
if not current_run:
HttpServer.abort(404, 'Invalid task/process combination: %s/%s' % (task_id, process_id))
process = self._observer.process_from_name(task_id, process_id)
if process is None:
msg = 'Could not recover process: %s/%s' % (task_id, process_id)
log.error(msg)
HttpServer.abort(404, msg)
current_run_number = current_run['process_run']
all_processes[current_run_number] = current_run
for run in range(current_run_number):
all_processes[run] = self._observer.process(task_id, process_id, run)
template = {
'task_id': task_id,
'process': {
'name': process_id,
'status': all_processes[current_run_number]["state"],
'cmdline': process.cmdline().get()
},
}
template['process'].update(**all_processes[current_run_number].get('used', {}))
template['runs'] = all_processes
log.debug('Rendering template is: %s', template)
return template