44
44
except ImportError : # python 3
45
45
from gi .repository import GObject as gobject
46
46
import gevent
47
+ from gevent import socket
48
+ from gevent import Greenlet
47
49
48
50
DBUS_UNKNOWN_INTERFACE = 'org.freedesktop.DBus.Error.UnknownInterface'
49
51
DBUS_UNKNOWN_METHOD = 'org.freedesktop.DBus.Error.UnknownMethod'
@@ -916,6 +918,85 @@ def do_get(self):
916
918
notifier = EventNotifier (wsock , filters )
917
919
918
920
921
+ class HostConsoleHandler (RouteHandler ):
922
+ ''' Handles the /console route, for clients to be able
923
+ read/write the host serial console. The way this is
924
+ done is by exposing a websocket that's mirrored to an
925
+ abstract UNIX domain socket, which is the source for
926
+ the console data. '''
927
+
928
+ verbs = ['GET' ]
929
+ # Naming the route console0, because the numbering will help
930
+ # on multi-bmc/multi-host systems.
931
+ rules = ['/console0' ]
932
+
933
+ def __init__ (self , app , bus ):
934
+ super (HostConsoleHandler , self ).__init__ (
935
+ app , bus , self .verbs , self .rules )
936
+
937
+ def find (self , ** kw ):
938
+ pass
939
+
940
+ def setup (self , ** kw ):
941
+ pass
942
+
943
+ def read_wsock (self , wsock , sock ):
944
+ while True :
945
+ try :
946
+ incoming = wsock .receive ()
947
+ if incoming :
948
+ # Read websocket, write to UNIX socket
949
+ sock .send (incoming )
950
+ except Exception as e :
951
+ sock .close ()
952
+ return
953
+
954
+ def read_sock (self , sock , wsock ):
955
+ max_sock_read_len = 4096
956
+ while True :
957
+ try :
958
+ outgoing = sock .recv (max_sock_read_len )
959
+ if outgoing :
960
+ # Read UNIX socket, write to websocket
961
+ wsock .send (outgoing )
962
+ except Exception as e :
963
+ wsock .close ()
964
+ return
965
+
966
+ def send_ping (self , wsock ) :
967
+ # Most webservers close websockets after 60 seconds of
968
+ # inactivity. Make sure to send a ping before that.
969
+ timeout = 45
970
+ payload = "ping"
971
+ # the ping payload can be anything, the receiver has to just
972
+ # return the same back.
973
+ while True :
974
+ gevent .sleep (timeout )
975
+ wsock .send_frame (payload , wsock .OPCODE_PING )
976
+
977
+ def do_get (self ):
978
+ wsock = request .environ .get ('wsgi.websocket' )
979
+ if not wsock :
980
+ abort (400 , 'Expected WebSocket based request.' )
981
+
982
+ # A UNIX domain socket structure defines a 108-byte pathname. The
983
+ # server in this case, obmc-console-server, expects a 108-byte path.
984
+ socket_name = "\0 obmc-console"
985
+ trailing_bytes = "\0 " * (108 - len (socket_name ))
986
+ socket_path = socket_name + trailing_bytes
987
+ sock = socket .socket (socket .AF_UNIX , socket .SOCK_STREAM )
988
+
989
+ try :
990
+ sock .connect (socket_path )
991
+ except Exception as e :
992
+ abort (500 , str (e ))
993
+
994
+ wsock_reader = Greenlet .spawn (self .read_wsock , wsock , sock )
995
+ sock_reader = Greenlet .spawn (self .read_sock , sock , wsock )
996
+ ping_sender = Greenlet .spawn (self .send_ping , wsock )
997
+ gevent .joinall ([wsock_reader , sock_reader , ping_sender ])
998
+
999
+
919
1000
class ImagePutHandler (RouteHandler ):
920
1001
''' Handles the /upload/image/<filename> route. '''
921
1002
@@ -1386,6 +1467,7 @@ def create_handlers(self):
1386
1467
self .download_dump_get_handler = DownloadDumpHandler (self , self .bus )
1387
1468
if self .have_wsock :
1388
1469
self .event_handler = EventHandler (self , self .bus )
1470
+ self .host_console_handler = HostConsoleHandler (self , self .bus )
1389
1471
self .instance_handler = InstanceHandler (self , self .bus )
1390
1472
1391
1473
def install_handlers (self ):
@@ -1402,6 +1484,7 @@ def install_handlers(self):
1402
1484
self .download_dump_get_handler .install ()
1403
1485
if self .have_wsock :
1404
1486
self .event_handler .install ()
1487
+ self .host_console_handler .install ()
1405
1488
# this has to come last, since it matches everything
1406
1489
self .instance_handler .install ()
1407
1490
0 commit comments