blob: ae61a847d687dfe2cb23ac4655d7b2a1de7b656d [file] [log] [blame]
#!/usr/bin/env python2
#
# Copyright 2017 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""This module launches the cherrypy server for HeatMapPlot and sets up\
web sockets to handle the messages between the clients and the server.
This module use remote/remote_device to dump heatmap from remote device or local
device. Currently, this module device with profile sensor. The heatmap is
reported as array of x readings and y readings. Heatmap data is sent to frontend
through webscoket. Then javascript will plot the data as bar charts.
"""
from __future__ import print_function
import argparse
import logging
import os
import re
import subprocess
import threading
import cherrypy
from ws4py import configure_logger
from ws4py.messaging import TextMessage
from ws4py.server.cherrypyserver import WebSocketPlugin, WebSocketTool
from ws4py.websocket import WebSocket
from remote.remote_device import RemoteHeatMapDevice
def SimpleSystem(cmd):
"""Execute a system command."""
ret = subprocess.call(cmd, shell=True)
if ret:
logging.warning('Command (%s) failed (ret=%s).', cmd, ret)
def SimpleSystemOutput(cmd):
"""Execute a system command and get its output."""
try:
proc = subprocess.Popen(
cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
stdout, _ = proc.communicate()
except Exception as e:
logging.warning('Command (%s) failed (%s).', cmd, e)
else:
return None if proc.returncode else stdout.strip()
def IsDestinationPortEnabled(port):
"""Check if the destination port is enabled in iptables.
If port 8000 is enabled, it looks like
ACCEPT tcp -- 0.0.0.0/0 0.0.0.0/0 ctstate NEW tcp dpt:8000
"""
pattern = re.compile(r'ACCEPT\s+tcp.+\s+ctstate\s+NEW\s+tcp\s+dpt:%d' % port)
rules = SimpleSystemOutput('sudo iptables -L INPUT -n --line-number')
for rule in rules.splitlines():
if pattern.search(rule):
return True
return False
def EnableDestinationPort(port):
"""Enable the destination port for input traffic in iptables."""
if IsDestinationPortEnabled(port):
cherrypy.log('Port %d has been already enabled in iptables.' % port)
else:
cherrypy.log('Adding a rule to accept incoming connections on port %d in '
'iptables.' % port)
cmd = ('sudo iptables -A INPUT -p tcp -m conntrack --ctstate NEW '
'--dport %d -j ACCEPT' % port)
if SimpleSystem(cmd) != 0:
raise 'Failed to enable port in iptables: %d.' % port
def InterruptHandler():
"""An interrupt handler for both SIGINT and SIGTERM.
When interrupt is seen, cherrypy engine will exit.
"""
cherrypy.log('Cherrypy engine exit due to interrupt.')
cherrypy.engine.exit()
class HeatMapWSHandler(WebSocket):
"""The web socket handler for HeatMap."""
setPoleCallback = None
def opened(self):
"""This method is called when the handler is opened."""
cherrypy.log('WS handler is opened!')
def received_message(self, msg):
"""A callback for received message."""
data = msg.data.split(':', 1)
mtype = data[0].lower()
content = data[1] if len(data) == 2 else '0'
if mtype == 'setpole':
# code to set pole
pole = int(content)
cherrypy.log("Setting pole to %d" % pole)
if HeatMapWSHandler.setPoleCallback:
HeatMapWSHandler.setPoleCallback(pole)
class HeatMapRoot(object):
"""A class to handle requests about docroot."""
def __init__(self, ip, port):
self.ip = ip
self.port = port
self.scheme = 'ws'
cherrypy.log('Root address: (%s, %s)' % (ip, str(port)))
cherrypy.log('scheme: %s' % self.scheme)
@cherrypy.expose
def index(self):
"""This is the default index.html page."""
websocket_dict = {
'websocketUrl': '%s://%s:%s/ws' % (self.scheme, self.ip, self.port),
}
root_page = os.path.join(os.path.abspath(os.path.dirname(__file__)),
'heatmap.html')
with open(root_page) as f:
return f.read() % websocket_dict
@cherrypy.expose
def ws(self):
"""This handles the request to create a new web socket per client."""
cherrypy.log('A new client requesting for WS')
cherrypy.log('WS handler created: %s' % repr(cherrypy.request.ws_handler))
class HeatMapPlot(threading.Thread):
"""The server handling the Plotting of HeatMap."""
def __init__(self, server_addr, server_port, device,
is_behind_iptables_firewall=False):
self._server_addr = server_addr
self._server_port = server_port
self._device = device
super(HeatMapPlot, self).__init__(name='HeatMap thread')
self.daemon = True
self._prev_tids = []
# Allow input traffic in iptables, if the user has specified. This setting
# should be used if HeatMap is being run directly on a chromebook, but it
# requires root access, so we don't want to use it all the time.
if is_behind_iptables_firewall:
EnableDestinationPort(self._server_port)
cherrypy.config.update({
'server.socket_host': self._server_addr,
'server.socket_port': self._server_port,
})
WebSocketPlugin(cherrypy.engine).subscribe()
cherrypy.tools.websocket = WebSocketTool()
# If the cherrypy server exits for whatever reason, close the device
# for required cleanup. Otherwise, there might exist local/remote
# zombie processes.
cherrypy.engine.subscribe('exit', self._device.__del__)
cherrypy.engine.signal_handler.handlers['SIGINT'] = InterruptHandler
cherrypy.engine.signal_handler.handlers['SIGTERM'] = InterruptHandler
def run(self):
"""Start the cherrypy engine."""
root = HeatMapRoot(self._server_addr, self._server_port)
cherrypy.quickstart(
root,
'',
config={
'/': {
'tools.staticdir.root':
os.path.abspath(os.path.dirname(__file__)),
'tools.staticdir.on': True,
'tools.staticdir.dir': '',
},
'/ws': {
'tools.websocket.on': True,
'tools.websocket.handler_cls': HeatMapWSHandler,
},
}
)
def StartBridgeHeatMapEvents(self):
"""Start to read heatmap events and send it over ws."""
cherrypy.log('Start to route heatmap events')
try:
while True:
event = self._device.NextEvent()
if event:
cherrypy.engine.publish('websocket-broadcast', event.ToJson())
else:
cherrypy.log('Fail to get heatmap event, check if heatmap_tool is on\
your device.')
cherrypy.engine.exit()
except KeyboardInterrupt:
cherrypy.log('Keyboard Interrupt accepted')
cherrypy.log('HeatMapPlot is being terminated')
cherrypy.engine.exit()
def _CheckLegalUser():
"""If this program is run in chroot, it should not be run as root for\
security reason."""
if os.path.exists('/etc/cros_chroot_version') and os.getuid() == 0:
print ('You should run heatmapplot in chroot as a regular user '
'instead of as root.\n')
exit(1)
def _ParseArguments():
"""Parse the command line options."""
parser = argparse.ArgumentParser(description='HeatMap Server')
parser.add_argument('-d', '--dut_addr', default=None,
help='the address of the dut')
parser.add_argument('-p', '--server_port', default=8080, type=int,
help='the port the web server listens to (default: 8080)')
parser.add_argument('--behind_firewall', action='store_true',
help=('With this flag set, you tell webplot to add a '
'rule to iptables to allow incoming traffic to '
'the webserver. If you are running HeatMapPlot on '
'a chromebook, this is needed.'))
parser.add_argument('-s', '--server_addr', default='127.0.0.1',
help='the address the webplot http server listens to')
args = parser.parse_args()
return args
def Main():
"""The main function to launch HeatMap service."""
_CheckLegalUser()
configure_logger(level=logging.ERROR)
args = _ParseArguments()
print('\n' + '-' * 70)
cherrypy.log('dut address: %s' % args.dut_addr)
cherrypy.log('web server address: %s' % args.server_addr)
cherrypy.log('web server port: %s' % args.server_port)
print('-' * 70 + '\n\n')
if args.server_port == 80:
url = 'http://%s' % args.server_addr
else:
url = 'http://%s:%d' % (args.server_addr, args.server_port)
msg = 'Type "%s" in browser %s to see heatmap.\n'
if args.server_addr == '127.0.0.1':
which_machine = 'on the webplot server machine'
else:
which_machine = 'on any machine'
print('*' * 70)
print(msg % (url, which_machine))
print('*' * 70 + '\n\n')
# Instantiate a touch device.
addr = args.dut_addr if args.dut_addr else '127.0.0.1'
device = RemoteHeatMapDevice(addr)
# Setup the call back function to set pole to device.
HeatMapWSHandler.setPoleCallback = device.SetPole
# Instantiate a webplot server daemon and start it.
HeatMap = HeatMapPlot(args.server_addr, args.server_port, device,
is_behind_iptables_firewall=args.behind_firewall)
HeatMap.start()
HeatMap.StartBridgeHeatMapEvents()
if __name__ == '__main__':
Main()