From 79259a9c32bbf144e6564f82ab5f5280aabcac67 Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Fri, 9 Aug 2013 16:59:08 -0400 Subject: [PATCH] Get to the point where it almost starts to work eventlet spawns the scgi server which is able to create a new console session. So far, not actually hooked in any way to a backend. The plan will be to hook up read and write of ConsoleSession instance handle method. That handle will in turn be hooked into the logging/watching scheme which in turn is hooked into the likes of pyghmi.ipmi.console and/or ssh execution --- confluent/main.py | 6 +++ confluent/util.py | 16 +++++++ confluent/webapi.py | 109 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 131 insertions(+) create mode 100644 confluent/util.py create mode 100644 confluent/webapi.py diff --git a/confluent/main.py b/confluent/main.py index c2a8b439..8dbf183e 100644 --- a/confluent/main.py +++ b/confluent/main.py @@ -11,6 +11,7 @@ # Things like heartbeating and discovery # It also will optionally snoop SLP DA requests +import confluent.webapi as webapi import eventlet from eventlet.green import socket from eventlet import wsgi @@ -35,3 +36,8 @@ def _load_plugins(): def run(): _load_plugins() + httpapi = webapi.HttpApi() + httpapi.start() + while (1): + eventlet.sleep(100) + diff --git a/confluent/util.py b/confluent/util.py new file mode 100644 index 00000000..0832530f --- /dev/null +++ b/confluent/util.py @@ -0,0 +1,16 @@ +# IBM (c) 2013 + +# Various utility functions that do not neatly fit into one category or another +import base64 +import os + +def randomstring(length=20): + """Generate a random string of requested length + + :param length: The number of characters to produce, defaults to 20 + """ + chunksize = length / 4 + if (length % 4 > 0): + chunksize += 1 + strval = base64.urlsafe_b64encode(os.urandom(chunksize * 3)) + return strval[0:length-1] diff --git a/confluent/webapi.py b/confluent/webapi.py new file mode 100644 index 00000000..a971b685 --- /dev/null +++ b/confluent/webapi.py @@ -0,0 +1,109 @@ +# Copyright (C) IBM 2013 +# All rights reserved +# This SCGI server provides a http wrap to confluent api +# It additionally manages httprequest console sessions as supported by +# shillinabox javascript +import base64 +import confluent.console as console +import confluent.util as util +import eventlet +import os +import string +scgi = eventlet.import_patched('flup.server.scgi') + + +consolesessions = {} + + +def _get_query_dict(qstring): + qdict = {} + if not qstring: + return qdict + for qpair in qstring.split('&'): + qkey, qvalue = qpair.split('=') + qdict[qkey] = qvalue + return qdict + + +def _authorize_request(env): + """Grant/Deny access based on data from wsgi env + + """ + # TODO(jbjohnso): actually evaluate the request for authorization + # In theory, the x509 or http auth stuff will get translated and then + # passed on to the core authorization function in an appropriate form + # expresses return in the form of http code + # 401 if there is no known identity + # 403 if valid identity, but no access + # going to run 200 just to get going for now + return 200 + + +def _format_response(response): + + +def _pick_mimetype(env): + """Detect the http indicated mime to send back. + + Note that as it gets into the ACCEPT header honoring, it only looks for + application/json and else gives up and assumes html. This is because + browsers are too terrible. It is assumed that + XMLHttpRequest.setRequestHeader will be used by clever javascript + if the '.json' scheme doesn't cut it. + """ + # TODO(jbjohnso): will this scheme actually play nice with shellinabox? + if env['PATH_INFO'].endswith('.json'): + return 'application/json' + elif env['PATH_INFO'].endswith('.html'): + return 'text/html' + elif 'application/json' in env['HTTP_ACCEPT']: + return 'application/json' + else: + return 'text/html' + + +def _assign_consessionid(consolesession): + sessid = util.randomstring(20) + while sessid in consolesessions.keys(): + sessid = util.randomstring(20) + consolesessions[sessid] = consolesession + return sessid + +def resourcehandler(env, start_response): + """Function to handle new wsgi requests + """ + authorized = _authorize_request(env) + mimetype = _pick_mimetype(env) + print repr(env) + if authorized in (401, 403): + start_response(authorized, []) + return + querydict = _get_query_dict(env['QUERY_STRING']) + if '/console/session' in env['PATH_INFO']: + prefix, _, _ = env['PATH_INFO'].partition('/console/session') + _, _, nodename = prefix.rpartition('/') + if 'session' not in querydict.keys() or not querydict['session']: + # Request for new session + consession = console.ConsoleSession(node=nodename) + if not consession: + start_response("500 Internal Server Error", []) + return + sessid = _assign_consessionid(consession) + start_response('200 OK', [('Content-Type', 'application/json; charset=utf-8')]) + return [d+'","data":""}'] + start_response('404 Not Found', []) + return [] + +class HttpApi(object): + def start(self): + # TODO(jbjohnso): move to unix socket and explore + # either making apache deal with it + # or just supporting nginx or lighthttpd + # for now, http port access + self.server = eventlet.spawn( + scgi.WSGIServer(resourcehandler, + bindAddress=("localhost",4004)).run()) + + + +