##// END OF EJS Templates
The cluster applications now have a working directory option.....
Brian Granger -
Show More
@@ -1,194 +1,198 b''
1 import os
1 import os
2
2
3 c = get_config()
3 c = get_config()
4
4
5 #-----------------------------------------------------------------------------
5 #-----------------------------------------------------------------------------
6 # Select which launchers to use
6 # Select which launchers to use
7 #-----------------------------------------------------------------------------
7 #-----------------------------------------------------------------------------
8
8
9 # This allows you to control what method is used to start the controller
9 # This allows you to control what method is used to start the controller
10 # and engines. The following methods are currently supported:
10 # and engines. The following methods are currently supported:
11 # * Start as a regular process on localhost.
11 # * Start as a regular process on localhost.
12 # * Start using mpiexec.
12 # * Start using mpiexec.
13 # * Start using PBS
13 # * Start using PBS
14 # * Start using SSH (currently broken)
14 # * Start using SSH (currently broken)
15
15
16 # The selected launchers can be configured below.
16 # The selected launchers can be configured below.
17
17
18 # Options are (LocalControllerLauncher, MPIExecControllerLauncher,
18 # Options are (LocalControllerLauncher, MPIExecControllerLauncher,
19 # PBSControllerLauncher, WindowsHPCControllerLauncher)
19 # PBSControllerLauncher, WindowsHPCControllerLauncher)
20 # c.Global.controller_launcher = 'IPython.kernel.launcher.LocalControllerLauncher'
20 # c.Global.controller_launcher = 'IPython.kernel.launcher.LocalControllerLauncher'
21
21
22 # Options are (LocalEngineSetLauncher, MPIExecEngineSetLauncher,
22 # Options are (LocalEngineSetLauncher, MPIExecEngineSetLauncher,
23 # PBSEngineSetLauncher)
23 # PBSEngineSetLauncher)
24 # c.Global.engine_launcher = 'IPython.kernel.launcher.LocalEngineSetLauncher'
24 # c.Global.engine_launcher = 'IPython.kernel.launcher.LocalEngineSetLauncher'
25
25
26 #-----------------------------------------------------------------------------
26 #-----------------------------------------------------------------------------
27 # Global configuration
27 # Global configuration
28 #-----------------------------------------------------------------------------
28 #-----------------------------------------------------------------------------
29
29
30 # The default number of engine that will be started. This is overridden by
30 # The default number of engine that will be started. This is overridden by
31 # the -n command line option: "ipcluster start -n 4"
31 # the -n command line option: "ipcluster start -n 4"
32 # c.Global.n = 2
32 # c.Global.n = 2
33
33
34 # Log to a file in cluster_dir/log, otherwise just log to sys.stdout.
34 # Log to a file in cluster_dir/log, otherwise just log to sys.stdout.
35 # c.Global.log_to_file = False
35 # c.Global.log_to_file = False
36
36
37 # Remove old logs from cluster_dir/log before starting.
37 # Remove old logs from cluster_dir/log before starting.
38 # c.Global.clean_logs = True
38 # c.Global.clean_logs = True
39
39
40 # The working directory for the process. The application will use os.chdir
41 # to change to this directory before starting.
42 # c.Global.working_dir = os.getcwd()
43
40 #-----------------------------------------------------------------------------
44 #-----------------------------------------------------------------------------
41 # Controller launcher configuration
45 # Controller launcher configuration
42 #-----------------------------------------------------------------------------
46 #-----------------------------------------------------------------------------
43
47
44 # Configure how the controller is started. The configuration of the controller
48 # Configure how the controller is started. The configuration of the controller
45 # can also bet setup by editing the controller config file:
49 # can also bet setup by editing the controller config file:
46 # ipcontroller_config.py
50 # ipcontroller_config.py
47
51
48 # The command line arguments to call the controller with.
52 # The command line arguments to call the controller with.
49 # c.LocalControllerLauncher.controller_args = \
53 # c.LocalControllerLauncher.controller_args = \
50 # ['--log-to-file','--log-level', '40']
54 # ['--log-to-file','--log-level', '40']
51
55
52 # The mpiexec/mpirun command to use in started the controller.
56 # The mpiexec/mpirun command to use in started the controller.
53 # c.MPIExecControllerLauncher.mpi_cmd = ['mpiexec']
57 # c.MPIExecControllerLauncher.mpi_cmd = ['mpiexec']
54
58
55 # Additional arguments to pass to the actual mpiexec command.
59 # Additional arguments to pass to the actual mpiexec command.
56 # c.MPIExecControllerLauncher.mpi_args = []
60 # c.MPIExecControllerLauncher.mpi_args = []
57
61
58 # The command line argument to call the controller with.
62 # The command line argument to call the controller with.
59 # c.MPIExecControllerLauncher.controller_args = \
63 # c.MPIExecControllerLauncher.controller_args = \
60 # ['--log-to-file','--log-level', '40']
64 # ['--log-to-file','--log-level', '40']
61
65
62 # The command line program to use to submit a PBS job.
66 # The command line program to use to submit a PBS job.
63 # c.PBSControllerLauncher.submit_command = 'qsub'
67 # c.PBSControllerLauncher.submit_command = 'qsub'
64
68
65 # The command line program to use to delete a PBS job.
69 # The command line program to use to delete a PBS job.
66 # c.PBSControllerLauncher.delete_command = 'qdel'
70 # c.PBSControllerLauncher.delete_command = 'qdel'
67
71
68 # A regular expression that takes the output of qsub and find the job id.
72 # A regular expression that takes the output of qsub and find the job id.
69 # c.PBSControllerLauncher.job_id_regexp = '\d+'
73 # c.PBSControllerLauncher.job_id_regexp = '\d+'
70
74
71 # The batch submission script used to start the controller. This is where
75 # The batch submission script used to start the controller. This is where
72 # environment variables would be setup, etc. This string is interpolated using
76 # environment variables would be setup, etc. This string is interpolated using
73 # the Itpl module in IPython.external. Basically, you can use ${profile} for
77 # the Itpl module in IPython.external. Basically, you can use ${profile} for
74 # the controller profile or ${cluster_dir} for the cluster_dir.
78 # the controller profile or ${cluster_dir} for the cluster_dir.
75 # c.PBSControllerLauncher.batch_template = """"""
79 # c.PBSControllerLauncher.batch_template = """"""
76
80
77 # The name of the instantiated batch script that will actually be used to
81 # The name of the instantiated batch script that will actually be used to
78 # submit the job. This will be written to the cluster directory.
82 # submit the job. This will be written to the cluster directory.
79 # c.PBSControllerLauncher.batch_file_name = u'pbs_batch_script_controller'
83 # c.PBSControllerLauncher.batch_file_name = u'pbs_batch_script_controller'
80
84
81 #-----------------------------------------------------------------------------
85 #-----------------------------------------------------------------------------
82 # Windows HPC Server 2008 launcher configuration
86 # Windows HPC Server 2008 launcher configuration
83 #-----------------------------------------------------------------------------
87 #-----------------------------------------------------------------------------
84
88
85 # c.WinHPCJob.username = 'DOMAIN\\user'
89 # c.WinHPCJob.username = 'DOMAIN\\user'
86 # c.WinHPCJob.priority = 'Highest'
90 # c.WinHPCJob.priority = 'Highest'
87 # c.WinHPCJob.requested_nodes = ''
91 # c.WinHPCJob.requested_nodes = ''
88 # c.WinHPCJob.project = ''
92 # c.WinHPCJob.project = ''
89 # c.WinHPCJob.is_exclusive = False
93 # c.WinHPCJob.is_exclusive = False
90
94
91 # c.WinHPCTask.environment_variables = {}
95 # c.WinHPCTask.environment_variables = {}
92 # c.WinHPCTask.work_directory = ''
96 # c.WinHPCTask.work_directory = ''
93 # c.WinHPCTask.is_rerunnable = True
97 # c.WinHPCTask.is_rerunnable = True
94
98
95 # c.IPControllerTask.task_name = 'IPController'
99 # c.IPControllerTask.task_name = 'IPController'
96 # c.IPControllerTask.controller_cmd = [u'ipcontroller.exe']
100 # c.IPControllerTask.controller_cmd = [u'ipcontroller.exe']
97 # c.IPControllerTask.controller_args = ['--log-to-file', '--log-level', '40']
101 # c.IPControllerTask.controller_args = ['--log-to-file', '--log-level', '40']
98 # c.IPControllerTask.environment_variables = {}
102 # c.IPControllerTask.environment_variables = {}
99
103
100 # c.IPEngineTask.task_name = 'IPController'
104 # c.IPEngineTask.task_name = 'IPController'
101 # c.IPEngineTask.engine_cmd = [u'ipengine.exe']
105 # c.IPEngineTask.engine_cmd = [u'ipengine.exe']
102 # c.IPEngineTask.engine_args = ['--log-to-file', '--log-level', '40']
106 # c.IPEngineTask.engine_args = ['--log-to-file', '--log-level', '40']
103 # c.IPEngineTask.environment_variables = {}
107 # c.IPEngineTask.environment_variables = {}
104
108
105 # c.WindowsHPCLauncher.scheduler = 'HEADNODE'
109 # c.WindowsHPCLauncher.scheduler = 'HEADNODE'
106 # c.WindowsHPCLauncher.username = '\\DOMAIN\USERNAME'
110 # c.WindowsHPCLauncher.username = '\\DOMAIN\USERNAME'
107 # c.WindowsHPCLauncher.priority = 'Highest'
111 # c.WindowsHPCLauncher.priority = 'Highest'
108 # c.WindowsHPCLauncher.requested_nodes = ''
112 # c.WindowsHPCLauncher.requested_nodes = ''
109 # c.WindowsHPCLauncher.job_file_name = u'ipython_job.xml'
113 # c.WindowsHPCLauncher.job_file_name = u'ipython_job.xml'
110 # c.WindowsHPCLauncher.project = 'MyProject'
114 # c.WindowsHPCLauncher.project = 'MyProject'
111
115
112 # c.WindowsHPCControllerLauncher.scheduler = 'HEADNODE'
116 # c.WindowsHPCControllerLauncher.scheduler = 'HEADNODE'
113 # c.WindowsHPCControllerLauncher.username = '\\DOMAIN\USERNAME'
117 # c.WindowsHPCControllerLauncher.username = '\\DOMAIN\USERNAME'
114 # c.WindowsHPCControllerLauncher.priority = 'Highest'
118 # c.WindowsHPCControllerLauncher.priority = 'Highest'
115 # c.WindowsHPCControllerLauncher.requested_nodes = ''
119 # c.WindowsHPCControllerLauncher.requested_nodes = ''
116 # c.WindowsHPCControllerLauncher.job_file_name = u'ipcontroller_job.xml'
120 # c.WindowsHPCControllerLauncher.job_file_name = u'ipcontroller_job.xml'
117 # c.WindowsHPCControllerLauncher.project = 'MyProject'
121 # c.WindowsHPCControllerLauncher.project = 'MyProject'
118
122
119
123
120 #-----------------------------------------------------------------------------
124 #-----------------------------------------------------------------------------
121 # Engine launcher configuration
125 # Engine launcher configuration
122 #-----------------------------------------------------------------------------
126 #-----------------------------------------------------------------------------
123
127
124 # Command line argument passed to the engines.
128 # Command line argument passed to the engines.
125 # c.LocalEngineSetLauncher.engine_args = ['--log-to-file','--log-level', '40']
129 # c.LocalEngineSetLauncher.engine_args = ['--log-to-file','--log-level', '40']
126
130
127 # The mpiexec/mpirun command to use in started the controller.
131 # The mpiexec/mpirun command to use in started the controller.
128 # c.MPIExecEngineSetLauncher.mpi_cmd = ['mpiexec']
132 # c.MPIExecEngineSetLauncher.mpi_cmd = ['mpiexec']
129
133
130 # Additional arguments to pass to the actual mpiexec command.
134 # Additional arguments to pass to the actual mpiexec command.
131 # c.MPIExecEngineSetLauncher.mpi_args = []
135 # c.MPIExecEngineSetLauncher.mpi_args = []
132
136
133 # Command line argument passed to the engines.
137 # Command line argument passed to the engines.
134 # c.MPIExecEngineSetLauncher.engine_args = ['--log-to-file','--log-level', '40']
138 # c.MPIExecEngineSetLauncher.engine_args = ['--log-to-file','--log-level', '40']
135
139
136 # The default number of engines to start if not given elsewhere.
140 # The default number of engines to start if not given elsewhere.
137 # c.MPIExecEngineSetLauncher.n = 1
141 # c.MPIExecEngineSetLauncher.n = 1
138
142
139 # The command line program to use to submit a PBS job.
143 # The command line program to use to submit a PBS job.
140 # c.PBSEngineSetLauncher.submit_command = 'qsub'
144 # c.PBSEngineSetLauncher.submit_command = 'qsub'
141
145
142 # The command line program to use to delete a PBS job.
146 # The command line program to use to delete a PBS job.
143 # c.PBSEngineSetLauncher.delete_command = 'qdel'
147 # c.PBSEngineSetLauncher.delete_command = 'qdel'
144
148
145 # A regular expression that takes the output of qsub and find the job id.
149 # A regular expression that takes the output of qsub and find the job id.
146 # c.PBSEngineSetLauncher.job_id_regexp = '\d+'
150 # c.PBSEngineSetLauncher.job_id_regexp = '\d+'
147
151
148 # The batch submission script used to start the engines. This is where
152 # The batch submission script used to start the engines. This is where
149 # environment variables would be setup, etc. This string is interpolated using
153 # environment variables would be setup, etc. This string is interpolated using
150 # the Itpl module in IPython.external. Basically, you can use ${n} for the
154 # the Itpl module in IPython.external. Basically, you can use ${n} for the
151 # number of engine, ${profile} or the engine profile and ${cluster_dir}
155 # number of engine, ${profile} or the engine profile and ${cluster_dir}
152 # for the cluster_dir.
156 # for the cluster_dir.
153 # c.PBSEngineSetLauncher.batch_template = """"""
157 # c.PBSEngineSetLauncher.batch_template = """"""
154
158
155 # The name of the instantiated batch script that will actually be used to
159 # The name of the instantiated batch script that will actually be used to
156 # submit the job. This will be written to the cluster directory.
160 # submit the job. This will be written to the cluster directory.
157 # c.PBSEngineSetLauncher.batch_file_name = u'pbs_batch_script_engines'
161 # c.PBSEngineSetLauncher.batch_file_name = u'pbs_batch_script_engines'
158
162
159 #-----------------------------------------------------------------------------
163 #-----------------------------------------------------------------------------
160 # Base launcher configuration
164 # Base launcher configuration
161 #-----------------------------------------------------------------------------
165 #-----------------------------------------------------------------------------
162
166
163 # The various launchers are organized into an inheritance hierarchy.
167 # The various launchers are organized into an inheritance hierarchy.
164 # The configurations can also be iherited and the following attributes
168 # The configurations can also be iherited and the following attributes
165 # allow you to configure the base classes.
169 # allow you to configure the base classes.
166
170
167 # c.MPIExecLauncher.mpi_cmd = ['mpiexec']
171 # c.MPIExecLauncher.mpi_cmd = ['mpiexec']
168 # c.MPIExecLauncher.mpi_args = []
172 # c.MPIExecLauncher.mpi_args = []
169 # c.MPIExecLauncher.program = []
173 # c.MPIExecLauncher.program = []
170 # c.MPIExecLauncher.program_args = []
174 # c.MPIExecLauncher.program_args = []
171 # c.MPIExecLauncher.n = 1
175 # c.MPIExecLauncher.n = 1
172
176
173 # c.SSHLauncher.ssh_cmd = ['ssh']
177 # c.SSHLauncher.ssh_cmd = ['ssh']
174 # c.SSHLauncher.ssh_args = []
178 # c.SSHLauncher.ssh_args = []
175 # c.SSHLauncher.program = []
179 # c.SSHLauncher.program = []
176 # s.SSHLauncher.program_args = []
180 # s.SSHLauncher.program_args = []
177 # c.SSHLauncher.hostname = ''
181 # c.SSHLauncher.hostname = ''
178 # c.SSHLauncher.user = os.environ['USER']
182 # c.SSHLauncher.user = os.environ['USER']
179
183
180 # c.BatchSystemLauncher.submit_command
184 # c.BatchSystemLauncher.submit_command
181 # c.BatchSystemLauncher.delete_command
185 # c.BatchSystemLauncher.delete_command
182 # c.BatchSystemLauncher.job_id_regexp
186 # c.BatchSystemLauncher.job_id_regexp
183 # c.BatchSystemLauncher.batch_template
187 # c.BatchSystemLauncher.batch_template
184 # c.BatchSystemLauncher.batch_file_name
188 # c.BatchSystemLauncher.batch_file_name
185
189
186 # c.PBSLauncher.submit_command = 'qsub'
190 # c.PBSLauncher.submit_command = 'qsub'
187 # c.PBSLauncher.delete_command = 'qdel'
191 # c.PBSLauncher.delete_command = 'qdel'
188 # c.PBSLauncher.job_id_regexp = '\d+'
192 # c.PBSLauncher.job_id_regexp = '\d+'
189 # c.PBSLauncher.batch_template = """"""
193 # c.PBSLauncher.batch_template = """"""
190 # c.PBSLauncher.batch_file_name = u'pbs_batch_script'
194 # c.PBSLauncher.batch_file_name = u'pbs_batch_script'
191
195
192
196
193
197
194
198
@@ -1,132 +1,136 b''
1 from IPython.config.loader import Config
1 from IPython.config.loader import Config
2
2
3 c = get_config()
3 c = get_config()
4
4
5 #-----------------------------------------------------------------------------
5 #-----------------------------------------------------------------------------
6 # Global configuration
6 # Global configuration
7 #-----------------------------------------------------------------------------
7 #-----------------------------------------------------------------------------
8
8
9 # Basic Global config attributes
9 # Basic Global config attributes
10
10
11 # Start up messages are logged to stdout using the logging module.
11 # Start up messages are logged to stdout using the logging module.
12 # These all happen before the twisted reactor is started and are
12 # These all happen before the twisted reactor is started and are
13 # useful for debugging purposes. Can be (10=DEBUG,20=INFO,30=WARN,40=CRITICAL)
13 # useful for debugging purposes. Can be (10=DEBUG,20=INFO,30=WARN,40=CRITICAL)
14 # and smaller is more verbose.
14 # and smaller is more verbose.
15 # c.Global.log_level = 20
15 # c.Global.log_level = 20
16
16
17 # Log to a file in cluster_dir/log, otherwise just log to sys.stdout.
17 # Log to a file in cluster_dir/log, otherwise just log to sys.stdout.
18 # c.Global.log_to_file = False
18 # c.Global.log_to_file = False
19
19
20 # Remove old logs from cluster_dir/log before starting.
20 # Remove old logs from cluster_dir/log before starting.
21 # c.Global.clean_logs = True
21 # c.Global.clean_logs = True
22
22
23 # A list of Python statements that will be run before starting the
23 # A list of Python statements that will be run before starting the
24 # controller. This is provided because occasionally certain things need to
24 # controller. This is provided because occasionally certain things need to
25 # be imported in the controller for pickling to work.
25 # be imported in the controller for pickling to work.
26 # c.Global.import_statements = ['import math']
26 # c.Global.import_statements = ['import math']
27
27
28 # Reuse the controller's FURL files. If False, FURL files are regenerated
28 # Reuse the controller's FURL files. If False, FURL files are regenerated
29 # each time the controller is run. If True, they will be reused, *but*, you
29 # each time the controller is run. If True, they will be reused, *but*, you
30 # also must set the network ports by hand. If set, this will override the
30 # also must set the network ports by hand. If set, this will override the
31 # values set for the client and engine connections below.
31 # values set for the client and engine connections below.
32 # c.Global.reuse_furls = True
32 # c.Global.reuse_furls = True
33
33
34 # Enable SSL encryption on all connections to the controller. If set, this
34 # Enable SSL encryption on all connections to the controller. If set, this
35 # will override the values set for the client and engine connections below.
35 # will override the values set for the client and engine connections below.
36 # c.Global.secure = True
36 # c.Global.secure = True
37
37
38 # The working directory for the process. The application will use os.chdir
39 # to change to this directory before starting.
40 # c.Global.working_dir = os.getcwd()
41
38 #-----------------------------------------------------------------------------
42 #-----------------------------------------------------------------------------
39 # Configure the client services
43 # Configure the client services
40 #-----------------------------------------------------------------------------
44 #-----------------------------------------------------------------------------
41
45
42 # Basic client service config attributes
46 # Basic client service config attributes
43
47
44 # The network interface the controller will listen on for client connections.
48 # The network interface the controller will listen on for client connections.
45 # This should be an IP address or hostname of the controller's host. The empty
49 # This should be an IP address or hostname of the controller's host. The empty
46 # string means listen on all interfaces.
50 # string means listen on all interfaces.
47 # c.FCClientServiceFactory.ip = ''
51 # c.FCClientServiceFactory.ip = ''
48
52
49 # The TCP/IP port the controller will listen on for client connections. If 0
53 # The TCP/IP port the controller will listen on for client connections. If 0
50 # a random port will be used. If the controller's host has a firewall running
54 # a random port will be used. If the controller's host has a firewall running
51 # it must allow incoming traffic on this port.
55 # it must allow incoming traffic on this port.
52 # c.FCClientServiceFactory.port = 0
56 # c.FCClientServiceFactory.port = 0
53
57
54 # The client learns how to connect to the controller by looking at the
58 # The client learns how to connect to the controller by looking at the
55 # location field embedded in the FURL. If this field is empty, all network
59 # location field embedded in the FURL. If this field is empty, all network
56 # interfaces that the controller is listening on will be listed. To have the
60 # interfaces that the controller is listening on will be listed. To have the
57 # client connect on a particular interface, list it here.
61 # client connect on a particular interface, list it here.
58 # c.FCClientServiceFactory.location = ''
62 # c.FCClientServiceFactory.location = ''
59
63
60 # Use SSL encryption for the client connection.
64 # Use SSL encryption for the client connection.
61 # c.FCClientServiceFactory.secure = True
65 # c.FCClientServiceFactory.secure = True
62
66
63 # Reuse the client FURL each time the controller is started. If set, you must
67 # Reuse the client FURL each time the controller is started. If set, you must
64 # also pick a specific network port above (FCClientServiceFactory.port).
68 # also pick a specific network port above (FCClientServiceFactory.port).
65 # c.FCClientServiceFactory.reuse_furls = False
69 # c.FCClientServiceFactory.reuse_furls = False
66
70
67 #-----------------------------------------------------------------------------
71 #-----------------------------------------------------------------------------
68 # Configure the engine services
72 # Configure the engine services
69 #-----------------------------------------------------------------------------
73 #-----------------------------------------------------------------------------
70
74
71 # Basic config attributes for the engine services.
75 # Basic config attributes for the engine services.
72
76
73 # The network interface the controller will listen on for engine connections.
77 # The network interface the controller will listen on for engine connections.
74 # This should be an IP address or hostname of the controller's host. The empty
78 # This should be an IP address or hostname of the controller's host. The empty
75 # string means listen on all interfaces.
79 # string means listen on all interfaces.
76 # c.FCEngineServiceFactory.ip = ''
80 # c.FCEngineServiceFactory.ip = ''
77
81
78 # The TCP/IP port the controller will listen on for engine connections. If 0
82 # The TCP/IP port the controller will listen on for engine connections. If 0
79 # a random port will be used. If the controller's host has a firewall running
83 # a random port will be used. If the controller's host has a firewall running
80 # it must allow incoming traffic on this port.
84 # it must allow incoming traffic on this port.
81 # c.FCEngineServiceFactory.port = 0
85 # c.FCEngineServiceFactory.port = 0
82
86
83 # The engine learns how to connect to the controller by looking at the
87 # The engine learns how to connect to the controller by looking at the
84 # location field embedded in the FURL. If this field is empty, all network
88 # location field embedded in the FURL. If this field is empty, all network
85 # interfaces that the controller is listening on will be listed. To have the
89 # interfaces that the controller is listening on will be listed. To have the
86 # client connect on a particular interface, list it here.
90 # client connect on a particular interface, list it here.
87 # c.FCEngineServiceFactory.location = ''
91 # c.FCEngineServiceFactory.location = ''
88
92
89 # Use SSL encryption for the engine connection.
93 # Use SSL encryption for the engine connection.
90 # c.FCEngineServiceFactory.secure = True
94 # c.FCEngineServiceFactory.secure = True
91
95
92 # Reuse the client FURL each time the controller is started. If set, you must
96 # Reuse the client FURL each time the controller is started. If set, you must
93 # also pick a specific network port above (FCClientServiceFactory.port).
97 # also pick a specific network port above (FCClientServiceFactory.port).
94 # c.FCEngineServiceFactory.reuse_furls = False
98 # c.FCEngineServiceFactory.reuse_furls = False
95
99
96 #-----------------------------------------------------------------------------
100 #-----------------------------------------------------------------------------
97 # Developer level configuration attributes
101 # Developer level configuration attributes
98 #-----------------------------------------------------------------------------
102 #-----------------------------------------------------------------------------
99
103
100 # You shouldn't have to modify anything in this section. These attributes
104 # You shouldn't have to modify anything in this section. These attributes
101 # are more for developers who want to change the behavior of the controller
105 # are more for developers who want to change the behavior of the controller
102 # at a fundamental level.
106 # at a fundamental level.
103
107
104 # c.FCClientServiceFactory.cert_file = u'ipcontroller-client.pem'
108 # c.FCClientServiceFactory.cert_file = u'ipcontroller-client.pem'
105
109
106 # default_client_interfaces = Config()
110 # default_client_interfaces = Config()
107 # default_client_interfaces.Task.interface_chain = [
111 # default_client_interfaces.Task.interface_chain = [
108 # 'IPython.kernel.task.ITaskController',
112 # 'IPython.kernel.task.ITaskController',
109 # 'IPython.kernel.taskfc.IFCTaskController'
113 # 'IPython.kernel.taskfc.IFCTaskController'
110 # ]
114 # ]
111 #
115 #
112 # default_client_interfaces.Task.furl_file = u'ipcontroller-tc.furl'
116 # default_client_interfaces.Task.furl_file = u'ipcontroller-tc.furl'
113 #
117 #
114 # default_client_interfaces.MultiEngine.interface_chain = [
118 # default_client_interfaces.MultiEngine.interface_chain = [
115 # 'IPython.kernel.multiengine.IMultiEngine',
119 # 'IPython.kernel.multiengine.IMultiEngine',
116 # 'IPython.kernel.multienginefc.IFCSynchronousMultiEngine'
120 # 'IPython.kernel.multienginefc.IFCSynchronousMultiEngine'
117 # ]
121 # ]
118 #
122 #
119 # default_client_interfaces.MultiEngine.furl_file = u'ipcontroller-mec.furl'
123 # default_client_interfaces.MultiEngine.furl_file = u'ipcontroller-mec.furl'
120 #
124 #
121 # c.FCEngineServiceFactory.interfaces = default_client_interfaces
125 # c.FCEngineServiceFactory.interfaces = default_client_interfaces
122
126
123 # c.FCEngineServiceFactory.cert_file = u'ipcontroller-engine.pem'
127 # c.FCEngineServiceFactory.cert_file = u'ipcontroller-engine.pem'
124
128
125 # default_engine_interfaces = Config()
129 # default_engine_interfaces = Config()
126 # default_engine_interfaces.Default.interface_chain = [
130 # default_engine_interfaces.Default.interface_chain = [
127 # 'IPython.kernel.enginefc.IFCControllerBase'
131 # 'IPython.kernel.enginefc.IFCControllerBase'
128 # ]
132 # ]
129 #
133 #
130 # default_engine_interfaces.Default.furl_file = u'ipcontroller-engine.furl'
134 # default_engine_interfaces.Default.furl_file = u'ipcontroller-engine.furl'
131 #
135 #
132 # c.FCEngineServiceFactory.interfaces = default_engine_interfaces
136 # c.FCEngineServiceFactory.interfaces = default_engine_interfaces
@@ -1,86 +1,90 b''
1 c = get_config()
1 c = get_config()
2
2
3 #-----------------------------------------------------------------------------
3 #-----------------------------------------------------------------------------
4 # Global configuration
4 # Global configuration
5 #-----------------------------------------------------------------------------
5 #-----------------------------------------------------------------------------
6
6
7 # Start up messages are logged to stdout using the logging module.
7 # Start up messages are logged to stdout using the logging module.
8 # These all happen before the twisted reactor is started and are
8 # These all happen before the twisted reactor is started and are
9 # useful for debugging purposes. Can be (10=DEBUG,20=INFO,30=WARN,40=CRITICAL)
9 # useful for debugging purposes. Can be (10=DEBUG,20=INFO,30=WARN,40=CRITICAL)
10 # and smaller is more verbose.
10 # and smaller is more verbose.
11 # c.Global.log_level = 20
11 # c.Global.log_level = 20
12
12
13 # Log to a file in cluster_dir/log, otherwise just log to sys.stdout.
13 # Log to a file in cluster_dir/log, otherwise just log to sys.stdout.
14 # c.Global.log_to_file = False
14 # c.Global.log_to_file = False
15
15
16 # Remove old logs from cluster_dir/log before starting.
16 # Remove old logs from cluster_dir/log before starting.
17 # c.Global.clean_logs = True
17 # c.Global.clean_logs = True
18
18
19 # A list of strings that will be executed in the users namespace on the engine
19 # A list of strings that will be executed in the users namespace on the engine
20 # before it connects to the controller.
20 # before it connects to the controller.
21 # c.Global.exec_lines = ['import numpy']
21 # c.Global.exec_lines = ['import numpy']
22
22
23 # The engine will try to connect to the controller multiple times, to allow
23 # The engine will try to connect to the controller multiple times, to allow
24 # the controller time to startup and write its FURL file. These parameters
24 # the controller time to startup and write its FURL file. These parameters
25 # control the number of retries (connect_max_tries) and the initial delay
25 # control the number of retries (connect_max_tries) and the initial delay
26 # (connect_delay) between attemps. The actual delay between attempts gets
26 # (connect_delay) between attemps. The actual delay between attempts gets
27 # longer each time by a factor of 1.5 (delay[i] = 1.5*delay[i-1])
27 # longer each time by a factor of 1.5 (delay[i] = 1.5*delay[i-1])
28 # those attemps.
28 # those attemps.
29 # c.Global.connect_delay = 0.1
29 # c.Global.connect_delay = 0.1
30 # c.Global.connect_max_tries = 15
30 # c.Global.connect_max_tries = 15
31
31
32 # By default, the engine will look for the controller's FURL file in its own
32 # By default, the engine will look for the controller's FURL file in its own
33 # cluster directory. Sometimes, the FURL file will be elsewhere and this
33 # cluster directory. Sometimes, the FURL file will be elsewhere and this
34 # attribute can be set to the full path of the FURL file.
34 # attribute can be set to the full path of the FURL file.
35 # c.Global.furl_file = u''
35 # c.Global.furl_file = u''
36
36
37 # The working directory for the process. The application will use os.chdir
38 # to change to this directory before starting.
39 # c.Global.working_dir = os.getcwd()
40
37 #-----------------------------------------------------------------------------
41 #-----------------------------------------------------------------------------
38 # MPI configuration
42 # MPI configuration
39 #-----------------------------------------------------------------------------
43 #-----------------------------------------------------------------------------
40
44
41 # Upon starting the engine can be configured to call MPI_Init. This section
45 # Upon starting the engine can be configured to call MPI_Init. This section
42 # configures that.
46 # configures that.
43
47
44 # Select which MPI section to execute to setup MPI. The value of this
48 # Select which MPI section to execute to setup MPI. The value of this
45 # attribute must match the name of another attribute in the MPI config
49 # attribute must match the name of another attribute in the MPI config
46 # section (mpi4py, pytrilinos, etc.). This can also be set by the --mpi
50 # section (mpi4py, pytrilinos, etc.). This can also be set by the --mpi
47 # command line option.
51 # command line option.
48 # c.MPI.use = ''
52 # c.MPI.use = ''
49
53
50 # Initialize MPI using mpi4py. To use this, set c.MPI.use = 'mpi4py' to use
54 # Initialize MPI using mpi4py. To use this, set c.MPI.use = 'mpi4py' to use
51 # --mpi=mpi4py at the command line.
55 # --mpi=mpi4py at the command line.
52 # c.MPI.mpi4py = """from mpi4py import MPI as mpi
56 # c.MPI.mpi4py = """from mpi4py import MPI as mpi
53 # mpi.size = mpi.COMM_WORLD.Get_size()
57 # mpi.size = mpi.COMM_WORLD.Get_size()
54 # mpi.rank = mpi.COMM_WORLD.Get_rank()
58 # mpi.rank = mpi.COMM_WORLD.Get_rank()
55 # """
59 # """
56
60
57 # Initialize MPI using pytrilinos. To use this, set c.MPI.use = 'pytrilinos'
61 # Initialize MPI using pytrilinos. To use this, set c.MPI.use = 'pytrilinos'
58 # to use --mpi=pytrilinos at the command line.
62 # to use --mpi=pytrilinos at the command line.
59 # c.MPI.pytrilinos = """from PyTrilinos import Epetra
63 # c.MPI.pytrilinos = """from PyTrilinos import Epetra
60 # class SimpleStruct:
64 # class SimpleStruct:
61 # pass
65 # pass
62 # mpi = SimpleStruct()
66 # mpi = SimpleStruct()
63 # mpi.rank = 0
67 # mpi.rank = 0
64 # mpi.size = 0
68 # mpi.size = 0
65 # """
69 # """
66
70
67 #-----------------------------------------------------------------------------
71 #-----------------------------------------------------------------------------
68 # Developer level configuration attributes
72 # Developer level configuration attributes
69 #-----------------------------------------------------------------------------
73 #-----------------------------------------------------------------------------
70
74
71 # You shouldn't have to modify anything in this section. These attributes
75 # You shouldn't have to modify anything in this section. These attributes
72 # are more for developers who want to change the behavior of the controller
76 # are more for developers who want to change the behavior of the controller
73 # at a fundamental level.
77 # at a fundamental level.
74
78
75 # You should not have to change these attributes.
79 # You should not have to change these attributes.
76
80
77 # c.Global.shell_class = 'IPython.kernel.core.interpreter.Interpreter'
81 # c.Global.shell_class = 'IPython.kernel.core.interpreter.Interpreter'
78
82
79 # c.Global.furl_file_name = u'ipcontroller-engine.furl'
83 # c.Global.furl_file_name = u'ipcontroller-engine.furl'
80
84
81
85
82
86
83
87
84
88
85
89
86
90
@@ -1,464 +1,481 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 """
3 """
4 The IPython cluster directory
4 The IPython cluster directory
5 """
5 """
6
6
7 #-----------------------------------------------------------------------------
7 #-----------------------------------------------------------------------------
8 # Copyright (C) 2008-2009 The IPython Development Team
8 # Copyright (C) 2008-2009 The IPython Development Team
9 #
9 #
10 # Distributed under the terms of the BSD License. The full license is in
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
11 # the file COPYING, distributed as part of this software.
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13
13
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 # Imports
15 # Imports
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17
17
18 from __future__ import with_statement
18 from __future__ import with_statement
19
19
20 import os
20 import os
21 import shutil
21 import shutil
22 import sys
22 import sys
23
23
24 from twisted.python import log
24 from twisted.python import log
25
25
26 from IPython.core import release
26 from IPython.core import release
27 from IPython.config.loader import PyFileConfigLoader
27 from IPython.config.loader import PyFileConfigLoader
28 from IPython.core.application import Application
28 from IPython.core.application import Application
29 from IPython.core.component import Component
29 from IPython.core.component import Component
30 from IPython.config.loader import ArgParseConfigLoader, NoConfigDefault
30 from IPython.config.loader import ArgParseConfigLoader, NoConfigDefault
31 from IPython.utils.traitlets import Unicode, Bool
31 from IPython.utils.traitlets import Unicode, Bool
32 from IPython.utils import genutils
32 from IPython.utils import genutils
33
33
34 #-----------------------------------------------------------------------------
34 #-----------------------------------------------------------------------------
35 # Imports
35 # Imports
36 #-----------------------------------------------------------------------------
36 #-----------------------------------------------------------------------------
37
37
38
38
39 class ClusterDirError(Exception):
39 class ClusterDirError(Exception):
40 pass
40 pass
41
41
42
42
43 class PIDFileError(Exception):
43 class PIDFileError(Exception):
44 pass
44 pass
45
45
46
46
47 class ClusterDir(Component):
47 class ClusterDir(Component):
48 """An object to manage the cluster directory and its resources.
48 """An object to manage the cluster directory and its resources.
49
49
50 The cluster directory is used by :command:`ipcontroller`,
50 The cluster directory is used by :command:`ipcontroller`,
51 :command:`ipcontroller` and :command:`ipcontroller` to manage the
51 :command:`ipcontroller` and :command:`ipcontroller` to manage the
52 configuration, logging and security of these applications.
52 configuration, logging and security of these applications.
53
53
54 This object knows how to find, create and manage these directories. This
54 This object knows how to find, create and manage these directories. This
55 should be used by any code that want's to handle cluster directories.
55 should be used by any code that want's to handle cluster directories.
56 """
56 """
57
57
58 security_dir_name = Unicode('security')
58 security_dir_name = Unicode('security')
59 log_dir_name = Unicode('log')
59 log_dir_name = Unicode('log')
60 pid_dir_name = Unicode('pid')
60 pid_dir_name = Unicode('pid')
61 security_dir = Unicode(u'')
61 security_dir = Unicode(u'')
62 log_dir = Unicode(u'')
62 log_dir = Unicode(u'')
63 pid_dir = Unicode(u'')
63 pid_dir = Unicode(u'')
64 location = Unicode(u'')
64 location = Unicode(u'')
65
65
66 def __init__(self, location):
66 def __init__(self, location):
67 super(ClusterDir, self).__init__(None)
67 super(ClusterDir, self).__init__(None)
68 self.location = location
68 self.location = location
69
69
70 def _location_changed(self, name, old, new):
70 def _location_changed(self, name, old, new):
71 if not os.path.isdir(new):
71 if not os.path.isdir(new):
72 os.makedirs(new, mode=0777)
72 os.makedirs(new, mode=0777)
73 else:
73 else:
74 os.chmod(new, 0777)
74 os.chmod(new, 0777)
75 self.security_dir = os.path.join(new, self.security_dir_name)
75 self.security_dir = os.path.join(new, self.security_dir_name)
76 self.log_dir = os.path.join(new, self.log_dir_name)
76 self.log_dir = os.path.join(new, self.log_dir_name)
77 self.pid_dir = os.path.join(new, self.pid_dir_name)
77 self.pid_dir = os.path.join(new, self.pid_dir_name)
78 self.check_dirs()
78 self.check_dirs()
79
79
80 def _log_dir_changed(self, name, old, new):
80 def _log_dir_changed(self, name, old, new):
81 self.check_log_dir()
81 self.check_log_dir()
82
82
83 def check_log_dir(self):
83 def check_log_dir(self):
84 if not os.path.isdir(self.log_dir):
84 if not os.path.isdir(self.log_dir):
85 os.mkdir(self.log_dir, 0777)
85 os.mkdir(self.log_dir, 0777)
86 else:
86 else:
87 os.chmod(self.log_dir, 0777)
87 os.chmod(self.log_dir, 0777)
88
88
89 def _security_dir_changed(self, name, old, new):
89 def _security_dir_changed(self, name, old, new):
90 self.check_security_dir()
90 self.check_security_dir()
91
91
92 def check_security_dir(self):
92 def check_security_dir(self):
93 if not os.path.isdir(self.security_dir):
93 if not os.path.isdir(self.security_dir):
94 os.mkdir(self.security_dir, 0700)
94 os.mkdir(self.security_dir, 0700)
95 else:
95 else:
96 os.chmod(self.security_dir, 0700)
96 os.chmod(self.security_dir, 0700)
97
97
98 def _pid_dir_changed(self, name, old, new):
98 def _pid_dir_changed(self, name, old, new):
99 self.check_pid_dir()
99 self.check_pid_dir()
100
100
101 def check_pid_dir(self):
101 def check_pid_dir(self):
102 if not os.path.isdir(self.pid_dir):
102 if not os.path.isdir(self.pid_dir):
103 os.mkdir(self.pid_dir, 0700)
103 os.mkdir(self.pid_dir, 0700)
104 else:
104 else:
105 os.chmod(self.pid_dir, 0700)
105 os.chmod(self.pid_dir, 0700)
106
106
107 def check_dirs(self):
107 def check_dirs(self):
108 self.check_security_dir()
108 self.check_security_dir()
109 self.check_log_dir()
109 self.check_log_dir()
110 self.check_pid_dir()
110 self.check_pid_dir()
111
111
112 def load_config_file(self, filename):
112 def load_config_file(self, filename):
113 """Load a config file from the top level of the cluster dir.
113 """Load a config file from the top level of the cluster dir.
114
114
115 Parameters
115 Parameters
116 ----------
116 ----------
117 filename : unicode or str
117 filename : unicode or str
118 The filename only of the config file that must be located in
118 The filename only of the config file that must be located in
119 the top-level of the cluster directory.
119 the top-level of the cluster directory.
120 """
120 """
121 loader = PyFileConfigLoader(filename, self.location)
121 loader = PyFileConfigLoader(filename, self.location)
122 return loader.load_config()
122 return loader.load_config()
123
123
124 def copy_config_file(self, config_file, path=None, overwrite=False):
124 def copy_config_file(self, config_file, path=None, overwrite=False):
125 """Copy a default config file into the active cluster directory.
125 """Copy a default config file into the active cluster directory.
126
126
127 Default configuration files are kept in :mod:`IPython.config.default`.
127 Default configuration files are kept in :mod:`IPython.config.default`.
128 This function moves these from that location to the working cluster
128 This function moves these from that location to the working cluster
129 directory.
129 directory.
130 """
130 """
131 if path is None:
131 if path is None:
132 import IPython.config.default
132 import IPython.config.default
133 path = IPython.config.default.__file__.split(os.path.sep)[:-1]
133 path = IPython.config.default.__file__.split(os.path.sep)[:-1]
134 path = os.path.sep.join(path)
134 path = os.path.sep.join(path)
135 src = os.path.join(path, config_file)
135 src = os.path.join(path, config_file)
136 dst = os.path.join(self.location, config_file)
136 dst = os.path.join(self.location, config_file)
137 if not os.path.isfile(dst) or overwrite:
137 if not os.path.isfile(dst) or overwrite:
138 shutil.copy(src, dst)
138 shutil.copy(src, dst)
139
139
140 def copy_all_config_files(self, path=None, overwrite=False):
140 def copy_all_config_files(self, path=None, overwrite=False):
141 """Copy all config files into the active cluster directory."""
141 """Copy all config files into the active cluster directory."""
142 for f in [u'ipcontroller_config.py', u'ipengine_config.py',
142 for f in [u'ipcontroller_config.py', u'ipengine_config.py',
143 u'ipcluster_config.py']:
143 u'ipcluster_config.py']:
144 self.copy_config_file(f, path=path, overwrite=overwrite)
144 self.copy_config_file(f, path=path, overwrite=overwrite)
145
145
146 @classmethod
146 @classmethod
147 def create_cluster_dir(csl, cluster_dir):
147 def create_cluster_dir(csl, cluster_dir):
148 """Create a new cluster directory given a full path.
148 """Create a new cluster directory given a full path.
149
149
150 Parameters
150 Parameters
151 ----------
151 ----------
152 cluster_dir : str
152 cluster_dir : str
153 The full path to the cluster directory. If it does exist, it will
153 The full path to the cluster directory. If it does exist, it will
154 be used. If not, it will be created.
154 be used. If not, it will be created.
155 """
155 """
156 return ClusterDir(cluster_dir)
156 return ClusterDir(cluster_dir)
157
157
158 @classmethod
158 @classmethod
159 def create_cluster_dir_by_profile(cls, path, profile=u'default'):
159 def create_cluster_dir_by_profile(cls, path, profile=u'default'):
160 """Create a cluster dir by profile name and path.
160 """Create a cluster dir by profile name and path.
161
161
162 Parameters
162 Parameters
163 ----------
163 ----------
164 path : str
164 path : str
165 The path (directory) to put the cluster directory in.
165 The path (directory) to put the cluster directory in.
166 profile : str
166 profile : str
167 The name of the profile. The name of the cluster directory will
167 The name of the profile. The name of the cluster directory will
168 be "cluster_<profile>".
168 be "cluster_<profile>".
169 """
169 """
170 if not os.path.isdir(path):
170 if not os.path.isdir(path):
171 raise ClusterDirError('Directory not found: %s' % path)
171 raise ClusterDirError('Directory not found: %s' % path)
172 cluster_dir = os.path.join(path, u'cluster_' + profile)
172 cluster_dir = os.path.join(path, u'cluster_' + profile)
173 return ClusterDir(cluster_dir)
173 return ClusterDir(cluster_dir)
174
174
175 @classmethod
175 @classmethod
176 def find_cluster_dir_by_profile(cls, ipython_dir, profile=u'default'):
176 def find_cluster_dir_by_profile(cls, ipython_dir, profile=u'default'):
177 """Find an existing cluster dir by profile name, return its ClusterDir.
177 """Find an existing cluster dir by profile name, return its ClusterDir.
178
178
179 This searches through a sequence of paths for a cluster dir. If it
179 This searches through a sequence of paths for a cluster dir. If it
180 is not found, a :class:`ClusterDirError` exception will be raised.
180 is not found, a :class:`ClusterDirError` exception will be raised.
181
181
182 The search path algorithm is:
182 The search path algorithm is:
183 1. ``os.getcwd()``
183 1. ``os.getcwd()``
184 2. ``ipython_dir``
184 2. ``ipython_dir``
185 3. The directories found in the ":" separated
185 3. The directories found in the ":" separated
186 :env:`IPCLUSTER_DIR_PATH` environment variable.
186 :env:`IPCLUSTER_DIR_PATH` environment variable.
187
187
188 Parameters
188 Parameters
189 ----------
189 ----------
190 ipython_dir : unicode or str
190 ipython_dir : unicode or str
191 The IPython directory to use.
191 The IPython directory to use.
192 profile : unicode or str
192 profile : unicode or str
193 The name of the profile. The name of the cluster directory
193 The name of the profile. The name of the cluster directory
194 will be "cluster_<profile>".
194 will be "cluster_<profile>".
195 """
195 """
196 dirname = u'cluster_' + profile
196 dirname = u'cluster_' + profile
197 cluster_dir_paths = os.environ.get('IPCLUSTER_DIR_PATH','')
197 cluster_dir_paths = os.environ.get('IPCLUSTER_DIR_PATH','')
198 if cluster_dir_paths:
198 if cluster_dir_paths:
199 cluster_dir_paths = cluster_dir_paths.split(':')
199 cluster_dir_paths = cluster_dir_paths.split(':')
200 else:
200 else:
201 cluster_dir_paths = []
201 cluster_dir_paths = []
202 paths = [os.getcwd(), ipython_dir] + cluster_dir_paths
202 paths = [os.getcwd(), ipython_dir] + cluster_dir_paths
203 for p in paths:
203 for p in paths:
204 cluster_dir = os.path.join(p, dirname)
204 cluster_dir = os.path.join(p, dirname)
205 if os.path.isdir(cluster_dir):
205 if os.path.isdir(cluster_dir):
206 return ClusterDir(cluster_dir)
206 return ClusterDir(cluster_dir)
207 else:
207 else:
208 raise ClusterDirError('Cluster directory not found in paths: %s' % dirname)
208 raise ClusterDirError('Cluster directory not found in paths: %s' % dirname)
209
209
210 @classmethod
210 @classmethod
211 def find_cluster_dir(cls, cluster_dir):
211 def find_cluster_dir(cls, cluster_dir):
212 """Find/create a cluster dir and return its ClusterDir.
212 """Find/create a cluster dir and return its ClusterDir.
213
213
214 This will create the cluster directory if it doesn't exist.
214 This will create the cluster directory if it doesn't exist.
215
215
216 Parameters
216 Parameters
217 ----------
217 ----------
218 cluster_dir : unicode or str
218 cluster_dir : unicode or str
219 The path of the cluster directory. This is expanded using
219 The path of the cluster directory. This is expanded using
220 :func:`IPython.utils.genutils.expand_path`.
220 :func:`IPython.utils.genutils.expand_path`.
221 """
221 """
222 cluster_dir = genutils.expand_path(cluster_dir)
222 cluster_dir = genutils.expand_path(cluster_dir)
223 if not os.path.isdir(cluster_dir):
223 if not os.path.isdir(cluster_dir):
224 raise ClusterDirError('Cluster directory not found: %s' % cluster_dir)
224 raise ClusterDirError('Cluster directory not found: %s' % cluster_dir)
225 return ClusterDir(cluster_dir)
225 return ClusterDir(cluster_dir)
226
226
227
227
228 class AppWithClusterDirArgParseConfigLoader(ArgParseConfigLoader):
228 class AppWithClusterDirArgParseConfigLoader(ArgParseConfigLoader):
229 """Default command line options for IPython cluster applications."""
229 """Default command line options for IPython cluster applications."""
230
230
231 def _add_other_arguments(self):
231 def _add_other_arguments(self):
232 self.parser.add_argument('--ipython-dir',
232 self.parser.add_argument('--ipython-dir',
233 dest='Global.ipython_dir',type=unicode,
233 dest='Global.ipython_dir',type=unicode,
234 help='Set to override default location of Global.ipython_dir.',
234 help='Set to override default location of Global.ipython_dir.',
235 default=NoConfigDefault,
235 default=NoConfigDefault,
236 metavar='Global.ipython_dir'
236 metavar='Global.ipython_dir'
237 )
237 )
238 self.parser.add_argument('-p', '--profile',
238 self.parser.add_argument('-p', '--profile',
239 dest='Global.profile',type=unicode,
239 dest='Global.profile',type=unicode,
240 help='The string name of the profile to be used. This determines '
240 help='The string name of the profile to be used. This determines '
241 'the name of the cluster dir as: cluster_<profile>. The default profile '
241 'the name of the cluster dir as: cluster_<profile>. The default profile '
242 'is named "default". The cluster directory is resolve this way '
242 'is named "default". The cluster directory is resolve this way '
243 'if the --cluster-dir option is not used.',
243 'if the --cluster-dir option is not used.',
244 default=NoConfigDefault,
244 default=NoConfigDefault,
245 metavar='Global.profile'
245 metavar='Global.profile'
246 )
246 )
247 self.parser.add_argument('--log-level',
247 self.parser.add_argument('--log-level',
248 dest="Global.log_level",type=int,
248 dest="Global.log_level",type=int,
249 help='Set the log level (0,10,20,30,40,50). Default is 30.',
249 help='Set the log level (0,10,20,30,40,50). Default is 30.',
250 default=NoConfigDefault,
250 default=NoConfigDefault,
251 metavar="Global.log_level"
251 metavar="Global.log_level"
252 )
252 )
253 self.parser.add_argument('--cluster-dir',
253 self.parser.add_argument('--cluster-dir',
254 dest='Global.cluster_dir',type=unicode,
254 dest='Global.cluster_dir',type=unicode,
255 help='Set the cluster dir. This overrides the logic used by the '
255 help='Set the cluster dir. This overrides the logic used by the '
256 '--profile option.',
256 '--profile option.',
257 default=NoConfigDefault,
257 default=NoConfigDefault,
258 metavar='Global.cluster_dir'
258 metavar='Global.cluster_dir'
259 ),
260 self.parser.add_argument('--working-dir',
261 dest='Global.working_dir',type=unicode,
262 help='Set the working dir for the process.',
263 default=NoConfigDefault,
264 metavar='Global.working_dir'
259 )
265 )
260 self.parser.add_argument('--clean-logs',
266 self.parser.add_argument('--clean-logs',
261 dest='Global.clean_logs', action='store_true',
267 dest='Global.clean_logs', action='store_true',
262 help='Delete old log flies before starting.',
268 help='Delete old log flies before starting.',
263 default=NoConfigDefault
269 default=NoConfigDefault
264 )
270 )
265 self.parser.add_argument('--no-clean-logs',
271 self.parser.add_argument('--no-clean-logs',
266 dest='Global.clean_logs', action='store_false',
272 dest='Global.clean_logs', action='store_false',
267 help="Don't Delete old log flies before starting.",
273 help="Don't Delete old log flies before starting.",
268 default=NoConfigDefault
274 default=NoConfigDefault
269 )
275 )
270
276
271 class ApplicationWithClusterDir(Application):
277 class ApplicationWithClusterDir(Application):
272 """An application that puts everything into a cluster directory.
278 """An application that puts everything into a cluster directory.
273
279
274 Instead of looking for things in the ipython_dir, this type of application
280 Instead of looking for things in the ipython_dir, this type of application
275 will use its own private directory called the "cluster directory"
281 will use its own private directory called the "cluster directory"
276 for things like config files, log files, etc.
282 for things like config files, log files, etc.
277
283
278 The cluster directory is resolved as follows:
284 The cluster directory is resolved as follows:
279
285
280 * If the ``--cluster-dir`` option is given, it is used.
286 * If the ``--cluster-dir`` option is given, it is used.
281 * If ``--cluster-dir`` is not given, the application directory is
287 * If ``--cluster-dir`` is not given, the application directory is
282 resolve using the profile name as ``cluster_<profile>``. The search
288 resolve using the profile name as ``cluster_<profile>``. The search
283 path for this directory is then i) cwd if it is found there
289 path for this directory is then i) cwd if it is found there
284 and ii) in ipython_dir otherwise.
290 and ii) in ipython_dir otherwise.
285
291
286 The config file for the application is to be put in the cluster
292 The config file for the application is to be put in the cluster
287 dir and named the value of the ``config_file_name`` class attribute.
293 dir and named the value of the ``config_file_name`` class attribute.
288 """
294 """
289
295
290 auto_create_cluster_dir = True
296 auto_create_cluster_dir = True
291
297
292 def create_default_config(self):
298 def create_default_config(self):
293 super(ApplicationWithClusterDir, self).create_default_config()
299 super(ApplicationWithClusterDir, self).create_default_config()
294 self.default_config.Global.profile = u'default'
300 self.default_config.Global.profile = u'default'
295 self.default_config.Global.cluster_dir = u''
301 self.default_config.Global.cluster_dir = u''
302 self.default_config.Global.working_dir = os.getcwd()
296 self.default_config.Global.log_to_file = False
303 self.default_config.Global.log_to_file = False
297 self.default_config.Global.clean_logs = False
304 self.default_config.Global.clean_logs = False
298
305
299 def create_command_line_config(self):
306 def create_command_line_config(self):
300 """Create and return a command line config loader."""
307 """Create and return a command line config loader."""
301 return AppWithClusterDirArgParseConfigLoader(
308 return AppWithClusterDirArgParseConfigLoader(
302 description=self.description,
309 description=self.description,
303 version=release.version
310 version=release.version
304 )
311 )
305
312
306 def find_resources(self):
313 def find_resources(self):
307 """This resolves the cluster directory.
314 """This resolves the cluster directory.
308
315
309 This tries to find the cluster directory and if successful, it will
316 This tries to find the cluster directory and if successful, it will
310 have done:
317 have done:
311 * Sets ``self.cluster_dir_obj`` to the :class:`ClusterDir` object for
318 * Sets ``self.cluster_dir_obj`` to the :class:`ClusterDir` object for
312 the application.
319 the application.
313 * Sets ``self.cluster_dir`` attribute of the application and config
320 * Sets ``self.cluster_dir`` attribute of the application and config
314 objects.
321 objects.
315
322
316 The algorithm used for this is as follows:
323 The algorithm used for this is as follows:
317 1. Try ``Global.cluster_dir``.
324 1. Try ``Global.cluster_dir``.
318 2. Try using ``Global.profile``.
325 2. Try using ``Global.profile``.
319 3. If both of these fail and ``self.auto_create_cluster_dir`` is
326 3. If both of these fail and ``self.auto_create_cluster_dir`` is
320 ``True``, then create the new cluster dir in the IPython directory.
327 ``True``, then create the new cluster dir in the IPython directory.
321 4. If all fails, then raise :class:`ClusterDirError`.
328 4. If all fails, then raise :class:`ClusterDirError`.
322 """
329 """
323
330
324 try:
331 try:
325 cluster_dir = self.command_line_config.Global.cluster_dir
332 cluster_dir = self.command_line_config.Global.cluster_dir
326 except AttributeError:
333 except AttributeError:
327 cluster_dir = self.default_config.Global.cluster_dir
334 cluster_dir = self.default_config.Global.cluster_dir
328 cluster_dir = genutils.expand_path(cluster_dir)
335 cluster_dir = genutils.expand_path(cluster_dir)
329 try:
336 try:
330 self.cluster_dir_obj = ClusterDir.find_cluster_dir(cluster_dir)
337 self.cluster_dir_obj = ClusterDir.find_cluster_dir(cluster_dir)
331 except ClusterDirError:
338 except ClusterDirError:
332 pass
339 pass
333 else:
340 else:
334 self.log.info('Using existing cluster dir: %s' % \
341 self.log.info('Using existing cluster dir: %s' % \
335 self.cluster_dir_obj.location
342 self.cluster_dir_obj.location
336 )
343 )
337 self.finish_cluster_dir()
344 self.finish_cluster_dir()
338 return
345 return
339
346
340 try:
347 try:
341 self.profile = self.command_line_config.Global.profile
348 self.profile = self.command_line_config.Global.profile
342 except AttributeError:
349 except AttributeError:
343 self.profile = self.default_config.Global.profile
350 self.profile = self.default_config.Global.profile
344 try:
351 try:
345 self.cluster_dir_obj = ClusterDir.find_cluster_dir_by_profile(
352 self.cluster_dir_obj = ClusterDir.find_cluster_dir_by_profile(
346 self.ipython_dir, self.profile)
353 self.ipython_dir, self.profile)
347 except ClusterDirError:
354 except ClusterDirError:
348 pass
355 pass
349 else:
356 else:
350 self.log.info('Using existing cluster dir: %s' % \
357 self.log.info('Using existing cluster dir: %s' % \
351 self.cluster_dir_obj.location
358 self.cluster_dir_obj.location
352 )
359 )
353 self.finish_cluster_dir()
360 self.finish_cluster_dir()
354 return
361 return
355
362
356 if self.auto_create_cluster_dir:
363 if self.auto_create_cluster_dir:
357 self.cluster_dir_obj = ClusterDir.create_cluster_dir_by_profile(
364 self.cluster_dir_obj = ClusterDir.create_cluster_dir_by_profile(
358 self.ipython_dir, self.profile
365 self.ipython_dir, self.profile
359 )
366 )
360 self.log.info('Creating new cluster dir: %s' % \
367 self.log.info('Creating new cluster dir: %s' % \
361 self.cluster_dir_obj.location
368 self.cluster_dir_obj.location
362 )
369 )
363 self.finish_cluster_dir()
370 self.finish_cluster_dir()
364 else:
371 else:
365 raise ClusterDirError('Could not find a valid cluster directory.')
372 raise ClusterDirError('Could not find a valid cluster directory.')
366
373
367 def finish_cluster_dir(self):
374 def finish_cluster_dir(self):
368 # Set the cluster directory
375 # Set the cluster directory
369 self.cluster_dir = self.cluster_dir_obj.location
376 self.cluster_dir = self.cluster_dir_obj.location
370
377
371 # These have to be set because they could be different from the one
378 # These have to be set because they could be different from the one
372 # that we just computed. Because command line has the highest
379 # that we just computed. Because command line has the highest
373 # priority, this will always end up in the master_config.
380 # priority, this will always end up in the master_config.
374 self.default_config.Global.cluster_dir = self.cluster_dir
381 self.default_config.Global.cluster_dir = self.cluster_dir
375 self.command_line_config.Global.cluster_dir = self.cluster_dir
382 self.command_line_config.Global.cluster_dir = self.cluster_dir
376
383
377 # Set the search path to the cluster directory
384 # Set the search path to the cluster directory
378 self.config_file_paths = (self.cluster_dir,)
385 self.config_file_paths = (self.cluster_dir,)
379
386
380 def find_config_file_name(self):
387 def find_config_file_name(self):
381 """Find the config file name for this application."""
388 """Find the config file name for this application."""
382 # For this type of Application it should be set as a class attribute.
389 # For this type of Application it should be set as a class attribute.
383 if not hasattr(self, 'config_file_name'):
390 if not hasattr(self, 'config_file_name'):
384 self.log.critical("No config filename found")
391 self.log.critical("No config filename found")
385
392
386 def find_config_file_paths(self):
393 def find_config_file_paths(self):
387 # Set the search path to the cluster directory
394 # Set the search path to the cluster directory
388 self.config_file_paths = (self.cluster_dir,)
395 self.config_file_paths = (self.cluster_dir,)
389
396
390 def pre_construct(self):
397 def pre_construct(self):
391 # The log and security dirs were set earlier, but here we put them
398 # The log and security dirs were set earlier, but here we put them
392 # into the config and log them.
399 # into the config and log them.
393 config = self.master_config
400 config = self.master_config
394 sdir = self.cluster_dir_obj.security_dir
401 sdir = self.cluster_dir_obj.security_dir
395 self.security_dir = config.Global.security_dir = sdir
402 self.security_dir = config.Global.security_dir = sdir
396 ldir = self.cluster_dir_obj.log_dir
403 ldir = self.cluster_dir_obj.log_dir
397 self.log_dir = config.Global.log_dir = ldir
404 self.log_dir = config.Global.log_dir = ldir
398 pdir = self.cluster_dir_obj.pid_dir
405 pdir = self.cluster_dir_obj.pid_dir
399 self.pid_dir = config.Global.pid_dir = pdir
406 self.pid_dir = config.Global.pid_dir = pdir
400 self.log.info("Cluster directory set to: %s" % self.cluster_dir)
407 self.log.info("Cluster directory set to: %s" % self.cluster_dir)
408 config.Global.working_dir = unicode(genutils.expand_path(config.Global.working_dir))
409 # Change to the working directory. We do this just before construct
410 # is called so all the components there have the right working dir.
411 self.to_working_dir()
412
413 def to_working_dir(self):
414 wd = self.master_config.Global.working_dir
415 if unicode(wd) != unicode(os.getcwd()):
416 os.chdir(wd)
417 self.log.info("Changing to working dir: %s" % wd)
401
418
402 def start_logging(self):
419 def start_logging(self):
403 # Remove old log files
420 # Remove old log files
404 if self.master_config.Global.clean_logs:
421 if self.master_config.Global.clean_logs:
405 log_dir = self.master_config.Global.log_dir
422 log_dir = self.master_config.Global.log_dir
406 for f in os.listdir(log_dir):
423 for f in os.listdir(log_dir):
407 if f.startswith(self.name + u'-') and f.endswith('.log'):
424 if f.startswith(self.name + u'-') and f.endswith('.log'):
408 os.remove(os.path.join(log_dir, f))
425 os.remove(os.path.join(log_dir, f))
409 # Start logging to the new log file
426 # Start logging to the new log file
410 if self.master_config.Global.log_to_file:
427 if self.master_config.Global.log_to_file:
411 log_filename = self.name + u'-' + str(os.getpid()) + u'.log'
428 log_filename = self.name + u'-' + str(os.getpid()) + u'.log'
412 logfile = os.path.join(self.log_dir, log_filename)
429 logfile = os.path.join(self.log_dir, log_filename)
413 open_log_file = open(logfile, 'w')
430 open_log_file = open(logfile, 'w')
414 else:
431 else:
415 open_log_file = sys.stdout
432 open_log_file = sys.stdout
416 log.startLogging(open_log_file)
433 log.startLogging(open_log_file)
417
434
418 def write_pid_file(self, overwrite=False):
435 def write_pid_file(self, overwrite=False):
419 """Create a .pid file in the pid_dir with my pid.
436 """Create a .pid file in the pid_dir with my pid.
420
437
421 This must be called after pre_construct, which sets `self.pid_dir`.
438 This must be called after pre_construct, which sets `self.pid_dir`.
422 This raises :exc:`PIDFileError` if the pid file exists already.
439 This raises :exc:`PIDFileError` if the pid file exists already.
423 """
440 """
424 pid_file = os.path.join(self.pid_dir, self.name + u'.pid')
441 pid_file = os.path.join(self.pid_dir, self.name + u'.pid')
425 if os.path.isfile(pid_file):
442 if os.path.isfile(pid_file):
426 pid = self.get_pid_from_file()
443 pid = self.get_pid_from_file()
427 if not overwrite:
444 if not overwrite:
428 raise PIDFileError(
445 raise PIDFileError(
429 'The pid file [%s] already exists. \nThis could mean that this '
446 'The pid file [%s] already exists. \nThis could mean that this '
430 'server is already running with [pid=%s].' % (pid_file, pid)
447 'server is already running with [pid=%s].' % (pid_file, pid)
431 )
448 )
432 with open(pid_file, 'w') as f:
449 with open(pid_file, 'w') as f:
433 self.log.info("Creating pid file: %s" % pid_file)
450 self.log.info("Creating pid file: %s" % pid_file)
434 f.write(repr(os.getpid())+'\n')
451 f.write(repr(os.getpid())+'\n')
435
452
436 def remove_pid_file(self):
453 def remove_pid_file(self):
437 """Remove the pid file.
454 """Remove the pid file.
438
455
439 This should be called at shutdown by registering a callback with
456 This should be called at shutdown by registering a callback with
440 :func:`reactor.addSystemEventTrigger`.
457 :func:`reactor.addSystemEventTrigger`.
441 """
458 """
442 pid_file = os.path.join(self.pid_dir, self.name + u'.pid')
459 pid_file = os.path.join(self.pid_dir, self.name + u'.pid')
443 if os.path.isfile(pid_file):
460 if os.path.isfile(pid_file):
444 try:
461 try:
445 self.log.info("Removing pid file: %s" % pid_file)
462 self.log.info("Removing pid file: %s" % pid_file)
446 os.remove(pid_file)
463 os.remove(pid_file)
447 except:
464 except:
448 self.log.warn("Error removing the pid file: %s" % pid_file)
465 self.log.warn("Error removing the pid file: %s" % pid_file)
449 raise
466 raise
450
467
451 def get_pid_from_file(self):
468 def get_pid_from_file(self):
452 """Get the pid from the pid file.
469 """Get the pid from the pid file.
453
470
454 If the pid file doesn't exist a :exc:`PIDFileError` is raised.
471 If the pid file doesn't exist a :exc:`PIDFileError` is raised.
455 """
472 """
456 pid_file = os.path.join(self.pid_dir, self.name + u'.pid')
473 pid_file = os.path.join(self.pid_dir, self.name + u'.pid')
457 if os.path.isfile(pid_file):
474 if os.path.isfile(pid_file):
458 with open(pid_file, 'r') as f:
475 with open(pid_file, 'r') as f:
459 pid = int(f.read().strip())
476 pid = int(f.read().strip())
460 return pid
477 return pid
461 else:
478 else:
462 raise PIDFileError('pid file not found: %s' % pid_file)
479 raise PIDFileError('pid file not found: %s' % pid_file)
463
480
464
481
@@ -1,402 +1,406 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 """
3 """
4 The ipcluster application.
4 The ipcluster application.
5 """
5 """
6
6
7 #-----------------------------------------------------------------------------
7 #-----------------------------------------------------------------------------
8 # Copyright (C) 2008-2009 The IPython Development Team
8 # Copyright (C) 2008-2009 The IPython Development Team
9 #
9 #
10 # Distributed under the terms of the BSD License. The full license is in
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
11 # the file COPYING, distributed as part of this software.
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13
13
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 # Imports
15 # Imports
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17
17
18 import logging
18 import logging
19 import os
19 import os
20 import signal
20 import signal
21 import sys
21 import sys
22
22
23 if os.name=='posix':
23 if os.name=='posix':
24 from twisted.scripts._twistd_unix import daemonize
24 from twisted.scripts._twistd_unix import daemonize
25
25
26 from IPython.core import release
26 from IPython.core import release
27 from IPython.external import argparse
27 from IPython.external import argparse
28 from IPython.config.loader import ArgParseConfigLoader, NoConfigDefault
28 from IPython.config.loader import ArgParseConfigLoader, NoConfigDefault
29 from IPython.utils.importstring import import_item
29 from IPython.utils.importstring import import_item
30
30
31 from IPython.kernel.clusterdir import (
31 from IPython.kernel.clusterdir import (
32 ApplicationWithClusterDir, ClusterDirError, PIDFileError
32 ApplicationWithClusterDir, ClusterDirError, PIDFileError
33 )
33 )
34
34
35 from twisted.internet import reactor
35 from twisted.internet import reactor
36 from twisted.python import log
36 from twisted.python import log
37
37
38
38
39 #-----------------------------------------------------------------------------
39 #-----------------------------------------------------------------------------
40 # The ipcluster application
40 # The ipcluster application
41 #-----------------------------------------------------------------------------
41 #-----------------------------------------------------------------------------
42
42
43
43
44 # Exit codes for ipcluster
44 # Exit codes for ipcluster
45
45
46 # This will be the exit code if the ipcluster appears to be running because
46 # This will be the exit code if the ipcluster appears to be running because
47 # a .pid file exists
47 # a .pid file exists
48 ALREADY_STARTED = 10
48 ALREADY_STARTED = 10
49
49
50 # This will be the exit code if ipcluster stop is run, but there is not .pid
50 # This will be the exit code if ipcluster stop is run, but there is not .pid
51 # file to be found.
51 # file to be found.
52 ALREADY_STOPPED = 11
52 ALREADY_STOPPED = 11
53
53
54
54
55 class IPClusterCLLoader(ArgParseConfigLoader):
55 class IPClusterCLLoader(ArgParseConfigLoader):
56
56
57 def _add_arguments(self):
57 def _add_arguments(self):
58 # This has all the common options that all subcommands use
58 # This has all the common options that all subcommands use
59 parent_parser1 = argparse.ArgumentParser(add_help=False)
59 parent_parser1 = argparse.ArgumentParser(add_help=False)
60 parent_parser1.add_argument('--ipython-dir',
60 parent_parser1.add_argument('--ipython-dir',
61 dest='Global.ipython_dir',type=unicode,
61 dest='Global.ipython_dir',type=unicode,
62 help='Set to override default location of Global.ipython_dir.',
62 help='Set to override default location of Global.ipython_dir.',
63 default=NoConfigDefault,
63 default=NoConfigDefault,
64 metavar='Global.ipython_dir')
64 metavar='Global.ipython_dir')
65 parent_parser1.add_argument('--log-level',
65 parent_parser1.add_argument('--log-level',
66 dest="Global.log_level",type=int,
66 dest="Global.log_level",type=int,
67 help='Set the log level (0,10,20,30,40,50). Default is 30.',
67 help='Set the log level (0,10,20,30,40,50). Default is 30.',
68 default=NoConfigDefault,
68 default=NoConfigDefault,
69 metavar='Global.log_level')
69 metavar='Global.log_level')
70
70
71 # This has all the common options that other subcommands use
71 # This has all the common options that other subcommands use
72 parent_parser2 = argparse.ArgumentParser(add_help=False)
72 parent_parser2 = argparse.ArgumentParser(add_help=False)
73 parent_parser2.add_argument('-p','--profile',
73 parent_parser2.add_argument('-p','--profile',
74 dest='Global.profile',type=unicode,
74 dest='Global.profile',type=unicode,
75 default=NoConfigDefault,
75 default=NoConfigDefault,
76 help='The string name of the profile to be used. This determines '
76 help='The string name of the profile to be used. This determines '
77 'the name of the cluster dir as: cluster_<profile>. The default profile '
77 'the name of the cluster dir as: cluster_<profile>. The default profile '
78 'is named "default". The cluster directory is resolve this way '
78 'is named "default". The cluster directory is resolve this way '
79 'if the --cluster-dir option is not used.',
79 'if the --cluster-dir option is not used.',
80 default=NoConfigDefault,
80 default=NoConfigDefault,
81 metavar='Global.profile')
81 metavar='Global.profile')
82 parent_parser2.add_argument('--cluster-dir',
82 parent_parser2.add_argument('--cluster-dir',
83 dest='Global.cluster_dir',type=unicode,
83 dest='Global.cluster_dir',type=unicode,
84 default=NoConfigDefault,
84 default=NoConfigDefault,
85 help='Set the cluster dir. This overrides the logic used by the '
85 help='Set the cluster dir. This overrides the logic used by the '
86 '--profile option.',
86 '--profile option.',
87 default=NoConfigDefault,
87 default=NoConfigDefault,
88 metavar='Global.cluster_dir')
88 metavar='Global.cluster_dir'),
89 parent_parser2.add_argument('--working-dir',
90 dest='Global.working_dir',type=unicode,
91 help='Set the working dir for the process.',
92 default=NoConfigDefault,
93 metavar='Global.working_dir')
89 parent_parser2.add_argument('--log-to-file',
94 parent_parser2.add_argument('--log-to-file',
90 action='store_true', dest='Global.log_to_file',
95 action='store_true', dest='Global.log_to_file',
91 default=NoConfigDefault,
96 default=NoConfigDefault,
92 help='Log to a file in the log directory (default is stdout)'
97 help='Log to a file in the log directory (default is stdout)'
93 )
98 )
94
99
95 subparsers = self.parser.add_subparsers(
100 subparsers = self.parser.add_subparsers(
96 dest='Global.subcommand',
101 dest='Global.subcommand',
97 title='ipcluster subcommands',
102 title='ipcluster subcommands',
98 description='ipcluster has a variety of subcommands. '
103 description='ipcluster has a variety of subcommands. '
99 'The general way of running ipcluster is "ipcluster <cmd> '
104 'The general way of running ipcluster is "ipcluster <cmd> '
100 ' [options]""',
105 ' [options]""',
101 help='For more help, type "ipcluster <cmd> -h"')
106 help='For more help, type "ipcluster <cmd> -h"')
102
107
103 parser_list = subparsers.add_parser(
108 parser_list = subparsers.add_parser(
104 'list',
109 'list',
105 help='List all clusters in cwd and ipython_dir.',
110 help='List all clusters in cwd and ipython_dir.',
106 parents=[parent_parser1]
111 parents=[parent_parser1]
107 )
112 )
108
113
109 parser_create = subparsers.add_parser(
114 parser_create = subparsers.add_parser(
110 'create',
115 'create',
111 help='Create a new cluster directory.',
116 help='Create a new cluster directory.',
112 parents=[parent_parser1, parent_parser2]
117 parents=[parent_parser1, parent_parser2]
113 )
118 )
114 parser_create.add_argument(
119 parser_create.add_argument(
115 '--reset-config',
120 '--reset-config',
116 dest='Global.reset_config', action='store_true',
121 dest='Global.reset_config', action='store_true',
117 default=NoConfigDefault,
122 default=NoConfigDefault,
118 help='Recopy the default config files to the cluster directory. '
123 help='Recopy the default config files to the cluster directory. '
119 'You will loose any modifications you have made to these files.'
124 'You will loose any modifications you have made to these files.'
120 )
125 )
121
126
122 parser_start = subparsers.add_parser(
127 parser_start = subparsers.add_parser(
123 'start',
128 'start',
124 help='Start a cluster.',
129 help='Start a cluster.',
125 parents=[parent_parser1, parent_parser2]
130 parents=[parent_parser1, parent_parser2]
126 )
131 )
127 parser_start.add_argument(
132 parser_start.add_argument(
128 '-n', '--number',
133 '-n', '--number',
129 type=int, dest='Global.n',
134 type=int, dest='Global.n',
130 default=NoConfigDefault,
135 default=NoConfigDefault,
131 help='The number of engines to start.',
136 help='The number of engines to start.',
132 metavar='Global.n'
137 metavar='Global.n'
133 )
138 )
134 parser_start.add_argument('--clean-logs',
139 parser_start.add_argument('--clean-logs',
135 dest='Global.clean_logs', action='store_true',
140 dest='Global.clean_logs', action='store_true',
136 help='Delete old log flies before starting.',
141 help='Delete old log flies before starting.',
137 default=NoConfigDefault
142 default=NoConfigDefault
138 )
143 )
139 parser_start.add_argument('--no-clean-logs',
144 parser_start.add_argument('--no-clean-logs',
140 dest='Global.clean_logs', action='store_false',
145 dest='Global.clean_logs', action='store_false',
141 help="Don't delete old log flies before starting.",
146 help="Don't delete old log flies before starting.",
142 default=NoConfigDefault
147 default=NoConfigDefault
143 )
148 )
144 parser_start.add_argument('--daemon',
149 parser_start.add_argument('--daemon',
145 dest='Global.daemonize', action='store_true',
150 dest='Global.daemonize', action='store_true',
146 help='Daemonize the ipcluster program. This implies --log-to-file',
151 help='Daemonize the ipcluster program. This implies --log-to-file',
147 default=NoConfigDefault
152 default=NoConfigDefault
148 )
153 )
149 parser_start.add_argument('--no-daemon',
154 parser_start.add_argument('--no-daemon',
150 dest='Global.daemonize', action='store_false',
155 dest='Global.daemonize', action='store_false',
151 help="Dont't daemonize the ipcluster program.",
156 help="Dont't daemonize the ipcluster program.",
152 default=NoConfigDefault
157 default=NoConfigDefault
153 )
158 )
154
159
155 parser_start = subparsers.add_parser(
160 parser_start = subparsers.add_parser(
156 'stop',
161 'stop',
157 help='Stop a cluster.',
162 help='Stop a cluster.',
158 parents=[parent_parser1, parent_parser2]
163 parents=[parent_parser1, parent_parser2]
159 )
164 )
160 parser_start.add_argument('--signal',
165 parser_start.add_argument('--signal',
161 dest='Global.signal', type=int,
166 dest='Global.signal', type=int,
162 help="The signal number to use in stopping the cluster (default=2).",
167 help="The signal number to use in stopping the cluster (default=2).",
163 metavar="Global.signal",
168 metavar="Global.signal",
164 default=NoConfigDefault
169 default=NoConfigDefault
165 )
170 )
166
171
167
172
168 default_config_file_name = u'ipcluster_config.py'
173 default_config_file_name = u'ipcluster_config.py'
169
174
170
175
171 class IPClusterApp(ApplicationWithClusterDir):
176 class IPClusterApp(ApplicationWithClusterDir):
172
177
173 name = u'ipcluster'
178 name = u'ipcluster'
174 description = 'Start an IPython cluster (controller and engines).'
179 description = 'Start an IPython cluster (controller and engines).'
175 config_file_name = default_config_file_name
180 config_file_name = default_config_file_name
176 default_log_level = logging.INFO
181 default_log_level = logging.INFO
177 auto_create_cluster_dir = False
182 auto_create_cluster_dir = False
178
183
179 def create_default_config(self):
184 def create_default_config(self):
180 super(IPClusterApp, self).create_default_config()
185 super(IPClusterApp, self).create_default_config()
181 self.default_config.Global.controller_launcher = \
186 self.default_config.Global.controller_launcher = \
182 'IPython.kernel.launcher.LocalControllerLauncher'
187 'IPython.kernel.launcher.LocalControllerLauncher'
183 self.default_config.Global.engine_launcher = \
188 self.default_config.Global.engine_launcher = \
184 'IPython.kernel.launcher.LocalEngineSetLauncher'
189 'IPython.kernel.launcher.LocalEngineSetLauncher'
185 self.default_config.Global.n = 2
190 self.default_config.Global.n = 2
186 self.default_config.Global.reset_config = False
191 self.default_config.Global.reset_config = False
187 self.default_config.Global.clean_logs = True
192 self.default_config.Global.clean_logs = True
188 self.default_config.Global.signal = 2
193 self.default_config.Global.signal = 2
189 self.default_config.Global.daemonize = False
194 self.default_config.Global.daemonize = False
190
195
191 def create_command_line_config(self):
196 def create_command_line_config(self):
192 """Create and return a command line config loader."""
197 """Create and return a command line config loader."""
193 return IPClusterCLLoader(
198 return IPClusterCLLoader(
194 description=self.description,
199 description=self.description,
195 version=release.version
200 version=release.version
196 )
201 )
197
202
198 def find_resources(self):
203 def find_resources(self):
199 subcommand = self.command_line_config.Global.subcommand
204 subcommand = self.command_line_config.Global.subcommand
200 if subcommand=='list':
205 if subcommand=='list':
201 self.list_cluster_dirs()
206 self.list_cluster_dirs()
202 # Exit immediately because there is nothing left to do.
207 # Exit immediately because there is nothing left to do.
203 self.exit()
208 self.exit()
204 elif subcommand=='create':
209 elif subcommand=='create':
205 self.auto_create_cluster_dir = True
210 self.auto_create_cluster_dir = True
206 super(IPClusterApp, self).find_resources()
211 super(IPClusterApp, self).find_resources()
207 elif subcommand=='start' or subcommand=='stop':
212 elif subcommand=='start' or subcommand=='stop':
208 self.auto_create_cluster_dir = False
213 self.auto_create_cluster_dir = False
209 try:
214 try:
210 super(IPClusterApp, self).find_resources()
215 super(IPClusterApp, self).find_resources()
211 except ClusterDirError:
216 except ClusterDirError:
212 raise ClusterDirError(
217 raise ClusterDirError(
213 "Could not find a cluster directory. A cluster dir must "
218 "Could not find a cluster directory. A cluster dir must "
214 "be created before running 'ipcluster start'. Do "
219 "be created before running 'ipcluster start'. Do "
215 "'ipcluster create -h' or 'ipcluster list -h' for more "
220 "'ipcluster create -h' or 'ipcluster list -h' for more "
216 "information about creating and listing cluster dirs."
221 "information about creating and listing cluster dirs."
217 )
222 )
218
223
219 def list_cluster_dirs(self):
224 def list_cluster_dirs(self):
220 # Find the search paths
225 # Find the search paths
221 cluster_dir_paths = os.environ.get('IPCLUSTER_DIR_PATH','')
226 cluster_dir_paths = os.environ.get('IPCLUSTER_DIR_PATH','')
222 if cluster_dir_paths:
227 if cluster_dir_paths:
223 cluster_dir_paths = cluster_dir_paths.split(':')
228 cluster_dir_paths = cluster_dir_paths.split(':')
224 else:
229 else:
225 cluster_dir_paths = []
230 cluster_dir_paths = []
226 try:
231 try:
227 ipython_dir = self.command_line_config.Global.ipython_dir
232 ipython_dir = self.command_line_config.Global.ipython_dir
228 except AttributeError:
233 except AttributeError:
229 ipython_dir = self.default_config.Global.ipython_dir
234 ipython_dir = self.default_config.Global.ipython_dir
230 paths = [os.getcwd(), ipython_dir] + \
235 paths = [os.getcwd(), ipython_dir] + \
231 cluster_dir_paths
236 cluster_dir_paths
232 paths = list(set(paths))
237 paths = list(set(paths))
233
238
234 self.log.info('Searching for cluster dirs in paths: %r' % paths)
239 self.log.info('Searching for cluster dirs in paths: %r' % paths)
235 for path in paths:
240 for path in paths:
236 files = os.listdir(path)
241 files = os.listdir(path)
237 for f in files:
242 for f in files:
238 full_path = os.path.join(path, f)
243 full_path = os.path.join(path, f)
239 if os.path.isdir(full_path) and f.startswith('cluster_'):
244 if os.path.isdir(full_path) and f.startswith('cluster_'):
240 profile = full_path.split('_')[-1]
245 profile = full_path.split('_')[-1]
241 start_cmd = '"ipcluster start -n 4 -p %s"' % profile
246 start_cmd = '"ipcluster start -n 4 -p %s"' % profile
242 print start_cmd + " ==> " + full_path
247 print start_cmd + " ==> " + full_path
243
248
244 def pre_construct(self):
249 def pre_construct(self):
250 # This is where we cd to the working directory.
245 super(IPClusterApp, self).pre_construct()
251 super(IPClusterApp, self).pre_construct()
246 config = self.master_config
252 config = self.master_config
247 try:
253 try:
248 daemon = config.Global.daemonize
254 daemon = config.Global.daemonize
249 if daemon:
255 if daemon:
250 config.Global.log_to_file = True
256 config.Global.log_to_file = True
251 except AttributeError:
257 except AttributeError:
252 pass
258 pass
253
259
254 def construct(self):
260 def construct(self):
255 config = self.master_config
261 config = self.master_config
256 if config.Global.subcommand=='list':
262 if config.Global.subcommand=='list':
257 pass
263 pass
258 elif config.Global.subcommand=='create':
264 elif config.Global.subcommand=='create':
259 self.log.info('Copying default config files to cluster directory '
265 self.log.info('Copying default config files to cluster directory '
260 '[overwrite=%r]' % (config.Global.reset_config,))
266 '[overwrite=%r]' % (config.Global.reset_config,))
261 self.cluster_dir_obj.copy_all_config_files(overwrite=config.Global.reset_config)
267 self.cluster_dir_obj.copy_all_config_files(overwrite=config.Global.reset_config)
262 elif config.Global.subcommand=='start':
268 elif config.Global.subcommand=='start':
263 self.start_logging()
269 self.start_logging()
264 reactor.callWhenRunning(self.start_launchers)
270 reactor.callWhenRunning(self.start_launchers)
265
271
266 def start_launchers(self):
272 def start_launchers(self):
267 config = self.master_config
273 config = self.master_config
268
274
269 # Create the launchers
275 # Create the launchers
270 el_class = import_item(config.Global.engine_launcher)
276 el_class = import_item(config.Global.engine_launcher)
271 self.engine_launcher = el_class(
277 self.engine_launcher = el_class(
272 self.cluster_dir, config=config
278 self.cluster_dir, config=config
273 )
279 )
274 cl_class = import_item(config.Global.controller_launcher)
280 cl_class = import_item(config.Global.controller_launcher)
275 self.controller_launcher = cl_class(
281 self.controller_launcher = cl_class(
276 self.cluster_dir, config=config
282 self.cluster_dir, config=config
277 )
283 )
278
284
279 # Setup signals
285 # Setup signals
280 signal.signal(signal.SIGINT, self.stop_launchers)
286 signal.signal(signal.SIGINT, self.stop_launchers)
281
287
282 # Setup the observing of stopping
288 # Setup the observing of stopping
283 d1 = self.controller_launcher.observe_stop()
289 d1 = self.controller_launcher.observe_stop()
284 d1.addCallback(self.stop_engines)
290 d1.addCallback(self.stop_engines)
285 d1.addErrback(self.err_and_stop)
291 d1.addErrback(self.err_and_stop)
286 # If this triggers, just let them die
292 # If this triggers, just let them die
287 # d2 = self.engine_launcher.observe_stop()
293 # d2 = self.engine_launcher.observe_stop()
288
294
289 # Start the controller and engines
295 # Start the controller and engines
290 d = self.controller_launcher.start(
296 d = self.controller_launcher.start(
291 profile=None, cluster_dir=config.Global.cluster_dir
297 profile=None, cluster_dir=config.Global.cluster_dir
292 )
298 )
293 d.addCallback(lambda _: self.start_engines())
299 d.addCallback(lambda _: self.start_engines())
294 d.addErrback(self.err_and_stop)
300 d.addErrback(self.err_and_stop)
295
301
296 def err_and_stop(self, f):
302 def err_and_stop(self, f):
297 log.msg('Unexpected error in ipcluster:')
303 log.msg('Unexpected error in ipcluster:')
298 log.err(f)
304 log.err(f)
299 reactor.stop()
305 reactor.stop()
300
306
301 def stop_engines(self, r):
307 def stop_engines(self, r):
302 return self.engine_launcher.stop()
308 return self.engine_launcher.stop()
303
309
304 def start_engines(self):
310 def start_engines(self):
305 config = self.master_config
311 config = self.master_config
306 d = self.engine_launcher.start(
312 d = self.engine_launcher.start(
307 config.Global.n,
313 config.Global.n,
308 profile=None, cluster_dir=config.Global.cluster_dir
314 profile=None, cluster_dir=config.Global.cluster_dir
309 )
315 )
310 return d
316 return d
311
317
312 def stop_launchers(self, signum, frame):
318 def stop_launchers(self, signum, frame):
313 log.msg("Stopping cluster")
319 log.msg("Stopping cluster")
314 d1 = self.engine_launcher.stop()
320 d1 = self.engine_launcher.stop()
315 d2 = self.controller_launcher.stop()
321 d2 = self.controller_launcher.stop()
316 # d1.addCallback(lambda _: self.controller_launcher.stop)
322 # d1.addCallback(lambda _: self.controller_launcher.stop)
317 d1.addErrback(self.err_and_stop)
323 d1.addErrback(self.err_and_stop)
318 d2.addErrback(self.err_and_stop)
324 d2.addErrback(self.err_and_stop)
319 reactor.callLater(2.0, reactor.stop)
325 reactor.callLater(2.0, reactor.stop)
320
326
321 def start_logging(self):
327 def start_logging(self):
322 # Remove old log files
328 # Remove old log files
323 if self.master_config.Global.clean_logs:
329 if self.master_config.Global.clean_logs:
324 log_dir = self.master_config.Global.log_dir
330 log_dir = self.master_config.Global.log_dir
325 for f in os.listdir(log_dir):
331 for f in os.listdir(log_dir):
326 if f.startswith('ipengine' + '-') and f.endswith('.log'):
332 if f.startswith('ipengine' + '-') and f.endswith('.log'):
327 os.remove(os.path.join(log_dir, f))
333 os.remove(os.path.join(log_dir, f))
328 for f in os.listdir(log_dir):
334 for f in os.listdir(log_dir):
329 if f.startswith('ipcontroller' + '-') and f.endswith('.log'):
335 if f.startswith('ipcontroller' + '-') and f.endswith('.log'):
330 os.remove(os.path.join(log_dir, f))
336 os.remove(os.path.join(log_dir, f))
331 super(IPClusterApp, self).start_logging()
337 super(IPClusterApp, self).start_logging()
332
338
333 def start_app(self):
339 def start_app(self):
334 """Start the application, depending on what subcommand is used."""
340 """Start the application, depending on what subcommand is used."""
335 subcmd = self.master_config.Global.subcommand
341 subcmd = self.master_config.Global.subcommand
336 if subcmd=='create' or subcmd=='list':
342 if subcmd=='create' or subcmd=='list':
337 return
343 return
338 elif subcmd=='start':
344 elif subcmd=='start':
339 self.start_app_start()
345 self.start_app_start()
340 elif subcmd=='stop':
346 elif subcmd=='stop':
341 self.start_app_stop()
347 self.start_app_stop()
342
348
343 def start_app_start(self):
349 def start_app_start(self):
344 """Start the app for the start subcommand."""
350 """Start the app for the start subcommand."""
345 config = self.master_config
351 config = self.master_config
346 # First see if the cluster is already running
352 # First see if the cluster is already running
347 try:
353 try:
348 pid = self.get_pid_from_file()
354 pid = self.get_pid_from_file()
349 except PIDFileError:
355 except PIDFileError:
350 pass
356 pass
351 else:
357 else:
352 self.log.critical(
358 self.log.critical(
353 'Cluster is already running with [pid=%s]. '
359 'Cluster is already running with [pid=%s]. '
354 'use "ipcluster stop" to stop the cluster.' % pid
360 'use "ipcluster stop" to stop the cluster.' % pid
355 )
361 )
356 # Here I exit with a unusual exit status that other processes
362 # Here I exit with a unusual exit status that other processes
357 # can watch for to learn how I existed.
363 # can watch for to learn how I existed.
358 self.exit(ALREADY_STARTED)
364 self.exit(ALREADY_STARTED)
359
365
360 # Now log and daemonize
366 # Now log and daemonize
361 self.log.info(
367 self.log.info(
362 'Starting ipcluster with [daemon=%r]' % config.Global.daemonize
368 'Starting ipcluster with [daemon=%r]' % config.Global.daemonize
363 )
369 )
364 if config.Global.daemonize:
370 if config.Global.daemonize:
365 if os.name=='posix':
371 if os.name=='posix':
366 daemonize()
372 daemonize()
367
373
368 # Now write the new pid file AFTER our new forked pid is active.
374 # Now write the new pid file AFTER our new forked pid is active.
369 self.write_pid_file()
375 self.write_pid_file()
370 # cd to the cluster_dir as our working directory.
371 os.chdir(config.Global.cluster_dir)
372 reactor.addSystemEventTrigger('during','shutdown', self.remove_pid_file)
376 reactor.addSystemEventTrigger('during','shutdown', self.remove_pid_file)
373 reactor.run()
377 reactor.run()
374
378
375 def start_app_stop(self):
379 def start_app_stop(self):
376 """Start the app for the stop subcommand."""
380 """Start the app for the stop subcommand."""
377 config = self.master_config
381 config = self.master_config
378 try:
382 try:
379 pid = self.get_pid_from_file()
383 pid = self.get_pid_from_file()
380 except PIDFileError:
384 except PIDFileError:
381 self.log.critical(
385 self.log.critical(
382 'Problem reading pid file, cluster is probably not running.'
386 'Problem reading pid file, cluster is probably not running.'
383 )
387 )
384 # Here I exit with a unusual exit status that other processes
388 # Here I exit with a unusual exit status that other processes
385 # can watch for to learn how I existed.
389 # can watch for to learn how I existed.
386 self.exit(ALREADY_STOPPED)
390 self.exit(ALREADY_STOPPED)
387 sig = config.Global.signal
391 sig = config.Global.signal
388 self.log.info(
392 self.log.info(
389 "Stopping cluster [pid=%r] with [signal=%r]" % (pid, sig)
393 "Stopping cluster [pid=%r] with [signal=%r]" % (pid, sig)
390 )
394 )
391 os.kill(pid, sig)
395 os.kill(pid, sig)
392
396
393
397
394 def launch_new_instance():
398 def launch_new_instance():
395 """Create and run the IPython cluster."""
399 """Create and run the IPython cluster."""
396 app = IPClusterApp()
400 app = IPClusterApp()
397 app.start()
401 app.start()
398
402
399
403
400 if __name__ == '__main__':
404 if __name__ == '__main__':
401 launch_new_instance()
405 launch_new_instance()
402
406
@@ -1,268 +1,265 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 """
3 """
4 The IPython controller application.
4 The IPython controller application.
5 """
5 """
6
6
7 #-----------------------------------------------------------------------------
7 #-----------------------------------------------------------------------------
8 # Copyright (C) 2008-2009 The IPython Development Team
8 # Copyright (C) 2008-2009 The IPython Development Team
9 #
9 #
10 # Distributed under the terms of the BSD License. The full license is in
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
11 # the file COPYING, distributed as part of this software.
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13
13
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 # Imports
15 # Imports
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17
17
18 from __future__ import with_statement
18 from __future__ import with_statement
19
19
20 import copy
20 import copy
21 import os
21 import os
22 import sys
22 import sys
23
23
24 from twisted.application import service
24 from twisted.application import service
25 from twisted.internet import reactor
25 from twisted.internet import reactor
26 from twisted.python import log
26 from twisted.python import log
27
27
28 from IPython.config.loader import Config, NoConfigDefault
28 from IPython.config.loader import Config, NoConfigDefault
29
29
30 from IPython.kernel.clusterdir import (
30 from IPython.kernel.clusterdir import (
31 ApplicationWithClusterDir,
31 ApplicationWithClusterDir,
32 AppWithClusterDirArgParseConfigLoader
32 AppWithClusterDirArgParseConfigLoader
33 )
33 )
34
34
35 from IPython.core import release
35 from IPython.core import release
36
36
37 from IPython.utils.traitlets import Str, Instance, Unicode
37 from IPython.utils.traitlets import Str, Instance, Unicode
38
38
39 from IPython.kernel import controllerservice
39 from IPython.kernel import controllerservice
40
40
41 from IPython.kernel.fcutil import FCServiceFactory
41 from IPython.kernel.fcutil import FCServiceFactory
42
42
43 #-----------------------------------------------------------------------------
43 #-----------------------------------------------------------------------------
44 # Default interfaces
44 # Default interfaces
45 #-----------------------------------------------------------------------------
45 #-----------------------------------------------------------------------------
46
46
47
47
48 # The default client interfaces for FCClientServiceFactory.interfaces
48 # The default client interfaces for FCClientServiceFactory.interfaces
49 default_client_interfaces = Config()
49 default_client_interfaces = Config()
50 default_client_interfaces.Task.interface_chain = [
50 default_client_interfaces.Task.interface_chain = [
51 'IPython.kernel.task.ITaskController',
51 'IPython.kernel.task.ITaskController',
52 'IPython.kernel.taskfc.IFCTaskController'
52 'IPython.kernel.taskfc.IFCTaskController'
53 ]
53 ]
54
54
55 default_client_interfaces.Task.furl_file = 'ipcontroller-tc.furl'
55 default_client_interfaces.Task.furl_file = 'ipcontroller-tc.furl'
56
56
57 default_client_interfaces.MultiEngine.interface_chain = [
57 default_client_interfaces.MultiEngine.interface_chain = [
58 'IPython.kernel.multiengine.IMultiEngine',
58 'IPython.kernel.multiengine.IMultiEngine',
59 'IPython.kernel.multienginefc.IFCSynchronousMultiEngine'
59 'IPython.kernel.multienginefc.IFCSynchronousMultiEngine'
60 ]
60 ]
61
61
62 default_client_interfaces.MultiEngine.furl_file = u'ipcontroller-mec.furl'
62 default_client_interfaces.MultiEngine.furl_file = u'ipcontroller-mec.furl'
63
63
64 # Make this a dict we can pass to Config.__init__ for the default
64 # Make this a dict we can pass to Config.__init__ for the default
65 default_client_interfaces = dict(copy.deepcopy(default_client_interfaces.items()))
65 default_client_interfaces = dict(copy.deepcopy(default_client_interfaces.items()))
66
66
67
67
68
68
69 # The default engine interfaces for FCEngineServiceFactory.interfaces
69 # The default engine interfaces for FCEngineServiceFactory.interfaces
70 default_engine_interfaces = Config()
70 default_engine_interfaces = Config()
71 default_engine_interfaces.Default.interface_chain = [
71 default_engine_interfaces.Default.interface_chain = [
72 'IPython.kernel.enginefc.IFCControllerBase'
72 'IPython.kernel.enginefc.IFCControllerBase'
73 ]
73 ]
74
74
75 default_engine_interfaces.Default.furl_file = u'ipcontroller-engine.furl'
75 default_engine_interfaces.Default.furl_file = u'ipcontroller-engine.furl'
76
76
77 # Make this a dict we can pass to Config.__init__ for the default
77 # Make this a dict we can pass to Config.__init__ for the default
78 default_engine_interfaces = dict(copy.deepcopy(default_engine_interfaces.items()))
78 default_engine_interfaces = dict(copy.deepcopy(default_engine_interfaces.items()))
79
79
80
80
81 #-----------------------------------------------------------------------------
81 #-----------------------------------------------------------------------------
82 # Service factories
82 # Service factories
83 #-----------------------------------------------------------------------------
83 #-----------------------------------------------------------------------------
84
84
85
85
86 class FCClientServiceFactory(FCServiceFactory):
86 class FCClientServiceFactory(FCServiceFactory):
87 """A Foolscap implementation of the client services."""
87 """A Foolscap implementation of the client services."""
88
88
89 cert_file = Unicode(u'ipcontroller-client.pem', config=True)
89 cert_file = Unicode(u'ipcontroller-client.pem', config=True)
90 interfaces = Instance(klass=Config, kw=default_client_interfaces,
90 interfaces = Instance(klass=Config, kw=default_client_interfaces,
91 allow_none=False, config=True)
91 allow_none=False, config=True)
92
92
93
93
94 class FCEngineServiceFactory(FCServiceFactory):
94 class FCEngineServiceFactory(FCServiceFactory):
95 """A Foolscap implementation of the engine services."""
95 """A Foolscap implementation of the engine services."""
96
96
97 cert_file = Unicode(u'ipcontroller-engine.pem', config=True)
97 cert_file = Unicode(u'ipcontroller-engine.pem', config=True)
98 interfaces = Instance(klass=dict, kw=default_engine_interfaces,
98 interfaces = Instance(klass=dict, kw=default_engine_interfaces,
99 allow_none=False, config=True)
99 allow_none=False, config=True)
100
100
101
101
102 #-----------------------------------------------------------------------------
102 #-----------------------------------------------------------------------------
103 # The main application
103 # The main application
104 #-----------------------------------------------------------------------------
104 #-----------------------------------------------------------------------------
105
105
106
106
107 cl_args = (
107 cl_args = (
108 # Client config
108 # Client config
109 (('--client-ip',), dict(
109 (('--client-ip',), dict(
110 type=str, dest='FCClientServiceFactory.ip', default=NoConfigDefault,
110 type=str, dest='FCClientServiceFactory.ip', default=NoConfigDefault,
111 help='The IP address or hostname the controller will listen on for '
111 help='The IP address or hostname the controller will listen on for '
112 'client connections.',
112 'client connections.',
113 metavar='FCClientServiceFactory.ip')
113 metavar='FCClientServiceFactory.ip')
114 ),
114 ),
115 (('--client-port',), dict(
115 (('--client-port',), dict(
116 type=int, dest='FCClientServiceFactory.port', default=NoConfigDefault,
116 type=int, dest='FCClientServiceFactory.port', default=NoConfigDefault,
117 help='The port the controller will listen on for client connections. '
117 help='The port the controller will listen on for client connections. '
118 'The default is to use 0, which will autoselect an open port.',
118 'The default is to use 0, which will autoselect an open port.',
119 metavar='FCClientServiceFactory.port')
119 metavar='FCClientServiceFactory.port')
120 ),
120 ),
121 (('--client-location',), dict(
121 (('--client-location',), dict(
122 type=str, dest='FCClientServiceFactory.location', default=NoConfigDefault,
122 type=str, dest='FCClientServiceFactory.location', default=NoConfigDefault,
123 help='The hostname or IP that clients should connect to. This does '
123 help='The hostname or IP that clients should connect to. This does '
124 'not control which interface the controller listens on. Instead, this '
124 'not control which interface the controller listens on. Instead, this '
125 'determines the hostname/IP that is listed in the FURL, which is how '
125 'determines the hostname/IP that is listed in the FURL, which is how '
126 'clients know where to connect. Useful if the controller is listening '
126 'clients know where to connect. Useful if the controller is listening '
127 'on multiple interfaces.',
127 'on multiple interfaces.',
128 metavar='FCClientServiceFactory.location')
128 metavar='FCClientServiceFactory.location')
129 ),
129 ),
130 # Engine config
130 # Engine config
131 (('--engine-ip',), dict(
131 (('--engine-ip',), dict(
132 type=str, dest='FCEngineServiceFactory.ip', default=NoConfigDefault,
132 type=str, dest='FCEngineServiceFactory.ip', default=NoConfigDefault,
133 help='The IP address or hostname the controller will listen on for '
133 help='The IP address or hostname the controller will listen on for '
134 'engine connections.',
134 'engine connections.',
135 metavar='FCEngineServiceFactory.ip')
135 metavar='FCEngineServiceFactory.ip')
136 ),
136 ),
137 (('--engine-port',), dict(
137 (('--engine-port',), dict(
138 type=int, dest='FCEngineServiceFactory.port', default=NoConfigDefault,
138 type=int, dest='FCEngineServiceFactory.port', default=NoConfigDefault,
139 help='The port the controller will listen on for engine connections. '
139 help='The port the controller will listen on for engine connections. '
140 'The default is to use 0, which will autoselect an open port.',
140 'The default is to use 0, which will autoselect an open port.',
141 metavar='FCEngineServiceFactory.port')
141 metavar='FCEngineServiceFactory.port')
142 ),
142 ),
143 (('--engine-location',), dict(
143 (('--engine-location',), dict(
144 type=str, dest='FCEngineServiceFactory.location', default=NoConfigDefault,
144 type=str, dest='FCEngineServiceFactory.location', default=NoConfigDefault,
145 help='The hostname or IP that engines should connect to. This does '
145 help='The hostname or IP that engines should connect to. This does '
146 'not control which interface the controller listens on. Instead, this '
146 'not control which interface the controller listens on. Instead, this '
147 'determines the hostname/IP that is listed in the FURL, which is how '
147 'determines the hostname/IP that is listed in the FURL, which is how '
148 'engines know where to connect. Useful if the controller is listening '
148 'engines know where to connect. Useful if the controller is listening '
149 'on multiple interfaces.',
149 'on multiple interfaces.',
150 metavar='FCEngineServiceFactory.location')
150 metavar='FCEngineServiceFactory.location')
151 ),
151 ),
152 # Global config
152 # Global config
153 (('--log-to-file',), dict(
153 (('--log-to-file',), dict(
154 action='store_true', dest='Global.log_to_file', default=NoConfigDefault,
154 action='store_true', dest='Global.log_to_file', default=NoConfigDefault,
155 help='Log to a file in the log directory (default is stdout)')
155 help='Log to a file in the log directory (default is stdout)')
156 ),
156 ),
157 (('-r','--reuse-furls'), dict(
157 (('-r','--reuse-furls'), dict(
158 action='store_true', dest='Global.reuse_furls', default=NoConfigDefault,
158 action='store_true', dest='Global.reuse_furls', default=NoConfigDefault,
159 help='Try to reuse all FURL files. If this is not set all FURL files '
159 help='Try to reuse all FURL files. If this is not set all FURL files '
160 'are deleted before the controller starts. This must be set if '
160 'are deleted before the controller starts. This must be set if '
161 'specific ports are specified by --engine-port or --client-port.')
161 'specific ports are specified by --engine-port or --client-port.')
162 ),
162 ),
163 (('--no-secure',), dict(
163 (('--no-secure',), dict(
164 action='store_false', dest='Global.secure', default=NoConfigDefault,
164 action='store_false', dest='Global.secure', default=NoConfigDefault,
165 help='Turn off SSL encryption for all connections.')
165 help='Turn off SSL encryption for all connections.')
166 ),
166 ),
167 (('--secure',), dict(
167 (('--secure',), dict(
168 action='store_true', dest='Global.secure', default=NoConfigDefault,
168 action='store_true', dest='Global.secure', default=NoConfigDefault,
169 help='Turn off SSL encryption for all connections.')
169 help='Turn off SSL encryption for all connections.')
170 )
170 )
171 )
171 )
172
172
173
173
174 class IPControllerAppCLConfigLoader(AppWithClusterDirArgParseConfigLoader):
174 class IPControllerAppCLConfigLoader(AppWithClusterDirArgParseConfigLoader):
175
175
176 arguments = cl_args
176 arguments = cl_args
177
177
178
178
179 default_config_file_name = u'ipcontroller_config.py'
179 default_config_file_name = u'ipcontroller_config.py'
180
180
181
181
182 class IPControllerApp(ApplicationWithClusterDir):
182 class IPControllerApp(ApplicationWithClusterDir):
183
183
184 name = u'ipcontroller'
184 name = u'ipcontroller'
185 description = 'Start the IPython controller for parallel computing.'
185 description = 'Start the IPython controller for parallel computing.'
186 config_file_name = default_config_file_name
186 config_file_name = default_config_file_name
187 auto_create_cluster_dir = True
187 auto_create_cluster_dir = True
188
188
189 def create_default_config(self):
189 def create_default_config(self):
190 super(IPControllerApp, self).create_default_config()
190 super(IPControllerApp, self).create_default_config()
191 self.default_config.Global.reuse_furls = False
191 self.default_config.Global.reuse_furls = False
192 self.default_config.Global.secure = True
192 self.default_config.Global.secure = True
193 self.default_config.Global.import_statements = []
193 self.default_config.Global.import_statements = []
194 self.default_config.Global.clean_logs = True
194 self.default_config.Global.clean_logs = True
195
195
196 def create_command_line_config(self):
196 def create_command_line_config(self):
197 """Create and return a command line config loader."""
197 """Create and return a command line config loader."""
198 return IPControllerAppCLConfigLoader(
198 return IPControllerAppCLConfigLoader(
199 description=self.description,
199 description=self.description,
200 version=release.version
200 version=release.version
201 )
201 )
202
202
203 def post_load_command_line_config(self):
203 def post_load_command_line_config(self):
204 # Now setup reuse_furls
204 # Now setup reuse_furls
205 c = self.command_line_config
205 c = self.command_line_config
206 if hasattr(c.Global, 'reuse_furls'):
206 if hasattr(c.Global, 'reuse_furls'):
207 c.FCClientServiceFactory.reuse_furls = c.Global.reuse_furls
207 c.FCClientServiceFactory.reuse_furls = c.Global.reuse_furls
208 c.FCEngineServiceFactory.reuse_furls = c.Global.reuse_furls
208 c.FCEngineServiceFactory.reuse_furls = c.Global.reuse_furls
209 del c.Global.reuse_furls
209 del c.Global.reuse_furls
210 if hasattr(c.Global, 'secure'):
210 if hasattr(c.Global, 'secure'):
211 c.FCClientServiceFactory.secure = c.Global.secure
211 c.FCClientServiceFactory.secure = c.Global.secure
212 c.FCEngineServiceFactory.secure = c.Global.secure
212 c.FCEngineServiceFactory.secure = c.Global.secure
213 del c.Global.secure
213 del c.Global.secure
214
214
215 def construct(self):
215 def construct(self):
216 # I am a little hesitant to put these into InteractiveShell itself.
216 # This is the working dir by now.
217 # But that might be the place for them
218 sys.path.insert(0, '')
217 sys.path.insert(0, '')
219
218
220 self.start_logging()
219 self.start_logging()
221 self.import_statements()
220 self.import_statements()
222
221
223 # Create the service hierarchy
222 # Create the service hierarchy
224 self.main_service = service.MultiService()
223 self.main_service = service.MultiService()
225 # The controller service
224 # The controller service
226 controller_service = controllerservice.ControllerService()
225 controller_service = controllerservice.ControllerService()
227 controller_service.setServiceParent(self.main_service)
226 controller_service.setServiceParent(self.main_service)
228 # The client tub and all its refereceables
227 # The client tub and all its refereceables
229 csfactory = FCClientServiceFactory(self.master_config, controller_service)
228 csfactory = FCClientServiceFactory(self.master_config, controller_service)
230 client_service = csfactory.create()
229 client_service = csfactory.create()
231 client_service.setServiceParent(self.main_service)
230 client_service.setServiceParent(self.main_service)
232 # The engine tub
231 # The engine tub
233 esfactory = FCEngineServiceFactory(self.master_config, controller_service)
232 esfactory = FCEngineServiceFactory(self.master_config, controller_service)
234 engine_service = esfactory.create()
233 engine_service = esfactory.create()
235 engine_service.setServiceParent(self.main_service)
234 engine_service.setServiceParent(self.main_service)
236
235
237 def import_statements(self):
236 def import_statements(self):
238 statements = self.master_config.Global.import_statements
237 statements = self.master_config.Global.import_statements
239 for s in statements:
238 for s in statements:
240 try:
239 try:
241 log.msg("Executing statement: '%s'" % s)
240 log.msg("Executing statement: '%s'" % s)
242 exec s in globals(), locals()
241 exec s in globals(), locals()
243 except:
242 except:
244 log.msg("Error running statement: %s" % s)
243 log.msg("Error running statement: %s" % s)
245
244
246 def start_app(self):
245 def start_app(self):
247 # Start the controller service.
246 # Start the controller service.
248 self.main_service.startService()
247 self.main_service.startService()
249 # Write the .pid file overwriting old ones. This allow multiple
248 # Write the .pid file overwriting old ones. This allow multiple
250 # controllers to clober each other. But Windows is not cleaning
249 # controllers to clober each other. But Windows is not cleaning
251 # these up properly.
250 # these up properly.
252 self.write_pid_file(overwrite=True)
251 self.write_pid_file(overwrite=True)
253 # cd to the cluster_dir as our working directory.
254 os.chdir(self.master_config.Global.cluster_dir)
255 # Add a trigger to delete the .pid file upon shutting down.
252 # Add a trigger to delete the .pid file upon shutting down.
256 reactor.addSystemEventTrigger('during','shutdown', self.remove_pid_file)
253 reactor.addSystemEventTrigger('during','shutdown', self.remove_pid_file)
257 reactor.run()
254 reactor.run()
258
255
259
256
260 def launch_new_instance():
257 def launch_new_instance():
261 """Create and run the IPython controller"""
258 """Create and run the IPython controller"""
262 app = IPControllerApp()
259 app = IPControllerApp()
263 app.start()
260 app.start()
264
261
265
262
266 if __name__ == '__main__':
263 if __name__ == '__main__':
267 launch_new_instance()
264 launch_new_instance()
268
265
@@ -1,240 +1,237 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 """
3 """
4 The IPython controller application
4 The IPython controller application
5 """
5 """
6
6
7 #-----------------------------------------------------------------------------
7 #-----------------------------------------------------------------------------
8 # Copyright (C) 2008-2009 The IPython Development Team
8 # Copyright (C) 2008-2009 The IPython Development Team
9 #
9 #
10 # Distributed under the terms of the BSD License. The full license is in
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
11 # the file COPYING, distributed as part of this software.
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13
13
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 # Imports
15 # Imports
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17
17
18 import os
18 import os
19 import sys
19 import sys
20
20
21 from twisted.application import service
21 from twisted.application import service
22 from twisted.internet import reactor
22 from twisted.internet import reactor
23 from twisted.python import log
23 from twisted.python import log
24
24
25 from IPython.config.loader import NoConfigDefault
25 from IPython.config.loader import NoConfigDefault
26
26
27 from IPython.kernel.clusterdir import (
27 from IPython.kernel.clusterdir import (
28 ApplicationWithClusterDir,
28 ApplicationWithClusterDir,
29 AppWithClusterDirArgParseConfigLoader
29 AppWithClusterDirArgParseConfigLoader
30 )
30 )
31 from IPython.core import release
31 from IPython.core import release
32
32
33 from IPython.utils.importstring import import_item
33 from IPython.utils.importstring import import_item
34
34
35 from IPython.kernel.engineservice import EngineService
35 from IPython.kernel.engineservice import EngineService
36 from IPython.kernel.fcutil import Tub
36 from IPython.kernel.fcutil import Tub
37 from IPython.kernel.engineconnector import EngineConnector
37 from IPython.kernel.engineconnector import EngineConnector
38
38
39 #-----------------------------------------------------------------------------
39 #-----------------------------------------------------------------------------
40 # The main application
40 # The main application
41 #-----------------------------------------------------------------------------
41 #-----------------------------------------------------------------------------
42
42
43
43
44 cl_args = (
44 cl_args = (
45 # Controller config
45 # Controller config
46 (('--furl-file',), dict(
46 (('--furl-file',), dict(
47 type=unicode, dest='Global.furl_file', default=NoConfigDefault,
47 type=unicode, dest='Global.furl_file', default=NoConfigDefault,
48 help='The full location of the file containing the FURL of the '
48 help='The full location of the file containing the FURL of the '
49 'controller. If this is not given, the FURL file must be in the '
49 'controller. If this is not given, the FURL file must be in the '
50 'security directory of the cluster directory. This location is '
50 'security directory of the cluster directory. This location is '
51 'resolved using the --profile and --app-dir options.',
51 'resolved using the --profile and --app-dir options.',
52 metavar='Global.furl_file')
52 metavar='Global.furl_file')
53 ),
53 ),
54 # MPI
54 # MPI
55 (('--mpi',), dict(
55 (('--mpi',), dict(
56 type=str, dest='MPI.use', default=NoConfigDefault,
56 type=str, dest='MPI.use', default=NoConfigDefault,
57 help='How to enable MPI (mpi4py, pytrilinos, or empty string to disable).',
57 help='How to enable MPI (mpi4py, pytrilinos, or empty string to disable).',
58 metavar='MPI.use')
58 metavar='MPI.use')
59 ),
59 ),
60 # Global config
60 # Global config
61 (('--log-to-file',), dict(
61 (('--log-to-file',), dict(
62 action='store_true', dest='Global.log_to_file', default=NoConfigDefault,
62 action='store_true', dest='Global.log_to_file', default=NoConfigDefault,
63 help='Log to a file in the log directory (default is stdout)')
63 help='Log to a file in the log directory (default is stdout)')
64 )
64 )
65 )
65 )
66
66
67
67
68 class IPEngineAppCLConfigLoader(AppWithClusterDirArgParseConfigLoader):
68 class IPEngineAppCLConfigLoader(AppWithClusterDirArgParseConfigLoader):
69
69
70 arguments = cl_args
70 arguments = cl_args
71
71
72
72
73 mpi4py_init = """from mpi4py import MPI as mpi
73 mpi4py_init = """from mpi4py import MPI as mpi
74 mpi.size = mpi.COMM_WORLD.Get_size()
74 mpi.size = mpi.COMM_WORLD.Get_size()
75 mpi.rank = mpi.COMM_WORLD.Get_rank()
75 mpi.rank = mpi.COMM_WORLD.Get_rank()
76 """
76 """
77
77
78 pytrilinos_init = """from PyTrilinos import Epetra
78 pytrilinos_init = """from PyTrilinos import Epetra
79 class SimpleStruct:
79 class SimpleStruct:
80 pass
80 pass
81 mpi = SimpleStruct()
81 mpi = SimpleStruct()
82 mpi.rank = 0
82 mpi.rank = 0
83 mpi.size = 0
83 mpi.size = 0
84 """
84 """
85
85
86
86
87 default_config_file_name = u'ipengine_config.py'
87 default_config_file_name = u'ipengine_config.py'
88
88
89
89
90 class IPEngineApp(ApplicationWithClusterDir):
90 class IPEngineApp(ApplicationWithClusterDir):
91
91
92 name = u'ipengine'
92 name = u'ipengine'
93 description = 'Start the IPython engine for parallel computing.'
93 description = 'Start the IPython engine for parallel computing.'
94 config_file_name = default_config_file_name
94 config_file_name = default_config_file_name
95 auto_create_cluster_dir = True
95 auto_create_cluster_dir = True
96
96
97 def create_default_config(self):
97 def create_default_config(self):
98 super(IPEngineApp, self).create_default_config()
98 super(IPEngineApp, self).create_default_config()
99
99
100 # The engine should not clean logs as we don't want to remove the
100 # The engine should not clean logs as we don't want to remove the
101 # active log files of other running engines.
101 # active log files of other running engines.
102 self.default_config.Global.clean_logs = False
102 self.default_config.Global.clean_logs = False
103
103
104 # Global config attributes
104 # Global config attributes
105 self.default_config.Global.exec_lines = []
105 self.default_config.Global.exec_lines = []
106 self.default_config.Global.shell_class = 'IPython.kernel.core.interpreter.Interpreter'
106 self.default_config.Global.shell_class = 'IPython.kernel.core.interpreter.Interpreter'
107
107
108 # Configuration related to the controller
108 # Configuration related to the controller
109 # This must match the filename (path not included) that the controller
109 # This must match the filename (path not included) that the controller
110 # used for the FURL file.
110 # used for the FURL file.
111 self.default_config.Global.furl_file_name = u'ipcontroller-engine.furl'
111 self.default_config.Global.furl_file_name = u'ipcontroller-engine.furl'
112 # If given, this is the actual location of the controller's FURL file.
112 # If given, this is the actual location of the controller's FURL file.
113 # If not, this is computed using the profile, app_dir and furl_file_name
113 # If not, this is computed using the profile, app_dir and furl_file_name
114 self.default_config.Global.furl_file = u''
114 self.default_config.Global.furl_file = u''
115
115
116 # The max number of connection attemps and the initial delay between
116 # The max number of connection attemps and the initial delay between
117 # those attemps.
117 # those attemps.
118 self.default_config.Global.connect_delay = 0.1
118 self.default_config.Global.connect_delay = 0.1
119 self.default_config.Global.connect_max_tries = 15
119 self.default_config.Global.connect_max_tries = 15
120
120
121 # MPI related config attributes
121 # MPI related config attributes
122 self.default_config.MPI.use = ''
122 self.default_config.MPI.use = ''
123 self.default_config.MPI.mpi4py = mpi4py_init
123 self.default_config.MPI.mpi4py = mpi4py_init
124 self.default_config.MPI.pytrilinos = pytrilinos_init
124 self.default_config.MPI.pytrilinos = pytrilinos_init
125
125
126 def create_command_line_config(self):
126 def create_command_line_config(self):
127 """Create and return a command line config loader."""
127 """Create and return a command line config loader."""
128 return IPEngineAppCLConfigLoader(
128 return IPEngineAppCLConfigLoader(
129 description=self.description,
129 description=self.description,
130 version=release.version
130 version=release.version
131 )
131 )
132
132
133 def post_load_command_line_config(self):
133 def post_load_command_line_config(self):
134 pass
134 pass
135
135
136 def pre_construct(self):
136 def pre_construct(self):
137 super(IPEngineApp, self).pre_construct()
137 super(IPEngineApp, self).pre_construct()
138 self.find_cont_furl_file()
138 self.find_cont_furl_file()
139
139
140 def find_cont_furl_file(self):
140 def find_cont_furl_file(self):
141 """Set the furl file.
141 """Set the furl file.
142
142
143 Here we don't try to actually see if it exists for is valid as that
143 Here we don't try to actually see if it exists for is valid as that
144 is hadled by the connection logic.
144 is hadled by the connection logic.
145 """
145 """
146 config = self.master_config
146 config = self.master_config
147 # Find the actual controller FURL file
147 # Find the actual controller FURL file
148 if not config.Global.furl_file:
148 if not config.Global.furl_file:
149 try_this = os.path.join(
149 try_this = os.path.join(
150 config.Global.cluster_dir,
150 config.Global.cluster_dir,
151 config.Global.security_dir,
151 config.Global.security_dir,
152 config.Global.furl_file_name
152 config.Global.furl_file_name
153 )
153 )
154 config.Global.furl_file = try_this
154 config.Global.furl_file = try_this
155
155
156 def construct(self):
156 def construct(self):
157 # I am a little hesitant to put these into InteractiveShell itself.
157 # This is the working dir by now.
158 # But that might be the place for them
159 sys.path.insert(0, '')
158 sys.path.insert(0, '')
160
159
161 self.start_mpi()
160 self.start_mpi()
162 self.start_logging()
161 self.start_logging()
163
162
164 # Create the underlying shell class and EngineService
163 # Create the underlying shell class and EngineService
165 shell_class = import_item(self.master_config.Global.shell_class)
164 shell_class = import_item(self.master_config.Global.shell_class)
166 self.engine_service = EngineService(shell_class, mpi=mpi)
165 self.engine_service = EngineService(shell_class, mpi=mpi)
167
166
168 self.exec_lines()
167 self.exec_lines()
169
168
170 # Create the service hierarchy
169 # Create the service hierarchy
171 self.main_service = service.MultiService()
170 self.main_service = service.MultiService()
172 self.engine_service.setServiceParent(self.main_service)
171 self.engine_service.setServiceParent(self.main_service)
173 self.tub_service = Tub()
172 self.tub_service = Tub()
174 self.tub_service.setServiceParent(self.main_service)
173 self.tub_service.setServiceParent(self.main_service)
175 # This needs to be called before the connection is initiated
174 # This needs to be called before the connection is initiated
176 self.main_service.startService()
175 self.main_service.startService()
177
176
178 # This initiates the connection to the controller and calls
177 # This initiates the connection to the controller and calls
179 # register_engine to tell the controller we are ready to do work
178 # register_engine to tell the controller we are ready to do work
180 self.engine_connector = EngineConnector(self.tub_service)
179 self.engine_connector = EngineConnector(self.tub_service)
181
180
182 log.msg("Using furl file: %s" % self.master_config.Global.furl_file)
181 log.msg("Using furl file: %s" % self.master_config.Global.furl_file)
183
182
184 reactor.callWhenRunning(self.call_connect)
183 reactor.callWhenRunning(self.call_connect)
185
184
186 def call_connect(self):
185 def call_connect(self):
187 d = self.engine_connector.connect_to_controller(
186 d = self.engine_connector.connect_to_controller(
188 self.engine_service,
187 self.engine_service,
189 self.master_config.Global.furl_file,
188 self.master_config.Global.furl_file,
190 self.master_config.Global.connect_delay,
189 self.master_config.Global.connect_delay,
191 self.master_config.Global.connect_max_tries
190 self.master_config.Global.connect_max_tries
192 )
191 )
193
192
194 def handle_error(f):
193 def handle_error(f):
195 log.msg('Error connecting to controller. This usually means that '
194 log.msg('Error connecting to controller. This usually means that '
196 'i) the controller was not started, ii) a firewall was blocking '
195 'i) the controller was not started, ii) a firewall was blocking '
197 'the engine from connecting to the controller or iii) the engine '
196 'the engine from connecting to the controller or iii) the engine '
198 ' was not pointed at the right FURL file:')
197 ' was not pointed at the right FURL file:')
199 log.msg(f.getErrorMessage())
198 log.msg(f.getErrorMessage())
200 reactor.callLater(0.1, reactor.stop)
199 reactor.callLater(0.1, reactor.stop)
201
200
202 d.addErrback(handle_error)
201 d.addErrback(handle_error)
203
202
204 def start_mpi(self):
203 def start_mpi(self):
205 global mpi
204 global mpi
206 mpikey = self.master_config.MPI.use
205 mpikey = self.master_config.MPI.use
207 mpi_import_statement = self.master_config.MPI.get(mpikey, None)
206 mpi_import_statement = self.master_config.MPI.get(mpikey, None)
208 if mpi_import_statement is not None:
207 if mpi_import_statement is not None:
209 try:
208 try:
210 self.log.info("Initializing MPI:")
209 self.log.info("Initializing MPI:")
211 self.log.info(mpi_import_statement)
210 self.log.info(mpi_import_statement)
212 exec mpi_import_statement in globals()
211 exec mpi_import_statement in globals()
213 except:
212 except:
214 mpi = None
213 mpi = None
215 else:
214 else:
216 mpi = None
215 mpi = None
217
216
218 def exec_lines(self):
217 def exec_lines(self):
219 for line in self.master_config.Global.exec_lines:
218 for line in self.master_config.Global.exec_lines:
220 try:
219 try:
221 log.msg("Executing statement: '%s'" % line)
220 log.msg("Executing statement: '%s'" % line)
222 self.engine_service.execute(line)
221 self.engine_service.execute(line)
223 except:
222 except:
224 log.msg("Error executing statement: %s" % line)
223 log.msg("Error executing statement: %s" % line)
225
224
226 def start_app(self):
225 def start_app(self):
227 # cd to the cluster_dir as our working directory.
228 os.chdir(self.master_config.Global.cluster_dir)
229 reactor.run()
226 reactor.run()
230
227
231
228
232 def launch_new_instance():
229 def launch_new_instance():
233 """Create and run the IPython controller"""
230 """Create and run the IPython controller"""
234 app = IPEngineApp()
231 app = IPEngineApp()
235 app.start()
232 app.start()
236
233
237
234
238 if __name__ == '__main__':
235 if __name__ == '__main__':
239 launch_new_instance()
236 launch_new_instance()
240
237
@@ -1,815 +1,820 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 """
3 """
4 Facilities for launching processing asynchronously.
4 Facilities for launching processing asynchronously.
5 """
5 """
6
6
7 #-----------------------------------------------------------------------------
7 #-----------------------------------------------------------------------------
8 # Copyright (C) 2008-2009 The IPython Development Team
8 # Copyright (C) 2008-2009 The IPython Development Team
9 #
9 #
10 # Distributed under the terms of the BSD License. The full license is in
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
11 # the file COPYING, distributed as part of this software.
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13
13
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 # Imports
15 # Imports
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17
17
18 import os
18 import os
19 import re
19 import re
20 import sys
20 import sys
21
21
22 from IPython.core.component import Component
22 from IPython.core.component import Component
23 from IPython.external import Itpl
23 from IPython.external import Itpl
24 from IPython.utils.traitlets import Str, Int, List, Unicode, Enum
24 from IPython.utils.traitlets import Str, Int, List, Unicode, Enum
25 from IPython.utils.platutils import find_cmd
25 from IPython.utils.platutils import find_cmd
26 from IPython.kernel.twistedutil import gatherBoth, make_deferred, sleep_deferred
26 from IPython.kernel.twistedutil import gatherBoth, make_deferred, sleep_deferred
27 from IPython.kernel.winhpcjob import (
27 from IPython.kernel.winhpcjob import (
28 WinHPCJob, WinHPCTask,
28 WinHPCJob, WinHPCTask,
29 IPControllerTask, IPEngineTask
29 IPControllerTask, IPEngineTask
30 )
30 )
31
31
32 from twisted.internet import reactor, defer
32 from twisted.internet import reactor, defer
33 from twisted.internet.defer import inlineCallbacks
33 from twisted.internet.defer import inlineCallbacks
34 from twisted.internet.protocol import ProcessProtocol
34 from twisted.internet.protocol import ProcessProtocol
35 from twisted.internet.utils import getProcessOutput
35 from twisted.internet.utils import getProcessOutput
36 from twisted.internet.error import ProcessDone, ProcessTerminated
36 from twisted.internet.error import ProcessDone, ProcessTerminated
37 from twisted.python import log
37 from twisted.python import log
38 from twisted.python.failure import Failure
38 from twisted.python.failure import Failure
39
39
40 #-----------------------------------------------------------------------------
40 #-----------------------------------------------------------------------------
41 # Generic launchers
41 # Generic launchers
42 #-----------------------------------------------------------------------------
42 #-----------------------------------------------------------------------------
43
43
44
44
45 class LauncherError(Exception):
45 class LauncherError(Exception):
46 pass
46 pass
47
47
48
48
49 class ProcessStateError(LauncherError):
49 class ProcessStateError(LauncherError):
50 pass
50 pass
51
51
52
52
53 class UnknownStatus(LauncherError):
53 class UnknownStatus(LauncherError):
54 pass
54 pass
55
55
56
56
57 class BaseLauncher(Component):
57 class BaseLauncher(Component):
58 """An asbtraction for starting, stopping and signaling a process."""
58 """An asbtraction for starting, stopping and signaling a process."""
59
59
60 # A directory for files related to the process. But, we don't cd to
60 # A directory for files related to the process. But, we don't cd to
61 # this directory,
61 # this directory,
62 working_dir = Unicode(u'')
62 working_dir = Unicode(u'')
63
63
64 def __init__(self, working_dir, parent=None, name=None, config=None):
64 def __init__(self, working_dir, parent=None, name=None, config=None):
65 super(BaseLauncher, self).__init__(parent, name, config)
65 super(BaseLauncher, self).__init__(parent, name, config)
66 self.working_dir = working_dir
66 self.working_dir = working_dir
67 self.state = 'before' # can be before, running, after
67 self.state = 'before' # can be before, running, after
68 self.stop_deferreds = []
68 self.stop_deferreds = []
69 self.start_data = None
69 self.start_data = None
70 self.stop_data = None
70 self.stop_data = None
71
71
72 @property
72 @property
73 def args(self):
73 def args(self):
74 """A list of cmd and args that will be used to start the process.
74 """A list of cmd and args that will be used to start the process.
75
75
76 This is what is passed to :func:`spawnProcess` and the first element
76 This is what is passed to :func:`spawnProcess` and the first element
77 will be the process name.
77 will be the process name.
78 """
78 """
79 return self.find_args()
79 return self.find_args()
80
80
81 def find_args(self):
81 def find_args(self):
82 """The ``.args`` property calls this to find the args list.
82 """The ``.args`` property calls this to find the args list.
83
83
84 Subcommand should implement this to construct the cmd and args.
84 Subcommand should implement this to construct the cmd and args.
85 """
85 """
86 raise NotImplementedError('find_args must be implemented in a subclass')
86 raise NotImplementedError('find_args must be implemented in a subclass')
87
87
88 @property
88 @property
89 def arg_str(self):
89 def arg_str(self):
90 """The string form of the program arguments."""
90 """The string form of the program arguments."""
91 return ' '.join(self.args)
91 return ' '.join(self.args)
92
92
93 @property
93 @property
94 def running(self):
94 def running(self):
95 """Am I running."""
95 """Am I running."""
96 if self.state == 'running':
96 if self.state == 'running':
97 return True
97 return True
98 else:
98 else:
99 return False
99 return False
100
100
101 def start(self):
101 def start(self):
102 """Start the process.
102 """Start the process.
103
103
104 This must return a deferred that fires with information about the
104 This must return a deferred that fires with information about the
105 process starting (like a pid, job id, etc.).
105 process starting (like a pid, job id, etc.).
106 """
106 """
107 return defer.fail(
107 return defer.fail(
108 Failure(NotImplementedError(
108 Failure(NotImplementedError(
109 'start must be implemented in a subclass')
109 'start must be implemented in a subclass')
110 )
110 )
111 )
111 )
112
112
113 def stop(self):
113 def stop(self):
114 """Stop the process and notify observers of stopping.
114 """Stop the process and notify observers of stopping.
115
115
116 This must return a deferred that fires with information about the
116 This must return a deferred that fires with information about the
117 processing stopping, like errors that occur while the process is
117 processing stopping, like errors that occur while the process is
118 attempting to be shut down. This deferred won't fire when the process
118 attempting to be shut down. This deferred won't fire when the process
119 actually stops. To observe the actual process stopping, see
119 actually stops. To observe the actual process stopping, see
120 :func:`observe_stop`.
120 :func:`observe_stop`.
121 """
121 """
122 return defer.fail(
122 return defer.fail(
123 Failure(NotImplementedError(
123 Failure(NotImplementedError(
124 'stop must be implemented in a subclass')
124 'stop must be implemented in a subclass')
125 )
125 )
126 )
126 )
127
127
128 def observe_stop(self):
128 def observe_stop(self):
129 """Get a deferred that will fire when the process stops.
129 """Get a deferred that will fire when the process stops.
130
130
131 The deferred will fire with data that contains information about
131 The deferred will fire with data that contains information about
132 the exit status of the process.
132 the exit status of the process.
133 """
133 """
134 if self.state=='after':
134 if self.state=='after':
135 return defer.succeed(self.stop_data)
135 return defer.succeed(self.stop_data)
136 else:
136 else:
137 d = defer.Deferred()
137 d = defer.Deferred()
138 self.stop_deferreds.append(d)
138 self.stop_deferreds.append(d)
139 return d
139 return d
140
140
141 def notify_start(self, data):
141 def notify_start(self, data):
142 """Call this to trigger startup actions.
142 """Call this to trigger startup actions.
143
143
144 This logs the process startup and sets the state to 'running'. It is
144 This logs the process startup and sets the state to 'running'. It is
145 a pass-through so it can be used as a callback.
145 a pass-through so it can be used as a callback.
146 """
146 """
147
147
148 log.msg('Process %r started: %r' % (self.args[0], data))
148 log.msg('Process %r started: %r' % (self.args[0], data))
149 self.start_data = data
149 self.start_data = data
150 self.state = 'running'
150 self.state = 'running'
151 return data
151 return data
152
152
153 def notify_stop(self, data):
153 def notify_stop(self, data):
154 """Call this to trigger process stop actions.
154 """Call this to trigger process stop actions.
155
155
156 This logs the process stopping and sets the state to 'after'. Call
156 This logs the process stopping and sets the state to 'after'. Call
157 this to trigger all the deferreds from :func:`observe_stop`."""
157 this to trigger all the deferreds from :func:`observe_stop`."""
158
158
159 log.msg('Process %r stopped: %r' % (self.args[0], data))
159 log.msg('Process %r stopped: %r' % (self.args[0], data))
160 self.stop_data = data
160 self.stop_data = data
161 self.state = 'after'
161 self.state = 'after'
162 for i in range(len(self.stop_deferreds)):
162 for i in range(len(self.stop_deferreds)):
163 d = self.stop_deferreds.pop()
163 d = self.stop_deferreds.pop()
164 d.callback(data)
164 d.callback(data)
165 return data
165 return data
166
166
167 def signal(self, sig):
167 def signal(self, sig):
168 """Signal the process.
168 """Signal the process.
169
169
170 Return a semi-meaningless deferred after signaling the process.
170 Return a semi-meaningless deferred after signaling the process.
171
171
172 Parameters
172 Parameters
173 ----------
173 ----------
174 sig : str or int
174 sig : str or int
175 'KILL', 'INT', etc., or any signal number
175 'KILL', 'INT', etc., or any signal number
176 """
176 """
177 return defer.fail(
177 return defer.fail(
178 Failure(NotImplementedError(
178 Failure(NotImplementedError(
179 'signal must be implemented in a subclass')
179 'signal must be implemented in a subclass')
180 )
180 )
181 )
181 )
182
182
183
183
184 class LocalProcessLauncherProtocol(ProcessProtocol):
184 class LocalProcessLauncherProtocol(ProcessProtocol):
185 """A ProcessProtocol to go with the LocalProcessLauncher."""
185 """A ProcessProtocol to go with the LocalProcessLauncher."""
186
186
187 def __init__(self, process_launcher):
187 def __init__(self, process_launcher):
188 self.process_launcher = process_launcher
188 self.process_launcher = process_launcher
189 self.pid = None
189 self.pid = None
190
190
191 def connectionMade(self):
191 def connectionMade(self):
192 self.pid = self.transport.pid
192 self.pid = self.transport.pid
193 self.process_launcher.notify_start(self.transport.pid)
193 self.process_launcher.notify_start(self.transport.pid)
194
194
195 def processEnded(self, status):
195 def processEnded(self, status):
196 value = status.value
196 value = status.value
197 if isinstance(value, ProcessDone):
197 if isinstance(value, ProcessDone):
198 self.process_launcher.notify_stop(
198 self.process_launcher.notify_stop(
199 {'exit_code':0,
199 {'exit_code':0,
200 'signal':None,
200 'signal':None,
201 'status':None,
201 'status':None,
202 'pid':self.pid
202 'pid':self.pid
203 }
203 }
204 )
204 )
205 elif isinstance(value, ProcessTerminated):
205 elif isinstance(value, ProcessTerminated):
206 self.process_launcher.notify_stop(
206 self.process_launcher.notify_stop(
207 {'exit_code':value.exitCode,
207 {'exit_code':value.exitCode,
208 'signal':value.signal,
208 'signal':value.signal,
209 'status':value.status,
209 'status':value.status,
210 'pid':self.pid
210 'pid':self.pid
211 }
211 }
212 )
212 )
213 else:
213 else:
214 raise UnknownStatus("Unknown exit status, this is probably a "
214 raise UnknownStatus("Unknown exit status, this is probably a "
215 "bug in Twisted")
215 "bug in Twisted")
216
216
217 def outReceived(self, data):
217 def outReceived(self, data):
218 log.msg(data)
218 log.msg(data)
219
219
220 def errReceived(self, data):
220 def errReceived(self, data):
221 log.err(data)
221 log.err(data)
222
222
223
223
224 class LocalProcessLauncher(BaseLauncher):
224 class LocalProcessLauncher(BaseLauncher):
225 """Start and stop an external process in an asynchronous manner."""
225 """Start and stop an external process in an asynchronous manner.
226
227 This will launch the external process with a working directory of
228 ``self.working_dir``.
229 """
226
230
227 # This is used to to construct self.args, which is passed to
231 # This is used to to construct self.args, which is passed to
228 # spawnProcess.
232 # spawnProcess.
229 cmd_and_args = List([])
233 cmd_and_args = List([])
230
234
231 def __init__(self, working_dir, parent=None, name=None, config=None):
235 def __init__(self, working_dir, parent=None, name=None, config=None):
232 super(LocalProcessLauncher, self).__init__(
236 super(LocalProcessLauncher, self).__init__(
233 working_dir, parent, name, config
237 working_dir, parent, name, config
234 )
238 )
235 self.process_protocol = None
239 self.process_protocol = None
236 self.start_deferred = None
240 self.start_deferred = None
237
241
238 def find_args(self):
242 def find_args(self):
239 return self.cmd_and_args
243 return self.cmd_and_args
240
244
241 def start(self):
245 def start(self):
242 if self.state == 'before':
246 if self.state == 'before':
243 self.process_protocol = LocalProcessLauncherProtocol(self)
247 self.process_protocol = LocalProcessLauncherProtocol(self)
244 self.start_deferred = defer.Deferred()
248 self.start_deferred = defer.Deferred()
245 self.process_transport = reactor.spawnProcess(
249 self.process_transport = reactor.spawnProcess(
246 self.process_protocol,
250 self.process_protocol,
247 str(self.args[0]), # twisted expects these to be str, not unicode
251 str(self.args[0]), # twisted expects these to be str, not unicode
248 [str(a) for a in self.args], # str expected, not unicode
252 [str(a) for a in self.args], # str expected, not unicode
249 env=os.environ
253 env=os.environ,
254 path=self.working_dir # start in the working_dir
250 )
255 )
251 return self.start_deferred
256 return self.start_deferred
252 else:
257 else:
253 s = 'The process was already started and has state: %r' % self.state
258 s = 'The process was already started and has state: %r' % self.state
254 return defer.fail(ProcessStateError(s))
259 return defer.fail(ProcessStateError(s))
255
260
256 def notify_start(self, data):
261 def notify_start(self, data):
257 super(LocalProcessLauncher, self).notify_start(data)
262 super(LocalProcessLauncher, self).notify_start(data)
258 self.start_deferred.callback(data)
263 self.start_deferred.callback(data)
259
264
260 def stop(self):
265 def stop(self):
261 return self.interrupt_then_kill()
266 return self.interrupt_then_kill()
262
267
263 @make_deferred
268 @make_deferred
264 def signal(self, sig):
269 def signal(self, sig):
265 if self.state == 'running':
270 if self.state == 'running':
266 self.process_transport.signalProcess(sig)
271 self.process_transport.signalProcess(sig)
267
272
268 @inlineCallbacks
273 @inlineCallbacks
269 def interrupt_then_kill(self, delay=2.0):
274 def interrupt_then_kill(self, delay=2.0):
270 """Send INT, wait a delay and then send KILL."""
275 """Send INT, wait a delay and then send KILL."""
271 yield self.signal('INT')
276 yield self.signal('INT')
272 yield sleep_deferred(delay)
277 yield sleep_deferred(delay)
273 yield self.signal('KILL')
278 yield self.signal('KILL')
274
279
275
280
276 class MPIExecLauncher(LocalProcessLauncher):
281 class MPIExecLauncher(LocalProcessLauncher):
277 """Launch an external process using mpiexec."""
282 """Launch an external process using mpiexec."""
278
283
279 # The mpiexec command to use in starting the process.
284 # The mpiexec command to use in starting the process.
280 mpi_cmd = List(['mpiexec'], config=True)
285 mpi_cmd = List(['mpiexec'], config=True)
281 # The command line arguments to pass to mpiexec.
286 # The command line arguments to pass to mpiexec.
282 mpi_args = List([], config=True)
287 mpi_args = List([], config=True)
283 # The program to start using mpiexec.
288 # The program to start using mpiexec.
284 program = List(['date'], config=True)
289 program = List(['date'], config=True)
285 # The command line argument to the program.
290 # The command line argument to the program.
286 program_args = List([], config=True)
291 program_args = List([], config=True)
287 # The number of instances of the program to start.
292 # The number of instances of the program to start.
288 n = Int(1, config=True)
293 n = Int(1, config=True)
289
294
290 def find_args(self):
295 def find_args(self):
291 """Build self.args using all the fields."""
296 """Build self.args using all the fields."""
292 return self.mpi_cmd + ['-n', self.n] + self.mpi_args + \
297 return self.mpi_cmd + ['-n', self.n] + self.mpi_args + \
293 self.program + self.program_args
298 self.program + self.program_args
294
299
295 def start(self, n):
300 def start(self, n):
296 """Start n instances of the program using mpiexec."""
301 """Start n instances of the program using mpiexec."""
297 self.n = n
302 self.n = n
298 return super(MPIExecLauncher, self).start()
303 return super(MPIExecLauncher, self).start()
299
304
300
305
301 class SSHLauncher(BaseLauncher):
306 class SSHLauncher(BaseLauncher):
302 """A minimal launcher for ssh.
307 """A minimal launcher for ssh.
303
308
304 To be useful this will probably have to be extended to use the ``sshx``
309 To be useful this will probably have to be extended to use the ``sshx``
305 idea for environment variables. There could be other things this needs
310 idea for environment variables. There could be other things this needs
306 as well.
311 as well.
307 """
312 """
308
313
309 ssh_cmd = List(['ssh'], config=True)
314 ssh_cmd = List(['ssh'], config=True)
310 ssh_args = List([], config=True)
315 ssh_args = List([], config=True)
311 program = List(['date'], config=True)
316 program = List(['date'], config=True)
312 program_args = List([], config=True)
317 program_args = List([], config=True)
313 hostname = Str('', config=True)
318 hostname = Str('', config=True)
314 user = Str('', config=True)
319 user = Str('', config=True)
315 location = Str('')
320 location = Str('')
316
321
317 def _hostname_changed(self, name, old, new):
322 def _hostname_changed(self, name, old, new):
318 self.location = '%s@%s' % (self.user, new)
323 self.location = '%s@%s' % (self.user, new)
319
324
320 def _user_changed(self, name, old, new):
325 def _user_changed(self, name, old, new):
321 self.location = '%s@%s' % (new, self.hostname)
326 self.location = '%s@%s' % (new, self.hostname)
322
327
323 def find_args(self):
328 def find_args(self):
324 return self.ssh_cmd + self.ssh_args + [self.location] + \
329 return self.ssh_cmd + self.ssh_args + [self.location] + \
325 self.program + self.program_args
330 self.program + self.program_args
326
331
327 def start(self, n, hostname=None, user=None):
332 def start(self, n, hostname=None, user=None):
328 if hostname is not None:
333 if hostname is not None:
329 self.hostname = hostname
334 self.hostname = hostname
330 if user is not None:
335 if user is not None:
331 self.user = user
336 self.user = user
332 return super(SSHLauncher, self).start()
337 return super(SSHLauncher, self).start()
333
338
334
339
335 # This is only used on Windows.
340 # This is only used on Windows.
336 if os.name=='nt':
341 if os.name=='nt':
337 job_cmd = find_cmd('job')
342 job_cmd = find_cmd('job')
338 else:
343 else:
339 job_cmd = 'job'
344 job_cmd = 'job'
340
345
341
346
342 class WindowsHPCLauncher(BaseLauncher):
347 class WindowsHPCLauncher(BaseLauncher):
343
348
344 # A regular expression used to get the job id from the output of the
349 # A regular expression used to get the job id from the output of the
345 # submit_command.
350 # submit_command.
346 job_id_regexp = Str('\d+', config=True)
351 job_id_regexp = Str('\d+', config=True)
347 # The filename of the instantiated job script.
352 # The filename of the instantiated job script.
348 job_file_name = Unicode(u'ipython_job.xml', config=True)
353 job_file_name = Unicode(u'ipython_job.xml', config=True)
349 # The full path to the instantiated job script. This gets made dynamically
354 # The full path to the instantiated job script. This gets made dynamically
350 # by combining the working_dir with the job_file_name.
355 # by combining the working_dir with the job_file_name.
351 job_file = Unicode(u'')
356 job_file = Unicode(u'')
352 # The hostname of the scheduler to submit the job to
357 # The hostname of the scheduler to submit the job to
353 scheduler = Str('HEADNODE', config=True)
358 scheduler = Str('HEADNODE', config=True)
354 username = Str(os.environ.get('USERNAME', ''), config=True)
359 username = Str(os.environ.get('USERNAME', ''), config=True)
355 priority = Enum(('Lowest','BelowNormal','Normal','AboveNormal','Highest'),
360 priority = Enum(('Lowest','BelowNormal','Normal','AboveNormal','Highest'),
356 default_value='Highest', config=True)
361 default_value='Highest', config=True)
357 requested_nodes = Str('', config=True)
362 requested_nodes = Str('', config=True)
358 project = Str('MyProject', config=True)
363 project = Str('MyProject', config=True)
359 job_cmd = Str(job_cmd, config=True)
364 job_cmd = Str(job_cmd, config=True)
360
365
361 def __init__(self, working_dir, parent=None, name=None, config=None):
366 def __init__(self, working_dir, parent=None, name=None, config=None):
362 super(WindowsHPCLauncher, self).__init__(
367 super(WindowsHPCLauncher, self).__init__(
363 working_dir, parent, name, config
368 working_dir, parent, name, config
364 )
369 )
365 self.job_file = os.path.join(self.working_dir, self.job_file_name)
370 self.job_file = os.path.join(self.working_dir, self.job_file_name)
366
371
367 def write_job_file(self, n):
372 def write_job_file(self, n):
368 raise NotImplementedError("Implement write_job_file in a subclass.")
373 raise NotImplementedError("Implement write_job_file in a subclass.")
369
374
370 def find_args(self):
375 def find_args(self):
371 return ['job.exe']
376 return ['job.exe']
372
377
373 def parse_job_id(self, output):
378 def parse_job_id(self, output):
374 """Take the output of the submit command and return the job id."""
379 """Take the output of the submit command and return the job id."""
375 m = re.search(self.job_id_regexp, output)
380 m = re.search(self.job_id_regexp, output)
376 if m is not None:
381 if m is not None:
377 job_id = m.group()
382 job_id = m.group()
378 else:
383 else:
379 raise LauncherError("Job id couldn't be determined: %s" % output)
384 raise LauncherError("Job id couldn't be determined: %s" % output)
380 self.job_id = job_id
385 self.job_id = job_id
381 log.msg('Job started with job id: %r' % job_id)
386 log.msg('Job started with job id: %r' % job_id)
382 return job_id
387 return job_id
383
388
384 @inlineCallbacks
389 @inlineCallbacks
385 def start(self, n):
390 def start(self, n):
386 """Start n copies of the process using the Win HPC job scheduler."""
391 """Start n copies of the process using the Win HPC job scheduler."""
387 self.write_job_file(n)
392 self.write_job_file(n)
388 args = [
393 args = [
389 'submit',
394 'submit',
390 '/jobfile:%s' % self.job_file,
395 '/jobfile:%s' % self.job_file,
391 '/scheduler:%s' % self.scheduler
396 '/scheduler:%s' % self.scheduler
392 ]
397 ]
393 log.msg("Starting Win HPC Job: %s" % (self.job_cmd + ' ' + ' '.join(args),))
398 log.msg("Starting Win HPC Job: %s" % (self.job_cmd + ' ' + ' '.join(args),))
394 output = yield getProcessOutput(self.job_cmd,
399 output = yield getProcessOutput(self.job_cmd,
395 args,
400 args,
396 env=os.environ,
401 env=os.environ,
397 path=self.working_dir
402 path=self.working_dir
398 )
403 )
399 job_id = self.parse_job_id(output)
404 job_id = self.parse_job_id(output)
400 self.notify_start(job_id)
405 self.notify_start(job_id)
401 defer.returnValue(job_id)
406 defer.returnValue(job_id)
402
407
403 @inlineCallbacks
408 @inlineCallbacks
404 def stop(self):
409 def stop(self):
405 args = [
410 args = [
406 'cancel',
411 'cancel',
407 self.job_id,
412 self.job_id,
408 '/scheduler:%s' % self.scheduler
413 '/scheduler:%s' % self.scheduler
409 ]
414 ]
410 log.msg("Stopping Win HPC Job: %s" % (self.job_cmd + ' ' + ' '.join(args),))
415 log.msg("Stopping Win HPC Job: %s" % (self.job_cmd + ' ' + ' '.join(args),))
411 try:
416 try:
412 output = yield getProcessOutput(self.job_cmd,
417 output = yield getProcessOutput(self.job_cmd,
413 args,
418 args,
414 env=os.environ,
419 env=os.environ,
415 path=self.working_dir
420 path=self.working_dir
416 )
421 )
417 except:
422 except:
418 output = 'The job already appears to be stoppped: %r' % self.job_id
423 output = 'The job already appears to be stoppped: %r' % self.job_id
419 self.notify_stop(output) # Pass the output of the kill cmd
424 self.notify_stop(output) # Pass the output of the kill cmd
420 defer.returnValue(output)
425 defer.returnValue(output)
421
426
422
427
423 class BatchSystemLauncher(BaseLauncher):
428 class BatchSystemLauncher(BaseLauncher):
424 """Launch an external process using a batch system.
429 """Launch an external process using a batch system.
425
430
426 This class is designed to work with UNIX batch systems like PBS, LSF,
431 This class is designed to work with UNIX batch systems like PBS, LSF,
427 GridEngine, etc. The overall model is that there are different commands
432 GridEngine, etc. The overall model is that there are different commands
428 like qsub, qdel, etc. that handle the starting and stopping of the process.
433 like qsub, qdel, etc. that handle the starting and stopping of the process.
429
434
430 This class also has the notion of a batch script. The ``batch_template``
435 This class also has the notion of a batch script. The ``batch_template``
431 attribute can be set to a string that is a template for the batch script.
436 attribute can be set to a string that is a template for the batch script.
432 This template is instantiated using Itpl. Thus the template can use
437 This template is instantiated using Itpl. Thus the template can use
433 ${n} fot the number of instances. Subclasses can add additional variables
438 ${n} fot the number of instances. Subclasses can add additional variables
434 to the template dict.
439 to the template dict.
435 """
440 """
436
441
437 # Subclasses must fill these in. See PBSEngineSet
442 # Subclasses must fill these in. See PBSEngineSet
438 # The name of the command line program used to submit jobs.
443 # The name of the command line program used to submit jobs.
439 submit_command = Str('', config=True)
444 submit_command = Str('', config=True)
440 # The name of the command line program used to delete jobs.
445 # The name of the command line program used to delete jobs.
441 delete_command = Str('', config=True)
446 delete_command = Str('', config=True)
442 # A regular expression used to get the job id from the output of the
447 # A regular expression used to get the job id from the output of the
443 # submit_command.
448 # submit_command.
444 job_id_regexp = Str('', config=True)
449 job_id_regexp = Str('', config=True)
445 # The string that is the batch script template itself.
450 # The string that is the batch script template itself.
446 batch_template = Str('', config=True)
451 batch_template = Str('', config=True)
447 # The filename of the instantiated batch script.
452 # The filename of the instantiated batch script.
448 batch_file_name = Unicode(u'batch_script', config=True)
453 batch_file_name = Unicode(u'batch_script', config=True)
449 # The full path to the instantiated batch script.
454 # The full path to the instantiated batch script.
450 batch_file = Unicode(u'')
455 batch_file = Unicode(u'')
451
456
452 def __init__(self, working_dir, parent=None, name=None, config=None):
457 def __init__(self, working_dir, parent=None, name=None, config=None):
453 super(BatchSystemLauncher, self).__init__(
458 super(BatchSystemLauncher, self).__init__(
454 working_dir, parent, name, config
459 working_dir, parent, name, config
455 )
460 )
456 self.batch_file = os.path.join(self.working_dir, self.batch_file_name)
461 self.batch_file = os.path.join(self.working_dir, self.batch_file_name)
457 self.context = {}
462 self.context = {}
458
463
459 def parse_job_id(self, output):
464 def parse_job_id(self, output):
460 """Take the output of the submit command and return the job id."""
465 """Take the output of the submit command and return the job id."""
461 m = re.match(self.job_id_regexp, output)
466 m = re.match(self.job_id_regexp, output)
462 if m is not None:
467 if m is not None:
463 job_id = m.group()
468 job_id = m.group()
464 else:
469 else:
465 raise LauncherError("Job id couldn't be determined: %s" % output)
470 raise LauncherError("Job id couldn't be determined: %s" % output)
466 self.job_id = job_id
471 self.job_id = job_id
467 log.msg('Job started with job id: %r' % job_id)
472 log.msg('Job started with job id: %r' % job_id)
468 return job_id
473 return job_id
469
474
470 def write_batch_script(self, n):
475 def write_batch_script(self, n):
471 """Instantiate and write the batch script to the working_dir."""
476 """Instantiate and write the batch script to the working_dir."""
472 self.context['n'] = n
477 self.context['n'] = n
473 script_as_string = Itpl.itplns(self.batch_template, self.context)
478 script_as_string = Itpl.itplns(self.batch_template, self.context)
474 log.msg('Writing instantiated batch script: %s' % self.batch_file)
479 log.msg('Writing instantiated batch script: %s' % self.batch_file)
475 f = open(self.batch_file, 'w')
480 f = open(self.batch_file, 'w')
476 f.write(script_as_string)
481 f.write(script_as_string)
477 f.close()
482 f.close()
478
483
479 @inlineCallbacks
484 @inlineCallbacks
480 def start(self, n):
485 def start(self, n):
481 """Start n copies of the process using a batch system."""
486 """Start n copies of the process using a batch system."""
482 self.write_batch_script(n)
487 self.write_batch_script(n)
483 output = yield getProcessOutput(self.submit_command,
488 output = yield getProcessOutput(self.submit_command,
484 [self.batch_file], env=os.environ)
489 [self.batch_file], env=os.environ)
485 job_id = self.parse_job_id(output)
490 job_id = self.parse_job_id(output)
486 self.notify_start(job_id)
491 self.notify_start(job_id)
487 defer.returnValue(job_id)
492 defer.returnValue(job_id)
488
493
489 @inlineCallbacks
494 @inlineCallbacks
490 def stop(self):
495 def stop(self):
491 output = yield getProcessOutput(self.delete_command,
496 output = yield getProcessOutput(self.delete_command,
492 [self.job_id], env=os.environ
497 [self.job_id], env=os.environ
493 )
498 )
494 self.notify_stop(output) # Pass the output of the kill cmd
499 self.notify_stop(output) # Pass the output of the kill cmd
495 defer.returnValue(output)
500 defer.returnValue(output)
496
501
497
502
498 class PBSLauncher(BatchSystemLauncher):
503 class PBSLauncher(BatchSystemLauncher):
499 """A BatchSystemLauncher subclass for PBS."""
504 """A BatchSystemLauncher subclass for PBS."""
500
505
501 submit_command = Str('qsub', config=True)
506 submit_command = Str('qsub', config=True)
502 delete_command = Str('qdel', config=True)
507 delete_command = Str('qdel', config=True)
503 job_id_regexp = Str('\d+', config=True)
508 job_id_regexp = Str('\d+', config=True)
504 batch_template = Str('', config=True)
509 batch_template = Str('', config=True)
505 batch_file_name = Unicode(u'pbs_batch_script', config=True)
510 batch_file_name = Unicode(u'pbs_batch_script', config=True)
506 batch_file = Unicode(u'')
511 batch_file = Unicode(u'')
507
512
508
513
509 #-----------------------------------------------------------------------------
514 #-----------------------------------------------------------------------------
510 # Controller launchers
515 # Controller launchers
511 #-----------------------------------------------------------------------------
516 #-----------------------------------------------------------------------------
512
517
513 def find_controller_cmd():
518 def find_controller_cmd():
514 """Find the command line ipcontroller program in a cross platform way."""
519 """Find the command line ipcontroller program in a cross platform way."""
515 if sys.platform == 'win32':
520 if sys.platform == 'win32':
516 # This logic is needed because the ipcontroller script doesn't
521 # This logic is needed because the ipcontroller script doesn't
517 # always get installed in the same way or in the same location.
522 # always get installed in the same way or in the same location.
518 from IPython.kernel import ipcontrollerapp
523 from IPython.kernel import ipcontrollerapp
519 script_location = ipcontrollerapp.__file__.replace('.pyc', '.py')
524 script_location = ipcontrollerapp.__file__.replace('.pyc', '.py')
520 # The -u option here turns on unbuffered output, which is required
525 # The -u option here turns on unbuffered output, which is required
521 # on Win32 to prevent wierd conflict and problems with Twisted.
526 # on Win32 to prevent wierd conflict and problems with Twisted.
522 # Also, use sys.executable to make sure we are picking up the
527 # Also, use sys.executable to make sure we are picking up the
523 # right python exe.
528 # right python exe.
524 cmd = [sys.executable, '-u', script_location]
529 cmd = [sys.executable, '-u', script_location]
525 else:
530 else:
526 # ipcontroller has to be on the PATH in this case.
531 # ipcontroller has to be on the PATH in this case.
527 cmd = ['ipcontroller']
532 cmd = ['ipcontroller']
528 return cmd
533 return cmd
529
534
530
535
531 class LocalControllerLauncher(LocalProcessLauncher):
536 class LocalControllerLauncher(LocalProcessLauncher):
532 """Launch a controller as a regular external process."""
537 """Launch a controller as a regular external process."""
533
538
534 controller_cmd = List(find_controller_cmd(), config=True)
539 controller_cmd = List(find_controller_cmd(), config=True)
535 # Command line arguments to ipcontroller.
540 # Command line arguments to ipcontroller.
536 controller_args = List(['--log-to-file','--log-level', '40'], config=True)
541 controller_args = List(['--log-to-file','--log-level', '40'], config=True)
537
542
538 def find_args(self):
543 def find_args(self):
539 return self.controller_cmd + self.controller_args
544 return self.controller_cmd + self.controller_args
540
545
541 def start(self, profile=None, cluster_dir=None):
546 def start(self, profile=None, cluster_dir=None):
542 """Start the controller by profile or cluster_dir."""
547 """Start the controller by profile or cluster_dir."""
543 if cluster_dir is not None:
548 if cluster_dir is not None:
544 self.controller_args.extend(['--cluster-dir', cluster_dir])
549 self.controller_args.extend(['--cluster-dir', cluster_dir])
545 if profile is not None:
550 if profile is not None:
546 self.controller_args.extend(['--profile', profile])
551 self.controller_args.extend(['--profile', profile])
547 log.msg("Starting LocalControllerLauncher: %r" % self.args)
552 log.msg("Starting LocalControllerLauncher: %r" % self.args)
548 return super(LocalControllerLauncher, self).start()
553 return super(LocalControllerLauncher, self).start()
549
554
550
555
551 class WindowsHPCControllerLauncher(WindowsHPCLauncher):
556 class WindowsHPCControllerLauncher(WindowsHPCLauncher):
552
557
553 job_file_name = Unicode(u'ipcontroller_job.xml', config=True)
558 job_file_name = Unicode(u'ipcontroller_job.xml', config=True)
554 extra_args = List([],config=False)
559 extra_args = List([],config=False)
555
560
556 def write_job_file(self, n):
561 def write_job_file(self, n):
557 job = WinHPCJob(self)
562 job = WinHPCJob(self)
558 job.job_name = "IPController"
563 job.job_name = "IPController"
559 job.username = self.username
564 job.username = self.username
560 job.priority = self.priority
565 job.priority = self.priority
561 job.requested_nodes = self.requested_nodes
566 job.requested_nodes = self.requested_nodes
562 job.project = self.project
567 job.project = self.project
563
568
564 t = IPControllerTask(self)
569 t = IPControllerTask(self)
565 t.work_directory = self.working_dir
570 t.work_directory = self.working_dir
566 # Add the --profile and --cluster-dir args from start.
571 # Add the --profile and --cluster-dir args from start.
567 t.controller_args.extend(self.extra_args)
572 t.controller_args.extend(self.extra_args)
568 job.add_task(t)
573 job.add_task(t)
569 log.msg("Writing job description file: %s" % self.job_file)
574 log.msg("Writing job description file: %s" % self.job_file)
570 job.write(self.job_file)
575 job.write(self.job_file)
571
576
572 def start(self, profile=None, cluster_dir=None):
577 def start(self, profile=None, cluster_dir=None):
573 """Start the controller by profile or cluster_dir."""
578 """Start the controller by profile or cluster_dir."""
574 if cluster_dir is not None:
579 if cluster_dir is not None:
575 self.extra_args = ['--cluster-dir', cluster_dir]
580 self.extra_args = ['--cluster-dir', cluster_dir]
576 if profile is not None:
581 if profile is not None:
577 self.extra_args = ['--profile', profile]
582 self.extra_args = ['--profile', profile]
578 return super(WindowsHPCControllerLauncher, self).start(1)
583 return super(WindowsHPCControllerLauncher, self).start(1)
579
584
580
585
581 class MPIExecControllerLauncher(MPIExecLauncher):
586 class MPIExecControllerLauncher(MPIExecLauncher):
582 """Launch a controller using mpiexec."""
587 """Launch a controller using mpiexec."""
583
588
584 controller_cmd = List(find_controller_cmd(), config=True)
589 controller_cmd = List(find_controller_cmd(), config=True)
585 # Command line arguments to ipcontroller.
590 # Command line arguments to ipcontroller.
586 controller_args = List(['--log-to-file','--log-level', '40'], config=True)
591 controller_args = List(['--log-to-file','--log-level', '40'], config=True)
587 n = Int(1, config=False)
592 n = Int(1, config=False)
588
593
589 def start(self, profile=None, cluster_dir=None):
594 def start(self, profile=None, cluster_dir=None):
590 """Start the controller by profile or cluster_dir."""
595 """Start the controller by profile or cluster_dir."""
591 if cluster_dir is not None:
596 if cluster_dir is not None:
592 self.controller_args.extend(['--cluster-dir', cluster_dir])
597 self.controller_args.extend(['--cluster-dir', cluster_dir])
593 if profile is not None:
598 if profile is not None:
594 self.controller_args.extend(['--profile', profile])
599 self.controller_args.extend(['--profile', profile])
595 log.msg("Starting MPIExecControllerLauncher: %r" % self.args)
600 log.msg("Starting MPIExecControllerLauncher: %r" % self.args)
596 return super(MPIExecControllerLauncher, self).start(1)
601 return super(MPIExecControllerLauncher, self).start(1)
597
602
598 def find_args(self):
603 def find_args(self):
599 return self.mpi_cmd + ['-n', self.n] + self.mpi_args + \
604 return self.mpi_cmd + ['-n', self.n] + self.mpi_args + \
600 self.controller_cmd + self.controller_args
605 self.controller_cmd + self.controller_args
601
606
602
607
603 class PBSControllerLauncher(PBSLauncher):
608 class PBSControllerLauncher(PBSLauncher):
604 """Launch a controller using PBS."""
609 """Launch a controller using PBS."""
605
610
606 batch_file_name = Unicode(u'pbs_batch_script_controller', config=True)
611 batch_file_name = Unicode(u'pbs_batch_script_controller', config=True)
607
612
608 def start(self, profile=None, cluster_dir=None):
613 def start(self, profile=None, cluster_dir=None):
609 """Start the controller by profile or cluster_dir."""
614 """Start the controller by profile or cluster_dir."""
610 # Here we save profile and cluster_dir in the context so they
615 # Here we save profile and cluster_dir in the context so they
611 # can be used in the batch script template as ${profile} and
616 # can be used in the batch script template as ${profile} and
612 # ${cluster_dir}
617 # ${cluster_dir}
613 if cluster_dir is not None:
618 if cluster_dir is not None:
614 self.context['cluster_dir'] = cluster_dir
619 self.context['cluster_dir'] = cluster_dir
615 if profile is not None:
620 if profile is not None:
616 self.context['profile'] = profile
621 self.context['profile'] = profile
617 log.msg("Starting PBSControllerLauncher: %r" % self.args)
622 log.msg("Starting PBSControllerLauncher: %r" % self.args)
618 return super(PBSControllerLauncher, self).start(1)
623 return super(PBSControllerLauncher, self).start(1)
619
624
620
625
621 class SSHControllerLauncher(SSHLauncher):
626 class SSHControllerLauncher(SSHLauncher):
622 pass
627 pass
623
628
624
629
625 #-----------------------------------------------------------------------------
630 #-----------------------------------------------------------------------------
626 # Engine launchers
631 # Engine launchers
627 #-----------------------------------------------------------------------------
632 #-----------------------------------------------------------------------------
628
633
629
634
630 def find_engine_cmd():
635 def find_engine_cmd():
631 """Find the command line ipengine program in a cross platform way."""
636 """Find the command line ipengine program in a cross platform way."""
632 if sys.platform == 'win32':
637 if sys.platform == 'win32':
633 # This logic is needed because the ipengine script doesn't
638 # This logic is needed because the ipengine script doesn't
634 # always get installed in the same way or in the same location.
639 # always get installed in the same way or in the same location.
635 from IPython.kernel import ipengineapp
640 from IPython.kernel import ipengineapp
636 script_location = ipengineapp.__file__.replace('.pyc', '.py')
641 script_location = ipengineapp.__file__.replace('.pyc', '.py')
637 # The -u option here turns on unbuffered output, which is required
642 # The -u option here turns on unbuffered output, which is required
638 # on Win32 to prevent wierd conflict and problems with Twisted.
643 # on Win32 to prevent wierd conflict and problems with Twisted.
639 # Also, use sys.executable to make sure we are picking up the
644 # Also, use sys.executable to make sure we are picking up the
640 # right python exe.
645 # right python exe.
641 cmd = [sys.executable, '-u', script_location]
646 cmd = [sys.executable, '-u', script_location]
642 else:
647 else:
643 # ipcontroller has to be on the PATH in this case.
648 # ipcontroller has to be on the PATH in this case.
644 cmd = ['ipengine']
649 cmd = ['ipengine']
645 return cmd
650 return cmd
646
651
647
652
648 class LocalEngineLauncher(LocalProcessLauncher):
653 class LocalEngineLauncher(LocalProcessLauncher):
649 """Launch a single engine as a regular externall process."""
654 """Launch a single engine as a regular externall process."""
650
655
651 engine_cmd = List(find_engine_cmd(), config=True)
656 engine_cmd = List(find_engine_cmd(), config=True)
652 # Command line arguments for ipengine.
657 # Command line arguments for ipengine.
653 engine_args = List(
658 engine_args = List(
654 ['--log-to-file','--log-level', '40'], config=True
659 ['--log-to-file','--log-level', '40'], config=True
655 )
660 )
656
661
657 def find_args(self):
662 def find_args(self):
658 return self.engine_cmd + self.engine_args
663 return self.engine_cmd + self.engine_args
659
664
660 def start(self, profile=None, cluster_dir=None):
665 def start(self, profile=None, cluster_dir=None):
661 """Start the engine by profile or cluster_dir."""
666 """Start the engine by profile or cluster_dir."""
662 if cluster_dir is not None:
667 if cluster_dir is not None:
663 self.engine_args.extend(['--cluster-dir', cluster_dir])
668 self.engine_args.extend(['--cluster-dir', cluster_dir])
664 if profile is not None:
669 if profile is not None:
665 self.engine_args.extend(['--profile', profile])
670 self.engine_args.extend(['--profile', profile])
666 return super(LocalEngineLauncher, self).start()
671 return super(LocalEngineLauncher, self).start()
667
672
668
673
669 class LocalEngineSetLauncher(BaseLauncher):
674 class LocalEngineSetLauncher(BaseLauncher):
670 """Launch a set of engines as regular external processes."""
675 """Launch a set of engines as regular external processes."""
671
676
672 # Command line arguments for ipengine.
677 # Command line arguments for ipengine.
673 engine_args = List(
678 engine_args = List(
674 ['--log-to-file','--log-level', '40'], config=True
679 ['--log-to-file','--log-level', '40'], config=True
675 )
680 )
676
681
677 def __init__(self, working_dir, parent=None, name=None, config=None):
682 def __init__(self, working_dir, parent=None, name=None, config=None):
678 super(LocalEngineSetLauncher, self).__init__(
683 super(LocalEngineSetLauncher, self).__init__(
679 working_dir, parent, name, config
684 working_dir, parent, name, config
680 )
685 )
681 self.launchers = []
686 self.launchers = []
682
687
683 def start(self, n, profile=None, cluster_dir=None):
688 def start(self, n, profile=None, cluster_dir=None):
684 """Start n engines by profile or cluster_dir."""
689 """Start n engines by profile or cluster_dir."""
685 dlist = []
690 dlist = []
686 for i in range(n):
691 for i in range(n):
687 el = LocalEngineLauncher(self.working_dir, self)
692 el = LocalEngineLauncher(self.working_dir, self)
688 # Copy the engine args over to each engine launcher.
693 # Copy the engine args over to each engine launcher.
689 import copy
694 import copy
690 el.engine_args = copy.deepcopy(self.engine_args)
695 el.engine_args = copy.deepcopy(self.engine_args)
691 d = el.start(profile, cluster_dir)
696 d = el.start(profile, cluster_dir)
692 if i==0:
697 if i==0:
693 log.msg("Starting LocalEngineSetLauncher: %r" % el.args)
698 log.msg("Starting LocalEngineSetLauncher: %r" % el.args)
694 self.launchers.append(el)
699 self.launchers.append(el)
695 dlist.append(d)
700 dlist.append(d)
696 # The consumeErrors here could be dangerous
701 # The consumeErrors here could be dangerous
697 dfinal = gatherBoth(dlist, consumeErrors=True)
702 dfinal = gatherBoth(dlist, consumeErrors=True)
698 dfinal.addCallback(self.notify_start)
703 dfinal.addCallback(self.notify_start)
699 return dfinal
704 return dfinal
700
705
701 def find_args(self):
706 def find_args(self):
702 return ['engine set']
707 return ['engine set']
703
708
704 def signal(self, sig):
709 def signal(self, sig):
705 dlist = []
710 dlist = []
706 for el in self.launchers:
711 for el in self.launchers:
707 d = el.signal(sig)
712 d = el.signal(sig)
708 dlist.append(d)
713 dlist.append(d)
709 dfinal = gatherBoth(dlist, consumeErrors=True)
714 dfinal = gatherBoth(dlist, consumeErrors=True)
710 return dfinal
715 return dfinal
711
716
712 def interrupt_then_kill(self, delay=1.0):
717 def interrupt_then_kill(self, delay=1.0):
713 dlist = []
718 dlist = []
714 for el in self.launchers:
719 for el in self.launchers:
715 d = el.interrupt_then_kill(delay)
720 d = el.interrupt_then_kill(delay)
716 dlist.append(d)
721 dlist.append(d)
717 dfinal = gatherBoth(dlist, consumeErrors=True)
722 dfinal = gatherBoth(dlist, consumeErrors=True)
718 return dfinal
723 return dfinal
719
724
720 def stop(self):
725 def stop(self):
721 return self.interrupt_then_kill()
726 return self.interrupt_then_kill()
722
727
723 def observe_stop(self):
728 def observe_stop(self):
724 dlist = [el.observe_stop() for el in self.launchers]
729 dlist = [el.observe_stop() for el in self.launchers]
725 dfinal = gatherBoth(dlist, consumeErrors=False)
730 dfinal = gatherBoth(dlist, consumeErrors=False)
726 dfinal.addCallback(self.notify_stop)
731 dfinal.addCallback(self.notify_stop)
727 return dfinal
732 return dfinal
728
733
729
734
730 class MPIExecEngineSetLauncher(MPIExecLauncher):
735 class MPIExecEngineSetLauncher(MPIExecLauncher):
731
736
732 engine_cmd = List(find_engine_cmd(), config=True)
737 engine_cmd = List(find_engine_cmd(), config=True)
733 # Command line arguments for ipengine.
738 # Command line arguments for ipengine.
734 engine_args = List(
739 engine_args = List(
735 ['--log-to-file','--log-level', '40'], config=True
740 ['--log-to-file','--log-level', '40'], config=True
736 )
741 )
737 n = Int(1, config=True)
742 n = Int(1, config=True)
738
743
739 def start(self, n, profile=None, cluster_dir=None):
744 def start(self, n, profile=None, cluster_dir=None):
740 """Start n engines by profile or cluster_dir."""
745 """Start n engines by profile or cluster_dir."""
741 if cluster_dir is not None:
746 if cluster_dir is not None:
742 self.engine_args.extend(['--cluster-dir', cluster_dir])
747 self.engine_args.extend(['--cluster-dir', cluster_dir])
743 if profile is not None:
748 if profile is not None:
744 self.engine_args.extend(['--profile', profile])
749 self.engine_args.extend(['--profile', profile])
745 log.msg('Starting MPIExecEngineSetLauncher: %r' % self.args)
750 log.msg('Starting MPIExecEngineSetLauncher: %r' % self.args)
746 return super(MPIExecEngineSetLauncher, self).start(n)
751 return super(MPIExecEngineSetLauncher, self).start(n)
747
752
748 def find_args(self):
753 def find_args(self):
749 return self.mpi_cmd + ['-n', self.n] + self.mpi_args + \
754 return self.mpi_cmd + ['-n', self.n] + self.mpi_args + \
750 self.engine_cmd + self.engine_args
755 self.engine_cmd + self.engine_args
751
756
752
757
753 class WindowsHPCEngineSetLauncher(WindowsHPCLauncher):
758 class WindowsHPCEngineSetLauncher(WindowsHPCLauncher):
754 pass
759 pass
755
760
756
761
757 class PBSEngineSetLauncher(PBSLauncher):
762 class PBSEngineSetLauncher(PBSLauncher):
758
763
759 batch_file_name = Unicode(u'pbs_batch_script_engines', config=True)
764 batch_file_name = Unicode(u'pbs_batch_script_engines', config=True)
760
765
761 def start(self, n, profile=None, cluster_dir=None):
766 def start(self, n, profile=None, cluster_dir=None):
762 """Start n engines by profile or cluster_dir."""
767 """Start n engines by profile or cluster_dir."""
763 if cluster_dir is not None:
768 if cluster_dir is not None:
764 self.program_args.extend(['--cluster-dir', cluster_dir])
769 self.program_args.extend(['--cluster-dir', cluster_dir])
765 if profile is not None:
770 if profile is not None:
766 self.program_args.extend(['-p', profile])
771 self.program_args.extend(['-p', profile])
767 log.msg('Starting PBSEngineSetLauncher: %r' % self.args)
772 log.msg('Starting PBSEngineSetLauncher: %r' % self.args)
768 return super(PBSEngineSetLauncher, self).start(n)
773 return super(PBSEngineSetLauncher, self).start(n)
769
774
770
775
771 class SSHEngineSetLauncher(BaseLauncher):
776 class SSHEngineSetLauncher(BaseLauncher):
772 pass
777 pass
773
778
774
779
775 #-----------------------------------------------------------------------------
780 #-----------------------------------------------------------------------------
776 # A launcher for ipcluster itself!
781 # A launcher for ipcluster itself!
777 #-----------------------------------------------------------------------------
782 #-----------------------------------------------------------------------------
778
783
779
784
780 def find_ipcluster_cmd():
785 def find_ipcluster_cmd():
781 """Find the command line ipcluster program in a cross platform way."""
786 """Find the command line ipcluster program in a cross platform way."""
782 if sys.platform == 'win32':
787 if sys.platform == 'win32':
783 # This logic is needed because the ipcluster script doesn't
788 # This logic is needed because the ipcluster script doesn't
784 # always get installed in the same way or in the same location.
789 # always get installed in the same way or in the same location.
785 from IPython.kernel import ipclusterapp
790 from IPython.kernel import ipclusterapp
786 script_location = ipclusterapp.__file__.replace('.pyc', '.py')
791 script_location = ipclusterapp.__file__.replace('.pyc', '.py')
787 # The -u option here turns on unbuffered output, which is required
792 # The -u option here turns on unbuffered output, which is required
788 # on Win32 to prevent wierd conflict and problems with Twisted.
793 # on Win32 to prevent wierd conflict and problems with Twisted.
789 # Also, use sys.executable to make sure we are picking up the
794 # Also, use sys.executable to make sure we are picking up the
790 # right python exe.
795 # right python exe.
791 cmd = [sys.executable, '-u', script_location]
796 cmd = [sys.executable, '-u', script_location]
792 else:
797 else:
793 # ipcontroller has to be on the PATH in this case.
798 # ipcontroller has to be on the PATH in this case.
794 cmd = ['ipcluster']
799 cmd = ['ipcluster']
795 return cmd
800 return cmd
796
801
797
802
798 class IPClusterLauncher(LocalProcessLauncher):
803 class IPClusterLauncher(LocalProcessLauncher):
799 """Launch the ipcluster program in an external process."""
804 """Launch the ipcluster program in an external process."""
800
805
801 ipcluster_cmd = List(find_ipcluster_cmd(), config=True)
806 ipcluster_cmd = List(find_ipcluster_cmd(), config=True)
802 # Command line arguments to pass to ipcluster.
807 # Command line arguments to pass to ipcluster.
803 ipcluster_args = List(
808 ipcluster_args = List(
804 ['--clean-logs', '--log-to-file', '--log-level', '40'], config=True)
809 ['--clean-logs', '--log-to-file', '--log-level', '40'], config=True)
805 ipcluster_subcommand = Str('start')
810 ipcluster_subcommand = Str('start')
806 ipcluster_n = Int(2)
811 ipcluster_n = Int(2)
807
812
808 def find_args(self):
813 def find_args(self):
809 return self.ipcluster_cmd + [self.ipcluster_subcommand] + \
814 return self.ipcluster_cmd + [self.ipcluster_subcommand] + \
810 ['-n', repr(self.ipcluster_n)] + self.ipcluster_args
815 ['-n', repr(self.ipcluster_n)] + self.ipcluster_args
811
816
812 def start(self):
817 def start(self):
813 log.msg("Starting ipcluster: %r" % self.args)
818 log.msg("Starting ipcluster: %r" % self.args)
814 return super(IPClusterLauncher, self).start()
819 return super(IPClusterLauncher, self).start()
815
820
General Comments 0
You need to be logged in to leave comments. Login now