def initialize(): global vtkpython global weblauncher # Get the module config. config = tangelo.plugin_config() # Raise an error if there's no vtkpython executable. vtkpython = config.get("vtkpython", None) if not vtkpython: msg = "No 'vtkpython' option specified in configuration plugin" tangelo.log_warning("VTKWEB", "[initialization] fatal error: %s" % (msg)) # Construct a run() function that will mask the restful API and just # inform the caller about the configuration problem. def run(): tangelo.http_status(400, "Bad Configuration") return {"error": msg} sys.modules[__name__].__dict__["run"] = run return vtkpython = tangelo.util.expandpath(vtkpython) tangelo.log("VTKWEB", "[initialization] Using vtkpython executable %s" % (vtkpython)) # Use the "web launcher" included with the plugin. weblauncher = os.path.realpath("%s/../include/vtkweb-launcher.py" % (os.path.dirname(__file__))) # Initialize a table of VTKWeb processes. if tangelo.plugin_store().get("processes") is None: tangelo.plugin_store()["processes"] = {} # Check to see if a reactor is running already. if twisted.internet.reactor.running: threads = [t for t in threading.enumerate() if t.name == "tangelo-vtkweb-plugin"] if len(threads) > 0: tangelo.log_warning( "VTKWEB", "[initialization] A reactor started by a previous loading of this plugin is already running" ) else: tangelo.log_warning( "VTKWEB", "[initialization] A reactor started by someone other than this plugin is already running" ) else: # Start the Twisted reactor, but in a separate thread so it doesn't # block the CherryPy main loop. Mark the thread as "daemon" so that # when Tangelo's main thread exits, the reactor thread will be killed # immediately. reactor = threading.Thread( target=twisted.internet.reactor.run, kwargs={"installSignalHandlers": False}, name="tangelo-vtkweb-plugin" ) reactor.daemon = True reactor.start() tangelo.log_info("VTKWEB", "[initialization] Starting Twisted reactor")
def initialize(): global vtkpython global weblauncher # Get the module config. config = tangelo.plugin_config() # Raise an error if there's no vtkpython executable. vtkpython = config.get("vtkpython", None) if not vtkpython: msg = "No 'vtkpython' option specified in configuration plugin" tangelo.log_warning("VTKWEB", "[initialization] fatal error: %s" % (msg)) # Construct a run() function that will mask the restful API and just # inform the caller about the configuration problem. def run(): tangelo.http_status(400, "Bad Configuration") return {"error": msg} sys.modules[__name__].__dict__["run"] = run return vtkpython = tangelo.util.expandpath(vtkpython) tangelo.log("VTKWEB", "[initialization] Using vtkpython executable %s" % (vtkpython)) # Use the "web launcher" included with the plugin. weblauncher = os.path.realpath("%s/../include/vtkweb-launcher.py" % (os.path.dirname(__file__))) # Initialize a table of VTKWeb processes. if tangelo.plugin_store().get("processes") is None: tangelo.plugin_store()["processes"] = {} # Check to see if a reactor is running already. if twisted.internet.reactor.running: threads = [t for t in threading.enumerate() if t.name == "tangelo-vtkweb-plugin"] if len(threads) > 0: tangelo.log_warning("VTKWEB", "[initialization] A reactor started by a previous loading of this plugin is already running") else: tangelo.log_warning("VTKWEB", "[initialization] A reactor started by someone other than this plugin is already running") else: # Start the Twisted reactor, but in a separate thread so it doesn't # block the CherryPy main loop. Mark the thread as "daemon" so that # when Tangelo's main thread exits, the reactor thread will be killed # immediately. reactor = threading.Thread( target=twisted.internet.reactor.run, kwargs={"installSignalHandlers": False}, name="tangelo-vtkweb-plugin" ) reactor.daemon = True reactor.start() tangelo.log_info("VTKWEB", "[initialization] Starting Twisted reactor")
def get(key=None): processes = tangelo.plugin_store()["processes"] # If no key was supplied, return list of running processes. if key is None: return processes.keys() # Error for bad key. if key not in processes: tangelo.http_status(400, "No Such Process Key") return {"error": "Requested key not in process table"} # Retrieve the process entry. rec = processes[key] response = {"status": "complete", "process": "running", "port": rec["port"], "stdout": rec["stdout"].readlines(), "stderr": rec["stderr"].readlines()} # Check the status of the process. returncode = rec["process"].poll() if returncode is not None: # Since the process has ended, delete the process object. del processes[key] # Fill out the report response. response["process"] = "terminated" response["returncode"] = returncode return response
def delete(key=None): # TODO(choudhury): shut down a vtkweb process by key after a given timeout. processes = tangelo.plugin_store()["processes"] if key is None: tangelo.http_status(400, "Required Argument Missing") return {"error": "'key' argument is required"} # Check for the key in the process table. if key not in processes: tangelo.http_status(400, "Key Not Found") return {"error": "Key %s not in process table" % (key)} # Terminate the process. tangelo.log_info("VTKWEB", "Shutting down process %s" % (key)) proc = processes[key] proc["process"].terminate() proc["process"].wait() tangelo.log_info("VTKWEB", "Process terminated") # Remove the process entry from the table. del processes[key] return {"key": key}
def post(*pargs, **query): args = query.get("args", "") timeout = float(query.get("timeout", 0)) processes = tangelo.plugin_store()["processes"] if len(pargs) == 0: tangelo.http_status(400, "Required Argument Missing") return {"error": "No program path was specified"} program_url = "/" + "/".join(pargs) content = analyze_url(program_url).content if content is None or content.type != Content.File: tangelo.http_status(404, "Not Found") return {"error": "Could not find a script at %s" % (program_url)} elif content.path is None: tangelo.http_status(403, "Restricted") return {"error": "The script at %s is access-restricted"} program = content.path # Check the user arguments. userargs = args.split() if "--port" in userargs: tangelo.http_status(400, "Illegal Argument") return {"error": "You may not specify '--port' among the arguments passed in 'args'"} # Obtain an available port. port = tangelo.util.get_free_port() # Generate a unique key. key = tangelo.util.generate_key(processes.keys()) # Detect http vs. https scheme = "ws" ssl_key = cherrypy.config.get("server.ssl_private_key") ssl_cert = cherrypy.config.get("server.ssl_certificate") # Generate command line. cmdline = [vtkpython, weblauncher, program, "--port", str(port)] + userargs if ssl_key and ssl_cert: scheme = "wss" cmdline.extend(["--sslKey", ssl_key, "--sslCert", ssl_cert]) # Launch the requested process. tangelo.log_info("VTKWEB", "Starting process: %s" % (" ".join(cmdline))) try: process = subprocess.Popen(cmdline, stdout=subprocess.PIPE, stderr=subprocess.PIPE) except (OSError, IOError) as e: tangelo.log_warning("VTKWEB", "Error: could not launch VTKWeb process") return {"error": e.strerror} # Capture the new process's stdout and stderr streams in # non-blocking readers. stdout = tangelo.util.NonBlockingReader(process.stdout) stderr = tangelo.util.NonBlockingReader(process.stderr) # Read from stdout to look for the signal that the process has # started properly. class FactoryStarted: pass class Failed: pass class Timeout: pass signal = "Starting factory" if timeout <= 0: timeout = 10 sleeptime = 0.5 wait = 0 saved_lines = [] try: while True: lines = stdout.readlines() saved_lines += lines for line in lines: if line == "": # This indicates that stdout has closed without # starting the process. raise Failed() elif signal in line: # This means that the server has started. raise FactoryStarted() # If neither failure nor success occurred in the last block # of lines from stdout, either time out, or try again after # a short delay. if wait >= timeout: raise Timeout() wait += sleeptime time.sleep(sleeptime) except Timeout: tangelo.http_status(524, "Timeout") return {"error": "Process startup timed out"} except Failed: tangelo.http_status(500) return {"error": "Process did not start up properly", "stdout": saved_lines, "stderr": stderr.readlines()} except FactoryStarted: stdout.pushlines(saved_lines) # Create a websocket handler path dedicated to this process. host = "localhost" if cherrypy.server.socket_host == "0.0.0.0" else cherrypy.server.socket_host tangelo.websocket.mount(key, WebSocketRelay(host, port, key), "wamp") # Log the new process in the process table, including non-blocking # stdout and stderr readers. processes[key] = {"port": port, "process": process, "stdout": stdout, "stderr": stderr} # Form the websocket URL from the hostname/port used in the # request, and the newly generated key. url = "%s://%s/ws/%s/ws" % (scheme, cherrypy.request.base.split("//")[1], key) return {"key": key, "url": url}