2
0
mirror of https://github.com/xcat2/confluent.git synced 2025-01-15 12:17:47 +00:00

Actually implement enough to get some console traffic going.

This implements a protocol that should be compatible with shellinabox's
javascript client code.
This commit is contained in:
Jarrod Johnson 2013-09-12 16:54:39 -04:00
parent a359ae5063
commit 1cbc16b22e
3 changed files with 73 additions and 11 deletions

View File

@ -8,6 +8,8 @@
#we track nodes that are actively being logged, watched, or have attached
#there should be no more than one handler per node
import confluent.pluginapi as plugin
import confluent.util as util
_handled_consoles = {}
class _ConsoleHandler(object):
@ -15,16 +17,26 @@ class _ConsoleHandler(object):
self._console = plugin.handle_path("/node/%s/_console/session" % node,
"create", configmanager)
self._console.connect(self.get_console_output)
self.rcpts = []
def register_rcpt(self, callback):
self.rcpts.append(callback)
def get_console_output(self, data):
#TODO: logging, forwarding, etc
print "huzzah"
print data
for rcpt in self.rcpts:
rcpt(data)
def write(self, data):
#TODO.... take note of data coming in from audit/log perspective?
#or just let echo take care of it and then we can skip this stack
#level?
self._console.write(data)
#this represents some api view of a console handler. This handles things like
#holding the caller specific queue data, for example, when http api should be
#sending data, but there is no outstanding POST request to hold it,
# this object has the job of halding the data
# this object has the job of holding the data
class ConsoleSession(object):
"""Create a new socket to converse with node console
@ -36,10 +48,31 @@ class ConsoleSession(object):
"""
def __init__(self, node, configmanager):
self.databuffer = ""
if node not in _handled_consoles:
_handled_consoles[node] = _ConsoleHandler(node, configmanager)
pass
# TODO(jbjohnso): actually do the cool stuff
self.conshdl = _handled_consoles[node]
self.write = _handled_consoles[node].write
_handled_consoles[node].register_rcpt(self.got_data)
def got_data(self, data):
self.databuffer += data
def get_next_output(self, timeout=45):
"""Poll for next available output on this console.
Ideally purely event driven scheme is perfect. AJAX over HTTP is
at least one case where we don't have that luxury
"""
currtime = util.monotonic_time()
deadline = currtime + 45
while len(self.databuffer) == 0 and currtime < deadline:
timeo = deadline - currtime
self.conshdl.wait_for_data(timeout=timeo)
currtime = util.monotonic_time()
retval = self.databuffer
self.databuffer = ""
return retval
def handle_request(request=None, connection=None, releaseconnection=False):

View File

@ -19,14 +19,14 @@ consolesessions = {}
def _get_query_dict(qstring, reqbody, reqtype):
qdict = {}
if not qstring:
return qdict
for qpair in qstring.split('&'):
qkey, qvalue = qpair.split('=')
qdict[qkey] = qvalue
if qstring:
for qpair in qstring.split('&'):
qkey, qvalue = qpair.split('=')
qdict[qkey] = qvalue
if reqbody is not None:
if reqtype == "application/x-www-form-urlencoded":
if "application/x-www-form-urlencoded" in reqtype:
print reqbody
raise(Exception("TODO: must actually do url form encode parse here"))
return qdict
@ -120,6 +120,21 @@ def resourcehandler(env, start_response):
start_response('200 OK', [('Content-Type',
'application/json; charset=utf-8')])
return ['{"session":"%s","data":""}' % sessid]
elif 'keys' in querydict.keys():
# client wishes to push some keys into the remote console
input = ""
for idx in xrange(0, len(querydict['keys'])):
input += chr(int(querydict['keys'][idx:idx+2]))
print "taking in "+input
sessid = querydict['session']
consolesessions[sessid].write(input)
start_response('200 OK', [('Content-Type',
'application/json; charset=utf-8')])
return # client has requests to send or receive, not both...
else: #no keys, but a session, means it's hooking to receive data
outdata = consolesessions[sessid].get_next_output(timeout=45)
json = '{"session":"%s","data":"%s"}'%(querydict['session'],
outdata)
start_response('404 Not Found', [])
return ["Unrecognized directive (404)"]

View File

@ -72,6 +72,20 @@ class Console(object):
def write(self, data):
self.solconnection.send_data(data)
def wait_for_data(self, timeout=600):
"""Wait for some network event.
This is currently not guaranteed to actually have data when
return. This is supposed to be something more appropriate
than sleep(0), but only marginally so.
"""
# reason for this is that we currently nicely pass through the callback
# straight to ipmi library. To implement this accurately, easiest path
# would be to add a layer through the callback. IMO there isn't enough
# value in assuring data coming back to bother with making the stack
# taller than it has to be
console.session.Session.wait_for_rsp(timeout=timeout)
def create(nodes, element, configmanager):
if element == '_console/session':