clustermanager.py
162 lines
| 5.6 KiB
| text/x-python
|
PythonLexer
MinRK
|
r18118 | """Manage IPython.parallel clusters in the notebook.""" | ||
Brian Granger
|
r6191 | |||
MinRK
|
r18118 | # Copyright (c) IPython Development Team. | ||
# Distributed under the terms of the Modified BSD License. | ||||
Brian Granger
|
r6191 | |||
from tornado import web | ||||
from IPython.config.configurable import LoggingConfigurable | ||||
MinRK
|
r18119 | from IPython.utils.traitlets import Dict, Instance, Float | ||
Brian Granger
|
r6199 | from IPython.core.profileapp import list_profiles_in | ||
from IPython.core.profiledir import ProfileDir | ||||
Thomas Kluyver
|
r13447 | from IPython.utils import py3compat | ||
Brian Granger
|
r6199 | from IPython.utils.path import get_ipython_dir | ||
Brian Granger
|
r6191 | |||
class ClusterManager(LoggingConfigurable): | ||||
profiles = Dict() | ||||
MinRK
|
r18119 | delay = Float(1., config=True, | ||
Brian Granger
|
r6199 | help="delay (in s) between starting the controller and the engines") | ||
loop = Instance('zmq.eventloop.ioloop.IOLoop') | ||||
def _loop_default(self): | ||||
from zmq.eventloop.ioloop import IOLoop | ||||
return IOLoop.instance() | ||||
def build_launchers(self, profile_dir): | ||||
MinRK
|
r17052 | from IPython.parallel.apps.ipclusterapp import IPClusterStart | ||
class DummyIPClusterStart(IPClusterStart): | ||||
"""Dummy subclass to skip init steps that conflict with global app. | ||||
Instantiating and initializing this class should result in fully configured | ||||
launchers, but no other side effects or state. | ||||
""" | ||||
def init_signal(self): | ||||
pass | ||||
def reinit_logging(self): | ||||
pass | ||||
MinRK
|
r6202 | starter = DummyIPClusterStart(log=self.log) | ||
starter.initialize(['--profile-dir', profile_dir]) | ||||
cl = starter.controller_launcher | ||||
esl = starter.engine_launcher | ||||
n = starter.n | ||||
Brian Granger
|
r6199 | return cl, esl, n | ||
def get_profile_dir(self, name, path): | ||||
p = ProfileDir.find_profile_dir_by_name(path,name=name) | ||||
return p.location | ||||
def update_profiles(self): | ||||
Brian Granger
|
r6191 | """List all profiles in the ipython_dir and cwd. | ||
""" | ||||
MinRK
|
r18118 | |||
stale = set(self.profiles) | ||||
Thomas Kluyver
|
r13447 | for path in [get_ipython_dir(), py3compat.getcwd()]: | ||
Brian Granger
|
r6199 | for profile in list_profiles_in(path): | ||
MinRK
|
r18118 | if profile in stale: | ||
stale.remove(profile) | ||||
Brian Granger
|
r6199 | pd = self.get_profile_dir(profile, path) | ||
if profile not in self.profiles: | ||||
MinRK
|
r18118 | self.log.debug("Adding cluster profile '%s'", profile) | ||
Brian Granger
|
r6199 | self.profiles[profile] = { | ||
'profile': profile, | ||||
'profile_dir': pd, | ||||
'status': 'stopped' | ||||
} | ||||
MinRK
|
r18118 | for profile in stale: | ||
# remove profiles that no longer exist | ||||
self.log.debug("Profile '%s' no longer exists", profile) | ||||
self.profiles.pop(stale) | ||||
Brian Granger
|
r6191 | |||
def list_profiles(self): | ||||
Brian Granger
|
r6199 | self.update_profiles() | ||
MinRK
|
r11149 | # sorted list, but ensure that 'default' always comes first | ||
default_first = lambda name: name if name != 'default' else '' | ||||
result = [self.profile_info(p) for p in sorted(self.profiles, key=default_first)] | ||||
Brian Granger
|
r6191 | return result | ||
Brian Granger
|
r6199 | def check_profile(self, profile): | ||
if profile not in self.profiles: | ||||
raise web.HTTPError(404, u'profile not found') | ||||
Brian Granger
|
r6191 | |||
def profile_info(self, profile): | ||||
Brian Granger
|
r6199 | self.check_profile(profile) | ||
result = {} | ||||
Brian Granger
|
r6191 | data = self.profiles.get(profile) | ||
Brian Granger
|
r6199 | result['profile'] = profile | ||
result['profile_dir'] = data['profile_dir'] | ||||
result['status'] = data['status'] | ||||
if 'n' in data: | ||||
Brian Granger
|
r6191 | result['n'] = data['n'] | ||
return result | ||||
Brian Granger
|
r6199 | def start_cluster(self, profile, n=None): | ||
Brian Granger
|
r6191 | """Start a cluster for a given profile.""" | ||
Brian Granger
|
r6199 | self.check_profile(profile) | ||
data = self.profiles[profile] | ||||
if data['status'] == 'running': | ||||
Brian Granger
|
r6191 | raise web.HTTPError(409, u'cluster already running') | ||
Brian Granger
|
r6199 | cl, esl, default_n = self.build_launchers(data['profile_dir']) | ||
n = n if n is not None else default_n | ||||
def clean_data(): | ||||
data.pop('controller_launcher',None) | ||||
data.pop('engine_set_launcher',None) | ||||
data.pop('n',None) | ||||
data['status'] = 'stopped' | ||||
def engines_stopped(r): | ||||
self.log.debug('Engines stopped') | ||||
if cl.running: | ||||
cl.stop() | ||||
clean_data() | ||||
esl.on_stop(engines_stopped) | ||||
def controller_stopped(r): | ||||
self.log.debug('Controller stopped') | ||||
if esl.running: | ||||
esl.stop() | ||||
clean_data() | ||||
cl.on_stop(controller_stopped) | ||||
MinRK
|
r18119 | loop = self.loop | ||
def start(): | ||||
"""start the controller, then the engines after a delay""" | ||||
cl.start() | ||||
loop.add_timeout(self.loop.time() + self.delay, lambda : esl.start(n)) | ||||
self.loop.add_callback(start) | ||||
Brian Granger
|
r6199 | |||
self.log.debug('Cluster started') | ||||
data['controller_launcher'] = cl | ||||
data['engine_set_launcher'] = esl | ||||
data['n'] = n | ||||
data['status'] = 'running' | ||||
Brian Granger
|
r6197 | return self.profile_info(profile) | ||
Brian Granger
|
r6191 | |||
def stop_cluster(self, profile): | ||||
"""Stop a cluster for a given profile.""" | ||||
Brian Granger
|
r6199 | self.check_profile(profile) | ||
data = self.profiles[profile] | ||||
if data['status'] == 'stopped': | ||||
Brian Granger
|
r6191 | raise web.HTTPError(409, u'cluster not running') | ||
Brian Granger
|
r6199 | data = self.profiles[profile] | ||
cl = data['controller_launcher'] | ||||
esl = data['engine_set_launcher'] | ||||
if cl.running: | ||||
cl.stop() | ||||
if esl.running: | ||||
esl.stop() | ||||
# Return a temp info dict, the real one is updated in the on_stop | ||||
# logic above. | ||||
result = { | ||||
'profile': data['profile'], | ||||
'profile_dir': data['profile_dir'], | ||||
'status': 'stopped' | ||||
} | ||||
return result | ||||
Brian Granger
|
r6191 | |||
def stop_all_clusters(self): | ||||
Brian Granger
|
r6199 | for p in self.profiles.keys(): | ||
MinRK
|
r6270 | self.stop_cluster(p) | ||