##// END OF EJS Templates
rebase IPython.parallel after removal of IPython.kernel...
MinRK -
Show More
@@ -1,184 +1,241 b''
1 1 import os
2 2
3 3 c = get_config()
4 4
5 5 #-----------------------------------------------------------------------------
6 6 # Select which launchers to use
7 7 #-----------------------------------------------------------------------------
8 8
9 9 # This allows you to control what method is used to start the controller
10 10 # and engines. The following methods are currently supported:
11 11 # - Start as a regular process on localhost.
12 12 # - Start using mpiexec.
13 13 # - Start using the Windows HPC Server 2008 scheduler
14 # - Start using PBS
15 # - Start using SSH (currently broken)
14 # - Start using PBS/SGE
15 # - Start using SSH
16 16
17 17
18 18 # The selected launchers can be configured below.
19 19
20 20 # Options are:
21 21 # - LocalControllerLauncher
22 22 # - MPIExecControllerLauncher
23 23 # - PBSControllerLauncher
24 # - SGEControllerLauncher
24 25 # - WindowsHPCControllerLauncher
25 # c.Global.controller_launcher = 'IPython.kernel.launcher.LocalControllerLauncher'
26 # c.Global.controller_launcher = 'IPython.parallel.launcher.LocalControllerLauncher'
27 # c.Global.controller_launcher = 'IPython.parallel.launcher.PBSControllerLauncher'
26 28
27 29 # Options are:
28 30 # - LocalEngineSetLauncher
29 31 # - MPIExecEngineSetLauncher
30 32 # - PBSEngineSetLauncher
33 # - SGEEngineSetLauncher
31 34 # - WindowsHPCEngineSetLauncher
32 # c.Global.engine_launcher = 'IPython.kernel.launcher.LocalEngineSetLauncher'
35 # c.Global.engine_launcher = 'IPython.parallel.launcher.LocalEngineSetLauncher'
33 36
34 37 #-----------------------------------------------------------------------------
35 38 # Global configuration
36 39 #-----------------------------------------------------------------------------
37 40
38 41 # The default number of engines that will be started. This is overridden by
39 42 # the -n command line option: "ipcluster start -n 4"
40 43 # c.Global.n = 2
41 44
42 45 # Log to a file in cluster_dir/log, otherwise just log to sys.stdout.
43 46 # c.Global.log_to_file = False
44 47
45 48 # Remove old logs from cluster_dir/log before starting.
46 49 # c.Global.clean_logs = True
47 50
48 51 # The working directory for the process. The application will use os.chdir
49 52 # to change to this directory before starting.
50 53 # c.Global.work_dir = os.getcwd()
51 54
52 55
53 56 #-----------------------------------------------------------------------------
54 57 # Local process launchers
55 58 #-----------------------------------------------------------------------------
56 59
57 60 # The command line arguments to call the controller with.
58 61 # c.LocalControllerLauncher.controller_args = \
59 62 # ['--log-to-file','--log-level', '40']
60 63
61 64 # The working directory for the controller
62 65 # c.LocalEngineSetLauncher.work_dir = u''
63 66
64 67 # Command line argument passed to the engines.
65 68 # c.LocalEngineSetLauncher.engine_args = ['--log-to-file','--log-level', '40']
66 69
67 70 #-----------------------------------------------------------------------------
68 71 # MPIExec launchers
69 72 #-----------------------------------------------------------------------------
70 73
71 # The mpiexec/mpirun command to use in started the controller.
72 # c.MPIExecControllerLauncher.mpi_cmd = ['mpiexec']
74 # The mpiexec/mpirun command to use in both the controller and engines.
75 # c.MPIExecLauncher.mpi_cmd = ['mpiexec']
73 76
74 77 # Additional arguments to pass to the actual mpiexec command.
78 # c.MPIExecLauncher.mpi_args = []
79
80 # The mpiexec/mpirun command and args can be overridden if they should be different
81 # for controller and engines.
82 # c.MPIExecControllerLauncher.mpi_cmd = ['mpiexec']
75 83 # c.MPIExecControllerLauncher.mpi_args = []
84 # c.MPIExecEngineSetLauncher.mpi_cmd = ['mpiexec']
85 # c.MPIExecEngineSetLauncher.mpi_args = []
76 86
77 87 # The command line argument to call the controller with.
78 88 # c.MPIExecControllerLauncher.controller_args = \
79 89 # ['--log-to-file','--log-level', '40']
80 90
81
82 # The mpiexec/mpirun command to use in started the controller.
83 # c.MPIExecEngineSetLauncher.mpi_cmd = ['mpiexec']
84
85 # Additional arguments to pass to the actual mpiexec command.
86 # c.MPIExecEngineSetLauncher.mpi_args = []
87
88 91 # Command line argument passed to the engines.
89 92 # c.MPIExecEngineSetLauncher.engine_args = ['--log-to-file','--log-level', '40']
90 93
91 94 # The default number of engines to start if not given elsewhere.
92 95 # c.MPIExecEngineSetLauncher.n = 1
93 96
94 97 #-----------------------------------------------------------------------------
95 98 # SSH launchers
96 99 #-----------------------------------------------------------------------------
97 100
98 # Todo
101 # ipclusterz can be used to launch controller and engines remotely via ssh.
102 # Note that currently ipclusterz does not do any file distribution, so if
103 # machines are not on a shared filesystem, config and json files must be
104 # distributed. For this reason, the reuse_files defaults to True on an
105 # ssh-launched Controller. This flag can be overridded by the program_args
106 # attribute of c.SSHControllerLauncher.
107
108 # set the ssh cmd for launching remote commands. The default is ['ssh']
109 # c.SSHLauncher.ssh_cmd = ['ssh']
110
111 # set the ssh cmd for launching remote commands. The default is ['ssh']
112 # c.SSHLauncher.ssh_args = ['tt']
113
114 # Set the user and hostname for the controller
115 # c.SSHControllerLauncher.hostname = 'controller.example.com'
116 # c.SSHControllerLauncher.user = os.environ.get('USER','username')
117
118 # Set the arguments to be passed to ipcontrollerz
119 # note that remotely launched ipcontrollerz will not get the contents of
120 # the local ipcontrollerz_config.py unless it resides on the *remote host*
121 # in the location specified by the --cluster_dir argument.
122 # c.SSHControllerLauncher.program_args = ['-r', '-ip', '0.0.0.0', '--cluster_dir', '/path/to/cd']
123
124 # Set the default args passed to ipenginez for SSH launched engines
125 # c.SSHEngineSetLauncher.engine_args = ['--mpi', 'mpi4py']
99 126
127 # SSH engines are launched as a dict of locations/n-engines.
128 # if a value is a tuple instead of an int, it is assumed to be of the form
129 # (n, [args]), setting the arguments to passed to ipenginez on `host`.
130 # otherwise, c.SSHEngineSetLauncher.engine_args will be used as the default.
131
132 # In this case, there will be 3 engines at my.example.com, and
133 # 2 at you@ipython.scipy.org with a special json connector location.
134 # c.SSHEngineSetLauncher.engines = {'my.example.com' : 3,
135 # 'you@ipython.scipy.org' : (2, ['-f', '/path/to/ipcontroller-engine.json']}
136 # }
100 137
101 138 #-----------------------------------------------------------------------------
102 139 # Unix batch (PBS) schedulers launchers
103 140 #-----------------------------------------------------------------------------
104 141
142 # SGE and PBS are very similar. All configurables in this section called 'PBS*'
143 # also exist as 'SGE*'.
144
105 145 # The command line program to use to submit a PBS job.
106 # c.PBSControllerLauncher.submit_command = 'qsub'
146 # c.PBSLauncher.submit_command = ['qsub']
107 147
108 148 # The command line program to use to delete a PBS job.
109 # c.PBSControllerLauncher.delete_command = 'qdel'
149 # c.PBSLauncher.delete_command = ['qdel']
150
151 # The PBS queue in which the job should run
152 # c.PBSLauncher.queue = 'myqueue'
110 153
111 154 # A regular expression that takes the output of qsub and find the job id.
112 # c.PBSControllerLauncher.job_id_regexp = r'\d+'
155 # c.PBSLauncher.job_id_regexp = r'\d+'
156
157 # If for some reason the Controller and Engines have different options above, they
158 # can be set as c.PBSControllerLauncher.<option> etc.
159
160 # PBS and SGE have default templates, but you can specify your own, either as strings
161 # or from files, as described here:
113 162
114 163 # The batch submission script used to start the controller. This is where
115 # environment variables would be setup, etc. This string is interpolated using
164 # environment variables would be setup, etc. This string is interpreted using
116 165 # the Itpl module in IPython.external. Basically, you can use ${n} for the
117 166 # number of engine and ${cluster_dir} for the cluster_dir.
118 # c.PBSControllerLauncher.batch_template = """"""
167 # c.PBSControllerLauncher.batch_template = """
168 # #PBS -N ipcontroller
169 # #PBS -q $queue
170 #
171 # ipcontrollerz --cluster-dir $cluster_dir
172 # """
173
174 # You can also load this template from a file
175 # c.PBSControllerLauncher.batch_template_file = u"/path/to/my/template.sh"
119 176
120 177 # The name of the instantiated batch script that will actually be used to
121 178 # submit the job. This will be written to the cluster directory.
122 # c.PBSControllerLauncher.batch_file_name = u'pbs_batch_script_controller'
123
124
125 # The command line program to use to submit a PBS job.
126 # c.PBSEngineSetLauncher.submit_command = 'qsub'
127
128 # The command line program to use to delete a PBS job.
129 # c.PBSEngineSetLauncher.delete_command = 'qdel'
130
131 # A regular expression that takes the output of qsub and find the job id.
132 # c.PBSEngineSetLauncher.job_id_regexp = r'\d+'
179 # c.PBSControllerLauncher.batch_file_name = u'pbs_controller'
133 180
134 181 # The batch submission script used to start the engines. This is where
135 # environment variables would be setup, etc. This string is interpolated using
182 # environment variables would be setup, etc. This string is interpreted using
136 183 # the Itpl module in IPython.external. Basically, you can use ${n} for the
137 184 # number of engine and ${cluster_dir} for the cluster_dir.
138 # c.PBSEngineSetLauncher.batch_template = """"""
185 # c.PBSEngineSetLauncher.batch_template = """
186 # #PBS -N ipcontroller
187 # #PBS -l nprocs=$n
188 #
189 # ipenginez --cluster-dir $cluster_dir$s
190 # """
191
192 # You can also load this template from a file
193 # c.PBSControllerLauncher.batch_template_file = u"/path/to/my/template.sh"
139 194
140 195 # The name of the instantiated batch script that will actually be used to
141 196 # submit the job. This will be written to the cluster directory.
142 # c.PBSEngineSetLauncher.batch_file_name = u'pbs_batch_script_engines'
197 # c.PBSEngineSetLauncher.batch_file_name = u'pbs_engines'
198
199
143 200
144 201 #-----------------------------------------------------------------------------
145 202 # Windows HPC Server 2008 launcher configuration
146 203 #-----------------------------------------------------------------------------
147 204
148 205 # c.IPControllerJob.job_name = 'IPController'
149 206 # c.IPControllerJob.is_exclusive = False
150 207 # c.IPControllerJob.username = r'USERDOMAIN\USERNAME'
151 208 # c.IPControllerJob.priority = 'Highest'
152 209 # c.IPControllerJob.requested_nodes = ''
153 210 # c.IPControllerJob.project = 'MyProject'
154 211
155 212 # c.IPControllerTask.task_name = 'IPController'
156 213 # c.IPControllerTask.controller_cmd = [u'ipcontroller.exe']
157 214 # c.IPControllerTask.controller_args = ['--log-to-file', '--log-level', '40']
158 215 # c.IPControllerTask.environment_variables = {}
159 216
160 217 # c.WindowsHPCControllerLauncher.scheduler = 'HEADNODE'
161 218 # c.WindowsHPCControllerLauncher.job_file_name = u'ipcontroller_job.xml'
162 219
163 220
164 221 # c.IPEngineSetJob.job_name = 'IPEngineSet'
165 222 # c.IPEngineSetJob.is_exclusive = False
166 223 # c.IPEngineSetJob.username = r'USERDOMAIN\USERNAME'
167 224 # c.IPEngineSetJob.priority = 'Highest'
168 225 # c.IPEngineSetJob.requested_nodes = ''
169 226 # c.IPEngineSetJob.project = 'MyProject'
170 227
171 228 # c.IPEngineTask.task_name = 'IPEngine'
172 229 # c.IPEngineTask.engine_cmd = [u'ipengine.exe']
173 230 # c.IPEngineTask.engine_args = ['--log-to-file', '--log-level', '40']
174 231 # c.IPEngineTask.environment_variables = {}
175 232
176 233 # c.WindowsHPCEngineSetLauncher.scheduler = 'HEADNODE'
177 234 # c.WindowsHPCEngineSetLauncher.job_file_name = u'ipengineset_job.xml'
178 235
179 236
180 237
181 238
182 239
183 240
184 241
@@ -1,136 +1,180 b''
1 1 from IPython.config.loader import Config
2 2
3 3 c = get_config()
4 4
5 5 #-----------------------------------------------------------------------------
6 6 # Global configuration
7 7 #-----------------------------------------------------------------------------
8 8
9 9 # Basic Global config attributes
10 10
11 11 # Start up messages are logged to stdout using the logging module.
12 12 # These all happen before the twisted reactor is started and are
13 13 # useful for debugging purposes. Can be (10=DEBUG,20=INFO,30=WARN,40=CRITICAL)
14 14 # and smaller is more verbose.
15 15 # c.Global.log_level = 20
16 16
17 17 # Log to a file in cluster_dir/log, otherwise just log to sys.stdout.
18 18 # c.Global.log_to_file = False
19 19
20 20 # Remove old logs from cluster_dir/log before starting.
21 21 # c.Global.clean_logs = True
22 22
23 23 # A list of Python statements that will be run before starting the
24 24 # controller. This is provided because occasionally certain things need to
25 25 # be imported in the controller for pickling to work.
26 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 JSON files. If False, JSON files are regenerated
29 29 # each time the controller is run. If True, they will be reused, *but*, you
30 30 # also must set the network ports by hand. If set, this will override the
31 31 # values set for the client and engine connections below.
32 # c.Global.reuse_furls = True
32 # c.Global.reuse_files = True
33 33
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.
34 # Enable exec_key authentication on all messages. Default is True
36 35 # c.Global.secure = True
37 36
38 37 # The working directory for the process. The application will use os.chdir
39 38 # to change to this directory before starting.
40 39 # c.Global.work_dir = os.getcwd()
41 40
41 # The log url for logging to an `iploggerz` application. This will override
42 # log-to-file.
43 # c.Global.log_url = 'tcp://127.0.0.1:20202'
44
45 # The specific external IP that is used to disambiguate multi-interface URLs.
46 # The default behavior is to guess from external IPs gleaned from `socket`.
47 # c.Global.location = '192.168.1.123'
48
49 # The ssh server remote clients should use to connect to this controller.
50 # It must be a machine that can see the interface specified in client_ip.
51 # The default for client_ip is localhost, in which case the sshserver must
52 # be an external IP of the controller machine.
53 # c.Global.sshserver = 'controller.example.com'
54
55 # the url to use for registration. If set, this overrides engine-ip,
56 # engine-transport client-ip,client-transport, and regport.
57 # c.RegistrationFactory.url = 'tcp://*:12345'
58
59 # the port to use for registration. Clients and Engines both use this
60 # port for registration.
61 # c.RegistrationFactory.regport = 10101
62
42 63 #-----------------------------------------------------------------------------
43 # Configure the client services
64 # Configure the Task Scheduler
44 65 #-----------------------------------------------------------------------------
45 66
46 # Basic client service config attributes
67 # The routing scheme. 'pure' will use the pure-ZMQ scheduler. Any other
68 # value will use a Python scheduler with various routing schemes.
69 # python schemes are: lru, weighted, random, twobin. Default is 'weighted'.
70 # Note that the pure ZMQ scheduler does not support many features, such as
71 # dying engines, dependencies, or engine-subset load-balancing.
72 # c.ControllerFactory.scheme = 'pure'
47 73
48 # The network interface the controller will listen on for client connections.
49 # This should be an IP address or hostname of the controller's host. The empty
50 # string means listen on all interfaces.
51 # c.FCClientServiceFactory.ip = ''
74 # The pure ZMQ scheduler can limit the number of outstanding tasks per engine
75 # by using the ZMQ HWM option. This allows engines with long-running tasks
76 # to not steal too many tasks from other engines. The default is 0, which
77 # means agressively distribute messages, never waiting for them to finish.
78 # c.ControllerFactory.hwm = 1
52 79
53 # The TCP/IP port the controller will listen on for client connections. If 0
54 # a random port will be used. If the controller's host has a firewall running
55 # it must allow incoming traffic on this port.
56 # c.FCClientServiceFactory.port = 0
80 # Whether to use Threads or Processes to start the Schedulers. Threads will
81 # use less resources, but potentially reduce throughput. Default is to
82 # use processes. Note that the a Python scheduler will always be in a Process.
83 # c.ControllerFactory.usethreads
57 84
58 # The client learns how to connect to the controller by looking at the
59 # location field embedded in the FURL. If this field is empty, all network
60 # interfaces that the controller is listening on will be listed. To have the
61 # client connect on a particular interface, list it here.
62 # c.FCClientServiceFactory.location = ''
85 #-----------------------------------------------------------------------------
86 # Configure the Hub
87 #-----------------------------------------------------------------------------
88
89 # Which class to use for the db backend. Currently supported are DictDB (the
90 # default), and MongoDB. Uncomment this line to enable MongoDB, which will
91 # slow-down the Hub's responsiveness, but also reduce its memory footprint.
92 # c.HubFactory.db_class = 'IPython.parallel.mongodb.MongoDB'
63 93
64 # Use SSL encryption for the client connection.
65 # c.FCClientServiceFactory.secure = True
94 # The heartbeat ping frequency. This is the frequency (in ms) at which the
95 # Hub pings engines for heartbeats. This determines how quickly the Hub
96 # will react to engines coming and going. A lower number means faster response
97 # time, but more network activity. The default is 100ms
98 # c.HubFactory.ping = 100
66 99
67 # Reuse the client FURL each time the controller is started. If set, you must
68 # also pick a specific network port above (FCClientServiceFactory.port).
69 # c.FCClientServiceFactory.reuse_furls = False
100 # HubFactory queue port pairs, to set by name: mux, iopub, control, task. Set
101 # each as a tuple of length 2 of ints. The default is to find random
102 # available ports
103 # c.HubFactory.mux = (10102,10112)
70 104
71 105 #-----------------------------------------------------------------------------
72 # Configure the engine services
106 # Configure the client connections
73 107 #-----------------------------------------------------------------------------
74 108
75 # Basic config attributes for the engine services.
109 # Basic client connection config attributes
76 110
77 # The network interface the controller will listen on for engine connections.
78 # This should be an IP address or hostname of the controller's host. The empty
79 # string means listen on all interfaces.
80 # c.FCEngineServiceFactory.ip = ''
111 # The network interface the controller will listen on for client connections.
112 # This should be an IP address or interface on the controller. An asterisk
113 # means listen on all interfaces. The transport can be any transport
114 # supported by zeromq (tcp,epgm,pgm,ib,ipc):
115 # c.HubFactory.client_ip = '*'
116 # c.HubFactory.client_transport = 'tcp'
81 117
82 # The TCP/IP port the controller will listen on for engine connections. If 0
83 # a random port will be used. If the controller's host has a firewall running
84 # it must allow incoming traffic on this port.
85 # c.FCEngineServiceFactory.port = 0
118 # individual client ports to configure by name: query_port, notifier_port
119 # c.HubFactory.query_port = 12345
86 120
87 # The engine learns how to connect to the controller by looking at the
88 # location field embedded in the FURL. If this field is empty, all network
89 # interfaces that the controller is listening on will be listed. To have the
90 # client connect on a particular interface, list it here.
91 # c.FCEngineServiceFactory.location = ''
121 #-----------------------------------------------------------------------------
122 # Configure the engine connections
123 #-----------------------------------------------------------------------------
92 124
93 # Use SSL encryption for the engine connection.
94 # c.FCEngineServiceFactory.secure = True
125 # Basic config attributes for the engine connections.
95 126
96 # Reuse the client FURL each time the controller is started. If set, you must
97 # also pick a specific network port above (FCClientServiceFactory.port).
98 # c.FCEngineServiceFactory.reuse_furls = False
127 # The network interface the controller will listen on for engine connections.
128 # This should be an IP address or interface on the controller. An asterisk
129 # means listen on all interfaces. The transport can be any transport
130 # supported by zeromq (tcp,epgm,pgm,ib,ipc):
131 # c.HubFactory.engine_ip = '*'
132 # c.HubFactory.engine_transport = 'tcp'
133
134 # set the engine heartbeat ports to use:
135 # c.HubFactory.hb = (10303,10313)
99 136
100 137 #-----------------------------------------------------------------------------
101 # Developer level configuration attributes
138 # Configure the TaskRecord database backend
102 139 #-----------------------------------------------------------------------------
103 140
104 # You shouldn't have to modify anything in this section. These attributes
105 # are more for developers who want to change the behavior of the controller
106 # at a fundamental level.
107
108 # c.FCClientServiceFactory.cert_file = u'ipcontroller-client.pem'
109
110 # default_client_interfaces = Config()
111 # default_client_interfaces.Task.interface_chain = [
112 # 'IPython.kernel.task.ITaskController',
113 # 'IPython.kernel.taskfc.IFCTaskController'
114 # ]
115 #
116 # default_client_interfaces.Task.furl_file = u'ipcontroller-tc.furl'
117 #
118 # default_client_interfaces.MultiEngine.interface_chain = [
119 # 'IPython.kernel.multiengine.IMultiEngine',
120 # 'IPython.kernel.multienginefc.IFCSynchronousMultiEngine'
121 # ]
122 #
123 # default_client_interfaces.MultiEngine.furl_file = u'ipcontroller-mec.furl'
124 #
125 # c.FCEngineServiceFactory.interfaces = default_client_interfaces
126
127 # c.FCEngineServiceFactory.cert_file = u'ipcontroller-engine.pem'
128
129 # default_engine_interfaces = Config()
130 # default_engine_interfaces.Default.interface_chain = [
131 # 'IPython.kernel.enginefc.IFCControllerBase'
132 # ]
133 #
134 # default_engine_interfaces.Default.furl_file = u'ipcontroller-engine.furl'
135 #
136 # c.FCEngineServiceFactory.interfaces = default_engine_interfaces
141 # For memory/persistance reasons, tasks can be stored out-of-memory in a database.
142 # Currently, only sqlite and mongodb are supported as backends, but the interface
143 # is fairly simple, so advanced developers could write their own backend.
144
145 # ----- in-memory configuration --------
146 # this line restores the default behavior: in-memory storage of all results.
147 # c.HubFactory.db_class = 'IPython.parallel.dictdb.DictDB'
148
149 # ----- sqlite configuration --------
150 # use this line to activate sqlite:
151 # c.HubFactory.db_class = 'IPython.parallel.sqlitedb.SQLiteDB'
152
153 # You can specify the name of the db-file. By default, this will be located
154 # in the active cluster_dir, e.g. ~/.ipython/clusterz_default/tasks.db
155 # c.SQLiteDB.filename = 'tasks.db'
156
157 # You can also specify the location of the db-file, if you want it to be somewhere
158 # other than the cluster_dir.
159 # c.SQLiteDB.location = '/scratch/'
160
161 # This will specify the name of the table for the controller to use. The default
162 # behavior is to use the session ID of the SessionFactory object (a uuid). Overriding
163 # this will result in results persisting for multiple sessions.
164 # c.SQLiteDB.table = 'results'
165
166 # ----- mongodb configuration --------
167 # use this line to activate mongodb:
168 # c.HubFactory.db_class = 'IPython.parallel.mongodb.MongoDB'
169
170 # You can specify the args and kwargs pymongo will use when creating the Connection.
171 # For more information on what these options might be, see pymongo documentation.
172 # c.MongoDB.connection_kwargs = {}
173 # c.MongoDB.connection_args = []
174
175 # This will specify the name of the mongo database for the controller to use. The default
176 # behavior is to use the session ID of the SessionFactory object (a uuid). Overriding
177 # this will result in task results persisting through multiple sessions.
178 # c.MongoDB.database = 'ipythondb'
179
180
@@ -1,90 +1,85 b''
1 1 c = get_config()
2 2
3 3 #-----------------------------------------------------------------------------
4 4 # Global configuration
5 5 #-----------------------------------------------------------------------------
6 6
7 7 # Start up messages are logged to stdout using the logging module.
8 8 # These all happen before the twisted reactor is started and are
9 9 # useful for debugging purposes. Can be (10=DEBUG,20=INFO,30=WARN,40=CRITICAL)
10 10 # and smaller is more verbose.
11 11 # c.Global.log_level = 20
12 12
13 13 # Log to a file in cluster_dir/log, otherwise just log to sys.stdout.
14 14 # c.Global.log_to_file = False
15 15
16 16 # Remove old logs from cluster_dir/log before starting.
17 17 # c.Global.clean_logs = True
18 18
19 19 # A list of strings that will be executed in the users namespace on the engine
20 20 # before it connects to the controller.
21 21 # c.Global.exec_lines = ['import numpy']
22 22
23 23 # The engine will try to connect to the controller multiple times, to allow
24 24 # the controller time to startup and write its FURL file. These parameters
25 25 # control the number of retries (connect_max_tries) and the initial delay
26 26 # (connect_delay) between attemps. The actual delay between attempts gets
27 27 # longer each time by a factor of 1.5 (delay[i] = 1.5*delay[i-1])
28 28 # those attemps.
29 29 # c.Global.connect_delay = 0.1
30 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
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.
35 # c.Global.furl_file = u''
32 # By default, the engine will look for the controller's JSON file in its own
33 # cluster directory. Sometimes, the JSON file will be elsewhere and this
34 # attribute can be set to the full path of the JSON file.
35 # c.Global.url_file = u'/path/to/my/ipcontroller-engine.json'
36 36
37 37 # The working directory for the process. The application will use os.chdir
38 38 # to change to this directory before starting.
39 39 # c.Global.work_dir = os.getcwd()
40 40
41 41 #-----------------------------------------------------------------------------
42 42 # MPI configuration
43 43 #-----------------------------------------------------------------------------
44 44
45 45 # Upon starting the engine can be configured to call MPI_Init. This section
46 46 # configures that.
47 47
48 48 # Select which MPI section to execute to setup MPI. The value of this
49 49 # attribute must match the name of another attribute in the MPI config
50 50 # section (mpi4py, pytrilinos, etc.). This can also be set by the --mpi
51 51 # command line option.
52 52 # c.MPI.use = ''
53 53
54 54 # Initialize MPI using mpi4py. To use this, set c.MPI.use = 'mpi4py' to use
55 55 # --mpi=mpi4py at the command line.
56 56 # c.MPI.mpi4py = """from mpi4py import MPI as mpi
57 57 # mpi.size = mpi.COMM_WORLD.Get_size()
58 58 # mpi.rank = mpi.COMM_WORLD.Get_rank()
59 59 # """
60 60
61 61 # Initialize MPI using pytrilinos. To use this, set c.MPI.use = 'pytrilinos'
62 62 # to use --mpi=pytrilinos at the command line.
63 63 # c.MPI.pytrilinos = """from PyTrilinos import Epetra
64 64 # class SimpleStruct:
65 65 # pass
66 66 # mpi = SimpleStruct()
67 67 # mpi.rank = 0
68 68 # mpi.size = 0
69 69 # """
70 70
71 71 #-----------------------------------------------------------------------------
72 72 # Developer level configuration attributes
73 73 #-----------------------------------------------------------------------------
74 74
75 75 # You shouldn't have to modify anything in this section. These attributes
76 76 # are more for developers who want to change the behavior of the controller
77 77 # at a fundamental level.
78 78
79 79 # You should not have to change these attributes.
80 80
81 # c.Global.shell_class = 'IPython.kernel.core.interpreter.Interpreter'
82
83 # c.Global.furl_file_name = u'ipcontroller-engine.furl'
84
85
86
81 # c.Global.url_file_name = u'ipcontroller-engine.furl'
87 82
88 83
89 84
90 85
@@ -1,537 +1,537 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 """
4 4 The IPython cluster directory
5 5 """
6 6
7 7 #-----------------------------------------------------------------------------
8 8 # Copyright (C) 2008-2009 The IPython Development Team
9 9 #
10 10 # Distributed under the terms of the BSD License. The full license is in
11 11 # the file COPYING, distributed as part of this software.
12 12 #-----------------------------------------------------------------------------
13 13
14 14 #-----------------------------------------------------------------------------
15 15 # Imports
16 16 #-----------------------------------------------------------------------------
17 17
18 18 from __future__ import with_statement
19 19
20 20 import os
21 21 import logging
22 22 import re
23 23 import shutil
24 24 import sys
25 25
26 26 from IPython.config.loader import PyFileConfigLoader
27 27 from IPython.config.configurable import Configurable
28 28 from IPython.core.application import Application, BaseAppConfigLoader
29 29 from IPython.core.crashhandler import CrashHandler
30 30 from IPython.core import release
31 31 from IPython.utils.path import (
32 32 get_ipython_package_dir,
33 33 expand_path
34 34 )
35 35 from IPython.utils.traitlets import Unicode
36 36
37 37 #-----------------------------------------------------------------------------
38 38 # Module errors
39 39 #-----------------------------------------------------------------------------
40 40
41 41 class ClusterDirError(Exception):
42 42 pass
43 43
44 44
45 45 class PIDFileError(Exception):
46 46 pass
47 47
48 48
49 49 #-----------------------------------------------------------------------------
50 50 # Class for managing cluster directories
51 51 #-----------------------------------------------------------------------------
52 52
53 53 class ClusterDir(Configurable):
54 54 """An object to manage the cluster directory and its resources.
55 55
56 56 The cluster directory is used by :command:`ipengine`,
57 57 :command:`ipcontroller` and :command:`ipclsuter` to manage the
58 58 configuration, logging and security of these applications.
59 59
60 60 This object knows how to find, create and manage these directories. This
61 61 should be used by any code that want's to handle cluster directories.
62 62 """
63 63
64 64 security_dir_name = Unicode('security')
65 65 log_dir_name = Unicode('log')
66 66 pid_dir_name = Unicode('pid')
67 67 security_dir = Unicode(u'')
68 68 log_dir = Unicode(u'')
69 69 pid_dir = Unicode(u'')
70 70 location = Unicode(u'')
71 71
72 72 def __init__(self, location=u''):
73 73 super(ClusterDir, self).__init__(location=location)
74 74
75 75 def _location_changed(self, name, old, new):
76 76 if not os.path.isdir(new):
77 77 os.makedirs(new)
78 78 self.security_dir = os.path.join(new, self.security_dir_name)
79 79 self.log_dir = os.path.join(new, self.log_dir_name)
80 80 self.pid_dir = os.path.join(new, self.pid_dir_name)
81 81 self.check_dirs()
82 82
83 83 def _log_dir_changed(self, name, old, new):
84 84 self.check_log_dir()
85 85
86 86 def check_log_dir(self):
87 87 if not os.path.isdir(self.log_dir):
88 88 os.mkdir(self.log_dir)
89 89
90 90 def _security_dir_changed(self, name, old, new):
91 91 self.check_security_dir()
92 92
93 93 def check_security_dir(self):
94 94 if not os.path.isdir(self.security_dir):
95 95 os.mkdir(self.security_dir, 0700)
96 96 os.chmod(self.security_dir, 0700)
97 97
98 98 def _pid_dir_changed(self, name, old, new):
99 99 self.check_pid_dir()
100 100
101 101 def check_pid_dir(self):
102 102 if not os.path.isdir(self.pid_dir):
103 103 os.mkdir(self.pid_dir, 0700)
104 104 os.chmod(self.pid_dir, 0700)
105 105
106 106 def check_dirs(self):
107 107 self.check_security_dir()
108 108 self.check_log_dir()
109 109 self.check_pid_dir()
110 110
111 111 def load_config_file(self, filename):
112 112 """Load a config file from the top level of the cluster dir.
113 113
114 114 Parameters
115 115 ----------
116 116 filename : unicode or str
117 117 The filename only of the config file that must be located in
118 118 the top-level of the cluster directory.
119 119 """
120 120 loader = PyFileConfigLoader(filename, self.location)
121 121 return loader.load_config()
122 122
123 123 def copy_config_file(self, config_file, path=None, overwrite=False):
124 124 """Copy a default config file into the active cluster directory.
125 125
126 126 Default configuration files are kept in :mod:`IPython.config.default`.
127 127 This function moves these from that location to the working cluster
128 128 directory.
129 129 """
130 130 if path is None:
131 131 import IPython.config.default
132 132 path = IPython.config.default.__file__.split(os.path.sep)[:-1]
133 133 path = os.path.sep.join(path)
134 134 src = os.path.join(path, config_file)
135 135 dst = os.path.join(self.location, config_file)
136 136 if not os.path.isfile(dst) or overwrite:
137 137 shutil.copy(src, dst)
138 138
139 139 def copy_all_config_files(self, path=None, overwrite=False):
140 140 """Copy all config files into the active cluster directory."""
141 for f in [u'ipcontrollerz_config.py', u'ipenginez_config.py',
142 u'ipclusterz_config.py']:
141 for f in [u'ipcontroller_config.py', u'ipengine_config.py',
142 u'ipcluster_config.py']:
143 143 self.copy_config_file(f, path=path, overwrite=overwrite)
144 144
145 145 @classmethod
146 146 def create_cluster_dir(csl, cluster_dir):
147 147 """Create a new cluster directory given a full path.
148 148
149 149 Parameters
150 150 ----------
151 151 cluster_dir : str
152 152 The full path to the cluster directory. If it does exist, it will
153 153 be used. If not, it will be created.
154 154 """
155 155 return ClusterDir(location=cluster_dir)
156 156
157 157 @classmethod
158 158 def create_cluster_dir_by_profile(cls, path, profile=u'default'):
159 159 """Create a cluster dir by profile name and path.
160 160
161 161 Parameters
162 162 ----------
163 163 path : str
164 164 The path (directory) to put the cluster directory in.
165 165 profile : str
166 166 The name of the profile. The name of the cluster directory will
167 be "clusterz_<profile>".
167 be "cluster_<profile>".
168 168 """
169 169 if not os.path.isdir(path):
170 170 raise ClusterDirError('Directory not found: %s' % path)
171 cluster_dir = os.path.join(path, u'clusterz_' + profile)
171 cluster_dir = os.path.join(path, u'cluster_' + profile)
172 172 return ClusterDir(location=cluster_dir)
173 173
174 174 @classmethod
175 175 def find_cluster_dir_by_profile(cls, ipython_dir, profile=u'default'):
176 176 """Find an existing cluster dir by profile name, return its ClusterDir.
177 177
178 178 This searches through a sequence of paths for a cluster dir. If it
179 179 is not found, a :class:`ClusterDirError` exception will be raised.
180 180
181 181 The search path algorithm is:
182 182 1. ``os.getcwd()``
183 183 2. ``ipython_dir``
184 184 3. The directories found in the ":" separated
185 185 :env:`IPCLUSTER_DIR_PATH` environment variable.
186 186
187 187 Parameters
188 188 ----------
189 189 ipython_dir : unicode or str
190 190 The IPython directory to use.
191 191 profile : unicode or str
192 192 The name of the profile. The name of the cluster directory
193 will be "clusterz_<profile>".
193 will be "cluster_<profile>".
194 194 """
195 dirname = u'clusterz_' + profile
195 dirname = u'cluster_' + profile
196 196 cluster_dir_paths = os.environ.get('IPCLUSTER_DIR_PATH','')
197 197 if cluster_dir_paths:
198 198 cluster_dir_paths = cluster_dir_paths.split(':')
199 199 else:
200 200 cluster_dir_paths = []
201 201 paths = [os.getcwd(), ipython_dir] + cluster_dir_paths
202 202 for p in paths:
203 203 cluster_dir = os.path.join(p, dirname)
204 204 if os.path.isdir(cluster_dir):
205 205 return ClusterDir(location=cluster_dir)
206 206 else:
207 207 raise ClusterDirError('Cluster directory not found in paths: %s' % dirname)
208 208
209 209 @classmethod
210 210 def find_cluster_dir(cls, cluster_dir):
211 211 """Find/create a cluster dir and return its ClusterDir.
212 212
213 213 This will create the cluster directory if it doesn't exist.
214 214
215 215 Parameters
216 216 ----------
217 217 cluster_dir : unicode or str
218 218 The path of the cluster directory. This is expanded using
219 219 :func:`IPython.utils.genutils.expand_path`.
220 220 """
221 221 cluster_dir = expand_path(cluster_dir)
222 222 if not os.path.isdir(cluster_dir):
223 223 raise ClusterDirError('Cluster directory not found: %s' % cluster_dir)
224 224 return ClusterDir(location=cluster_dir)
225 225
226 226
227 227 #-----------------------------------------------------------------------------
228 228 # Command line options
229 229 #-----------------------------------------------------------------------------
230 230
231 231 class ClusterDirConfigLoader(BaseAppConfigLoader):
232 232
233 233 def _add_cluster_profile(self, parser):
234 234 paa = parser.add_argument
235 235 paa('-p', '--profile',
236 236 dest='Global.profile',type=unicode,
237 237 help=
238 238 """The string name of the profile to be used. This determines the name
239 239 of the cluster dir as: cluster_<profile>. The default profile is named
240 240 'default'. The cluster directory is resolve this way if the
241 241 --cluster-dir option is not used.""",
242 242 metavar='Global.profile')
243 243
244 244 def _add_cluster_dir(self, parser):
245 245 paa = parser.add_argument
246 246 paa('--cluster-dir',
247 247 dest='Global.cluster_dir',type=unicode,
248 248 help="""Set the cluster dir. This overrides the logic used by the
249 249 --profile option.""",
250 250 metavar='Global.cluster_dir')
251 251
252 252 def _add_work_dir(self, parser):
253 253 paa = parser.add_argument
254 254 paa('--work-dir',
255 255 dest='Global.work_dir',type=unicode,
256 256 help='Set the working dir for the process.',
257 257 metavar='Global.work_dir')
258 258
259 259 def _add_clean_logs(self, parser):
260 260 paa = parser.add_argument
261 261 paa('--clean-logs',
262 262 dest='Global.clean_logs', action='store_true',
263 263 help='Delete old log flies before starting.')
264 264
265 265 def _add_no_clean_logs(self, parser):
266 266 paa = parser.add_argument
267 267 paa('--no-clean-logs',
268 268 dest='Global.clean_logs', action='store_false',
269 269 help="Don't Delete old log flies before starting.")
270 270
271 271 def _add_arguments(self):
272 272 super(ClusterDirConfigLoader, self)._add_arguments()
273 273 self._add_cluster_profile(self.parser)
274 274 self._add_cluster_dir(self.parser)
275 275 self._add_work_dir(self.parser)
276 276 self._add_clean_logs(self.parser)
277 277 self._add_no_clean_logs(self.parser)
278 278
279 279
280 280 #-----------------------------------------------------------------------------
281 281 # Crash handler for this application
282 282 #-----------------------------------------------------------------------------
283 283
284 284
285 285 _message_template = """\
286 286 Oops, $self.app_name crashed. We do our best to make it stable, but...
287 287
288 288 A crash report was automatically generated with the following information:
289 289 - A verbatim copy of the crash traceback.
290 290 - Data on your current $self.app_name configuration.
291 291
292 292 It was left in the file named:
293 293 \t'$self.crash_report_fname'
294 294 If you can email this file to the developers, the information in it will help
295 295 them in understanding and correcting the problem.
296 296
297 297 You can mail it to: $self.contact_name at $self.contact_email
298 298 with the subject '$self.app_name Crash Report'.
299 299
300 300 If you want to do it now, the following command will work (under Unix):
301 301 mail -s '$self.app_name Crash Report' $self.contact_email < $self.crash_report_fname
302 302
303 303 To ensure accurate tracking of this issue, please file a report about it at:
304 304 $self.bug_tracker
305 305 """
306 306
307 307 class ClusterDirCrashHandler(CrashHandler):
308 308 """sys.excepthook for IPython itself, leaves a detailed report on disk."""
309 309
310 310 message_template = _message_template
311 311
312 312 def __init__(self, app):
313 313 contact_name = release.authors['Brian'][0]
314 314 contact_email = release.authors['Brian'][1]
315 315 bug_tracker = 'http://github.com/ipython/ipython/issues'
316 316 super(ClusterDirCrashHandler,self).__init__(
317 317 app, contact_name, contact_email, bug_tracker
318 318 )
319 319
320 320
321 321 #-----------------------------------------------------------------------------
322 322 # Main application
323 323 #-----------------------------------------------------------------------------
324 324
325 325 class ApplicationWithClusterDir(Application):
326 326 """An application that puts everything into a cluster directory.
327 327
328 328 Instead of looking for things in the ipython_dir, this type of application
329 329 will use its own private directory called the "cluster directory"
330 330 for things like config files, log files, etc.
331 331
332 332 The cluster directory is resolved as follows:
333 333
334 334 * If the ``--cluster-dir`` option is given, it is used.
335 335 * If ``--cluster-dir`` is not given, the application directory is
336 336 resolve using the profile name as ``cluster_<profile>``. The search
337 337 path for this directory is then i) cwd if it is found there
338 338 and ii) in ipython_dir otherwise.
339 339
340 340 The config file for the application is to be put in the cluster
341 341 dir and named the value of the ``config_file_name`` class attribute.
342 342 """
343 343
344 344 command_line_loader = ClusterDirConfigLoader
345 345 crash_handler_class = ClusterDirCrashHandler
346 346 auto_create_cluster_dir = True
347 347 # temporarily override default_log_level to INFO
348 348 default_log_level = logging.INFO
349 349
350 350 def create_default_config(self):
351 351 super(ApplicationWithClusterDir, self).create_default_config()
352 352 self.default_config.Global.profile = u'default'
353 353 self.default_config.Global.cluster_dir = u''
354 354 self.default_config.Global.work_dir = os.getcwd()
355 355 self.default_config.Global.log_to_file = False
356 356 self.default_config.Global.log_url = None
357 357 self.default_config.Global.clean_logs = False
358 358
359 359 def find_resources(self):
360 360 """This resolves the cluster directory.
361 361
362 362 This tries to find the cluster directory and if successful, it will
363 363 have done:
364 364 * Sets ``self.cluster_dir_obj`` to the :class:`ClusterDir` object for
365 365 the application.
366 366 * Sets ``self.cluster_dir`` attribute of the application and config
367 367 objects.
368 368
369 369 The algorithm used for this is as follows:
370 370 1. Try ``Global.cluster_dir``.
371 371 2. Try using ``Global.profile``.
372 372 3. If both of these fail and ``self.auto_create_cluster_dir`` is
373 373 ``True``, then create the new cluster dir in the IPython directory.
374 374 4. If all fails, then raise :class:`ClusterDirError`.
375 375 """
376 376
377 377 try:
378 378 cluster_dir = self.command_line_config.Global.cluster_dir
379 379 except AttributeError:
380 380 cluster_dir = self.default_config.Global.cluster_dir
381 381 cluster_dir = expand_path(cluster_dir)
382 382 try:
383 383 self.cluster_dir_obj = ClusterDir.find_cluster_dir(cluster_dir)
384 384 except ClusterDirError:
385 385 pass
386 386 else:
387 387 self.log.info('Using existing cluster dir: %s' % \
388 388 self.cluster_dir_obj.location
389 389 )
390 390 self.finish_cluster_dir()
391 391 return
392 392
393 393 try:
394 394 self.profile = self.command_line_config.Global.profile
395 395 except AttributeError:
396 396 self.profile = self.default_config.Global.profile
397 397 try:
398 398 self.cluster_dir_obj = ClusterDir.find_cluster_dir_by_profile(
399 399 self.ipython_dir, self.profile)
400 400 except ClusterDirError:
401 401 pass
402 402 else:
403 403 self.log.info('Using existing cluster dir: %s' % \
404 404 self.cluster_dir_obj.location
405 405 )
406 406 self.finish_cluster_dir()
407 407 return
408 408
409 409 if self.auto_create_cluster_dir:
410 410 self.cluster_dir_obj = ClusterDir.create_cluster_dir_by_profile(
411 411 self.ipython_dir, self.profile
412 412 )
413 413 self.log.info('Creating new cluster dir: %s' % \
414 414 self.cluster_dir_obj.location
415 415 )
416 416 self.finish_cluster_dir()
417 417 else:
418 418 raise ClusterDirError('Could not find a valid cluster directory.')
419 419
420 420 def finish_cluster_dir(self):
421 421 # Set the cluster directory
422 422 self.cluster_dir = self.cluster_dir_obj.location
423 423
424 424 # These have to be set because they could be different from the one
425 425 # that we just computed. Because command line has the highest
426 426 # priority, this will always end up in the master_config.
427 427 self.default_config.Global.cluster_dir = self.cluster_dir
428 428 self.command_line_config.Global.cluster_dir = self.cluster_dir
429 429
430 430 def find_config_file_name(self):
431 431 """Find the config file name for this application."""
432 432 # For this type of Application it should be set as a class attribute.
433 433 if not hasattr(self, 'default_config_file_name'):
434 434 self.log.critical("No config filename found")
435 435 else:
436 436 self.config_file_name = self.default_config_file_name
437 437
438 438 def find_config_file_paths(self):
439 439 # Set the search path to to the cluster directory. We should NOT
440 440 # include IPython.config.default here as the default config files
441 441 # are ALWAYS automatically moved to the cluster directory.
442 442 conf_dir = os.path.join(get_ipython_package_dir(), 'config', 'default')
443 443 self.config_file_paths = (self.cluster_dir,)
444 444
445 445 def pre_construct(self):
446 446 # The log and security dirs were set earlier, but here we put them
447 447 # into the config and log them.
448 448 config = self.master_config
449 449 sdir = self.cluster_dir_obj.security_dir
450 450 self.security_dir = config.Global.security_dir = sdir
451 451 ldir = self.cluster_dir_obj.log_dir
452 452 self.log_dir = config.Global.log_dir = ldir
453 453 pdir = self.cluster_dir_obj.pid_dir
454 454 self.pid_dir = config.Global.pid_dir = pdir
455 455 self.log.info("Cluster directory set to: %s" % self.cluster_dir)
456 456 config.Global.work_dir = unicode(expand_path(config.Global.work_dir))
457 457 # Change to the working directory. We do this just before construct
458 458 # is called so all the components there have the right working dir.
459 459 self.to_work_dir()
460 460
461 461 def to_work_dir(self):
462 462 wd = self.master_config.Global.work_dir
463 463 if unicode(wd) != unicode(os.getcwd()):
464 464 os.chdir(wd)
465 465 self.log.info("Changing to working dir: %s" % wd)
466 466
467 467 def start_logging(self):
468 468 # Remove old log files
469 469 if self.master_config.Global.clean_logs:
470 470 log_dir = self.master_config.Global.log_dir
471 471 for f in os.listdir(log_dir):
472 472 if re.match(r'%s-\d+\.(log|err|out)'%self.name,f):
473 473 # if f.startswith(self.name + u'-') and f.endswith('.log'):
474 474 os.remove(os.path.join(log_dir, f))
475 475 # Start logging to the new log file
476 476 if self.master_config.Global.log_to_file:
477 477 log_filename = self.name + u'-' + str(os.getpid()) + u'.log'
478 478 logfile = os.path.join(self.log_dir, log_filename)
479 479 open_log_file = open(logfile, 'w')
480 480 elif self.master_config.Global.log_url:
481 481 open_log_file = None
482 482 else:
483 483 open_log_file = sys.stdout
484 484 if open_log_file is not None:
485 485 self.log.removeHandler(self._log_handler)
486 486 self._log_handler = logging.StreamHandler(open_log_file)
487 487 self._log_formatter = logging.Formatter("[%(name)s] %(message)s")
488 488 self._log_handler.setFormatter(self._log_formatter)
489 489 self.log.addHandler(self._log_handler)
490 490 # log.startLogging(open_log_file)
491 491
492 492 def write_pid_file(self, overwrite=False):
493 493 """Create a .pid file in the pid_dir with my pid.
494 494
495 495 This must be called after pre_construct, which sets `self.pid_dir`.
496 496 This raises :exc:`PIDFileError` if the pid file exists already.
497 497 """
498 498 pid_file = os.path.join(self.pid_dir, self.name + u'.pid')
499 499 if os.path.isfile(pid_file):
500 500 pid = self.get_pid_from_file()
501 501 if not overwrite:
502 502 raise PIDFileError(
503 503 'The pid file [%s] already exists. \nThis could mean that this '
504 504 'server is already running with [pid=%s].' % (pid_file, pid)
505 505 )
506 506 with open(pid_file, 'w') as f:
507 507 self.log.info("Creating pid file: %s" % pid_file)
508 508 f.write(repr(os.getpid())+'\n')
509 509
510 510 def remove_pid_file(self):
511 511 """Remove the pid file.
512 512
513 513 This should be called at shutdown by registering a callback with
514 514 :func:`reactor.addSystemEventTrigger`. This needs to return
515 515 ``None``.
516 516 """
517 517 pid_file = os.path.join(self.pid_dir, self.name + u'.pid')
518 518 if os.path.isfile(pid_file):
519 519 try:
520 520 self.log.info("Removing pid file: %s" % pid_file)
521 521 os.remove(pid_file)
522 522 except:
523 523 self.log.warn("Error removing the pid file: %s" % pid_file)
524 524
525 525 def get_pid_from_file(self):
526 526 """Get the pid from the pid file.
527 527
528 528 If the pid file doesn't exist a :exc:`PIDFileError` is raised.
529 529 """
530 530 pid_file = os.path.join(self.pid_dir, self.name + u'.pid')
531 531 if os.path.isfile(pid_file):
532 532 with open(pid_file, 'r') as f:
533 533 pid = int(f.read().strip())
534 534 return pid
535 535 else:
536 536 raise PIDFileError('pid file not found: %s' % pid_file)
537 537
@@ -1,592 +1,592 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 """
4 4 The ipcluster application.
5 5 """
6 6
7 7 #-----------------------------------------------------------------------------
8 8 # Copyright (C) 2008-2009 The IPython Development Team
9 9 #
10 10 # Distributed under the terms of the BSD License. The full license is in
11 11 # the file COPYING, distributed as part of this software.
12 12 #-----------------------------------------------------------------------------
13 13
14 14 #-----------------------------------------------------------------------------
15 15 # Imports
16 16 #-----------------------------------------------------------------------------
17 17
18 18 import errno
19 19 import logging
20 20 import os
21 21 import re
22 22 import signal
23 23
24 24 import zmq
25 25 from zmq.eventloop import ioloop
26 26
27 27 from IPython.external.argparse import ArgumentParser, SUPPRESS
28 28 from IPython.utils.importstring import import_item
29 29 from IPython.parallel.clusterdir import (
30 30 ApplicationWithClusterDir, ClusterDirConfigLoader,
31 31 ClusterDirError, PIDFileError
32 32 )
33 33
34 34
35 35 #-----------------------------------------------------------------------------
36 36 # Module level variables
37 37 #-----------------------------------------------------------------------------
38 38
39 39
40 default_config_file_name = u'ipclusterz_config.py'
40 default_config_file_name = u'ipcluster_config.py'
41 41
42 42
43 43 _description = """\
44 44 Start an IPython cluster for parallel computing.\n\n
45 45
46 46 An IPython cluster consists of 1 controller and 1 or more engines.
47 47 This command automates the startup of these processes using a wide
48 48 range of startup methods (SSH, local processes, PBS, mpiexec,
49 49 Windows HPC Server 2008). To start a cluster with 4 engines on your
50 local host simply do 'ipclusterz start -n 4'. For more complex usage
51 you will typically do 'ipclusterz create -p mycluster', then edit
52 configuration files, followed by 'ipclusterz start -p mycluster -n 4'.
50 local host simply do 'ipcluster start -n 4'. For more complex usage
51 you will typically do 'ipcluster create -p mycluster', then edit
52 configuration files, followed by 'ipcluster start -p mycluster -n 4'.
53 53 """
54 54
55 55
56 56 # Exit codes for ipcluster
57 57
58 58 # This will be the exit code if the ipcluster appears to be running because
59 59 # a .pid file exists
60 60 ALREADY_STARTED = 10
61 61
62 62
63 63 # This will be the exit code if ipcluster stop is run, but there is not .pid
64 64 # file to be found.
65 65 ALREADY_STOPPED = 11
66 66
67 67 # This will be the exit code if ipcluster engines is run, but there is not .pid
68 68 # file to be found.
69 69 NO_CLUSTER = 12
70 70
71 71
72 72 #-----------------------------------------------------------------------------
73 73 # Command line options
74 74 #-----------------------------------------------------------------------------
75 75
76 76
77 77 class IPClusterAppConfigLoader(ClusterDirConfigLoader):
78 78
79 79 def _add_arguments(self):
80 80 # Don't call ClusterDirConfigLoader._add_arguments as we don't want
81 81 # its defaults on self.parser. Instead, we will put those on
82 82 # default options on our subparsers.
83 83
84 84 # This has all the common options that all subcommands use
85 85 parent_parser1 = ArgumentParser(
86 86 add_help=False,
87 87 argument_default=SUPPRESS
88 88 )
89 89 self._add_ipython_dir(parent_parser1)
90 90 self._add_log_level(parent_parser1)
91 91
92 92 # This has all the common options that other subcommands use
93 93 parent_parser2 = ArgumentParser(
94 94 add_help=False,
95 95 argument_default=SUPPRESS
96 96 )
97 97 self._add_cluster_profile(parent_parser2)
98 98 self._add_cluster_dir(parent_parser2)
99 99 self._add_work_dir(parent_parser2)
100 100 paa = parent_parser2.add_argument
101 101 paa('--log-to-file',
102 102 action='store_true', dest='Global.log_to_file',
103 103 help='Log to a file in the log directory (default is stdout)')
104 104
105 105 # Create the object used to create the subparsers.
106 106 subparsers = self.parser.add_subparsers(
107 107 dest='Global.subcommand',
108 108 title='ipcluster subcommands',
109 109 description=
110 110 """ipcluster has a variety of subcommands. The general way of
111 running ipcluster is 'ipclusterz <cmd> [options]'. To get help
112 on a particular subcommand do 'ipclusterz <cmd> -h'."""
113 # help="For more help, type 'ipclusterz <cmd> -h'",
111 running ipcluster is 'ipcluster <cmd> [options]'. To get help
112 on a particular subcommand do 'ipcluster <cmd> -h'."""
113 # help="For more help, type 'ipcluster <cmd> -h'",
114 114 )
115 115
116 116 # The "list" subcommand parser
117 117 parser_list = subparsers.add_parser(
118 118 'list',
119 119 parents=[parent_parser1],
120 120 argument_default=SUPPRESS,
121 121 help="List all clusters in cwd and ipython_dir.",
122 122 description=
123 123 """List all available clusters, by cluster directory, that can
124 124 be found in the current working directly or in the ipython
125 125 directory. Cluster directories are named using the convention
126 'clusterz_<profile>'."""
126 'cluster_<profile>'."""
127 127 )
128 128
129 129 # The "create" subcommand parser
130 130 parser_create = subparsers.add_parser(
131 131 'create',
132 132 parents=[parent_parser1, parent_parser2],
133 133 argument_default=SUPPRESS,
134 134 help="Create a new cluster directory.",
135 135 description=
136 136 """Create an ipython cluster directory by its profile name or
137 137 cluster directory path. Cluster directories contain
138 138 configuration, log and security related files and are named
139 using the convention 'clusterz_<profile>'. By default they are
139 using the convention 'cluster_<profile>'. By default they are
140 140 located in your ipython directory. Once created, you will
141 141 probably need to edit the configuration files in the cluster
142 142 directory to configure your cluster. Most users will create a
143 143 cluster directory by profile name,
144 'ipclusterz create -p mycluster', which will put the directory
145 in '<ipython_dir>/clusterz_mycluster'.
144 'ipcluster create -p mycluster', which will put the directory
145 in '<ipython_dir>/cluster_mycluster'.
146 146 """
147 147 )
148 148 paa = parser_create.add_argument
149 149 paa('--reset-config',
150 150 dest='Global.reset_config', action='store_true',
151 151 help=
152 152 """Recopy the default config files to the cluster directory.
153 153 You will loose any modifications you have made to these files.""")
154 154
155 155 # The "start" subcommand parser
156 156 parser_start = subparsers.add_parser(
157 157 'start',
158 158 parents=[parent_parser1, parent_parser2],
159 159 argument_default=SUPPRESS,
160 160 help="Start a cluster.",
161 161 description=
162 162 """Start an ipython cluster by its profile name or cluster
163 163 directory. Cluster directories contain configuration, log and
164 164 security related files and are named using the convention
165 'clusterz_<profile>' and should be creating using the 'start'
165 'cluster_<profile>' and should be creating using the 'start'
166 166 subcommand of 'ipcluster'. If your cluster directory is in
167 167 the cwd or the ipython directory, you can simply refer to it
168 using its profile name, 'ipclusterz start -n 4 -p <profile>`,
168 using its profile name, 'ipcluster start -n 4 -p <profile>`,
169 169 otherwise use the '--cluster-dir' option.
170 170 """
171 171 )
172 172
173 173 paa = parser_start.add_argument
174 174 paa('-n', '--number',
175 175 type=int, dest='Global.n',
176 176 help='The number of engines to start.',
177 177 metavar='Global.n')
178 178 paa('--clean-logs',
179 179 dest='Global.clean_logs', action='store_true',
180 180 help='Delete old log flies before starting.')
181 181 paa('--no-clean-logs',
182 182 dest='Global.clean_logs', action='store_false',
183 183 help="Don't delete old log flies before starting.")
184 184 paa('--daemon',
185 185 dest='Global.daemonize', action='store_true',
186 186 help='Daemonize the ipcluster program. This implies --log-to-file')
187 187 paa('--no-daemon',
188 188 dest='Global.daemonize', action='store_false',
189 189 help="Dont't daemonize the ipcluster program.")
190 190 paa('--delay',
191 191 type=float, dest='Global.delay',
192 192 help="Specify the delay (in seconds) between starting the controller and starting the engine(s).")
193 193
194 194 # The "stop" subcommand parser
195 195 parser_stop = subparsers.add_parser(
196 196 'stop',
197 197 parents=[parent_parser1, parent_parser2],
198 198 argument_default=SUPPRESS,
199 199 help="Stop a running cluster.",
200 200 description=
201 201 """Stop a running ipython cluster by its profile name or cluster
202 202 directory. Cluster directories are named using the convention
203 'clusterz_<profile>'. If your cluster directory is in
203 'cluster_<profile>'. If your cluster directory is in
204 204 the cwd or the ipython directory, you can simply refer to it
205 using its profile name, 'ipclusterz stop -p <profile>`, otherwise
205 using its profile name, 'ipcluster stop -p <profile>`, otherwise
206 206 use the '--cluster-dir' option.
207 207 """
208 208 )
209 209 paa = parser_stop.add_argument
210 210 paa('--signal',
211 211 dest='Global.signal', type=int,
212 212 help="The signal number to use in stopping the cluster (default=2).",
213 213 metavar="Global.signal")
214 214
215 215 # the "engines" subcommand parser
216 216 parser_engines = subparsers.add_parser(
217 217 'engines',
218 218 parents=[parent_parser1, parent_parser2],
219 219 argument_default=SUPPRESS,
220 220 help="Attach some engines to an existing controller or cluster.",
221 221 description=
222 222 """Start one or more engines to connect to an existing Cluster
223 223 by profile name or cluster directory.
224 224 Cluster directories contain configuration, log and
225 225 security related files and are named using the convention
226 'clusterz_<profile>' and should be creating using the 'start'
226 'cluster_<profile>' and should be creating using the 'start'
227 227 subcommand of 'ipcluster'. If your cluster directory is in
228 228 the cwd or the ipython directory, you can simply refer to it
229 using its profile name, 'ipclusterz engines -n 4 -p <profile>`,
229 using its profile name, 'ipcluster engines -n 4 -p <profile>`,
230 230 otherwise use the '--cluster-dir' option.
231 231 """
232 232 )
233 233 paa = parser_engines.add_argument
234 234 paa('-n', '--number',
235 235 type=int, dest='Global.n',
236 236 help='The number of engines to start.',
237 237 metavar='Global.n')
238 238 paa('--daemon',
239 239 dest='Global.daemonize', action='store_true',
240 240 help='Daemonize the ipcluster program. This implies --log-to-file')
241 241 paa('--no-daemon',
242 242 dest='Global.daemonize', action='store_false',
243 243 help="Dont't daemonize the ipcluster program.")
244 244
245 245 #-----------------------------------------------------------------------------
246 246 # Main application
247 247 #-----------------------------------------------------------------------------
248 248
249 249
250 250 class IPClusterApp(ApplicationWithClusterDir):
251 251
252 name = u'ipclusterz'
252 name = u'ipcluster'
253 253 description = _description
254 254 usage = None
255 255 command_line_loader = IPClusterAppConfigLoader
256 256 default_config_file_name = default_config_file_name
257 257 default_log_level = logging.INFO
258 258 auto_create_cluster_dir = False
259 259
260 260 def create_default_config(self):
261 261 super(IPClusterApp, self).create_default_config()
262 262 self.default_config.Global.controller_launcher = \
263 263 'IPython.parallel.launcher.LocalControllerLauncher'
264 264 self.default_config.Global.engine_launcher = \
265 265 'IPython.parallel.launcher.LocalEngineSetLauncher'
266 266 self.default_config.Global.n = 2
267 267 self.default_config.Global.delay = 2
268 268 self.default_config.Global.reset_config = False
269 269 self.default_config.Global.clean_logs = True
270 270 self.default_config.Global.signal = signal.SIGINT
271 271 self.default_config.Global.daemonize = False
272 272
273 273 def find_resources(self):
274 274 subcommand = self.command_line_config.Global.subcommand
275 275 if subcommand=='list':
276 276 self.list_cluster_dirs()
277 277 # Exit immediately because there is nothing left to do.
278 278 self.exit()
279 279 elif subcommand=='create':
280 280 self.auto_create_cluster_dir = True
281 281 super(IPClusterApp, self).find_resources()
282 282 elif subcommand=='start' or subcommand=='stop':
283 283 self.auto_create_cluster_dir = True
284 284 try:
285 285 super(IPClusterApp, self).find_resources()
286 286 except ClusterDirError:
287 287 raise ClusterDirError(
288 288 "Could not find a cluster directory. A cluster dir must "
289 "be created before running 'ipclusterz start'. Do "
290 "'ipclusterz create -h' or 'ipclusterz list -h' for more "
289 "be created before running 'ipcluster start'. Do "
290 "'ipcluster create -h' or 'ipcluster list -h' for more "
291 291 "information about creating and listing cluster dirs."
292 292 )
293 293 elif subcommand=='engines':
294 294 self.auto_create_cluster_dir = False
295 295 try:
296 296 super(IPClusterApp, self).find_resources()
297 297 except ClusterDirError:
298 298 raise ClusterDirError(
299 299 "Could not find a cluster directory. A cluster dir must "
300 "be created before running 'ipclusterz start'. Do "
301 "'ipclusterz create -h' or 'ipclusterz list -h' for more "
300 "be created before running 'ipcluster start'. Do "
301 "'ipcluster create -h' or 'ipcluster list -h' for more "
302 302 "information about creating and listing cluster dirs."
303 303 )
304 304
305 305 def list_cluster_dirs(self):
306 306 # Find the search paths
307 307 cluster_dir_paths = os.environ.get('IPCLUSTER_DIR_PATH','')
308 308 if cluster_dir_paths:
309 309 cluster_dir_paths = cluster_dir_paths.split(':')
310 310 else:
311 311 cluster_dir_paths = []
312 312 try:
313 313 ipython_dir = self.command_line_config.Global.ipython_dir
314 314 except AttributeError:
315 315 ipython_dir = self.default_config.Global.ipython_dir
316 316 paths = [os.getcwd(), ipython_dir] + \
317 317 cluster_dir_paths
318 318 paths = list(set(paths))
319 319
320 320 self.log.info('Searching for cluster dirs in paths: %r' % paths)
321 321 for path in paths:
322 322 files = os.listdir(path)
323 323 for f in files:
324 324 full_path = os.path.join(path, f)
325 if os.path.isdir(full_path) and f.startswith('clusterz_'):
325 if os.path.isdir(full_path) and f.startswith('cluster_'):
326 326 profile = full_path.split('_')[-1]
327 start_cmd = 'ipclusterz start -p %s -n 4' % profile
327 start_cmd = 'ipcluster start -p %s -n 4' % profile
328 328 print start_cmd + " ==> " + full_path
329 329
330 330 def pre_construct(self):
331 331 # IPClusterApp.pre_construct() is where we cd to the working directory.
332 332 super(IPClusterApp, self).pre_construct()
333 333 config = self.master_config
334 334 try:
335 335 daemon = config.Global.daemonize
336 336 if daemon:
337 337 config.Global.log_to_file = True
338 338 except AttributeError:
339 339 pass
340 340
341 341 def construct(self):
342 342 config = self.master_config
343 343 subcmd = config.Global.subcommand
344 344 reset = config.Global.reset_config
345 345 if subcmd == 'list':
346 346 return
347 347 if subcmd == 'create':
348 348 self.log.info('Copying default config files to cluster directory '
349 349 '[overwrite=%r]' % (reset,))
350 350 self.cluster_dir_obj.copy_all_config_files(overwrite=reset)
351 351 if subcmd =='start':
352 352 self.cluster_dir_obj.copy_all_config_files(overwrite=False)
353 353 self.start_logging()
354 354 self.loop = ioloop.IOLoop.instance()
355 355 # reactor.callWhenRunning(self.start_launchers)
356 356 dc = ioloop.DelayedCallback(self.start_launchers, 0, self.loop)
357 357 dc.start()
358 358 if subcmd == 'engines':
359 359 self.start_logging()
360 360 self.loop = ioloop.IOLoop.instance()
361 361 # reactor.callWhenRunning(self.start_launchers)
362 362 engine_only = lambda : self.start_launchers(controller=False)
363 363 dc = ioloop.DelayedCallback(engine_only, 0, self.loop)
364 364 dc.start()
365 365
366 366 def start_launchers(self, controller=True):
367 367 config = self.master_config
368 368
369 369 # Create the launchers. In both bases, we set the work_dir of
370 370 # the launcher to the cluster_dir. This is where the launcher's
371 371 # subprocesses will be launched. It is not where the controller
372 372 # and engine will be launched.
373 373 if controller:
374 374 cl_class = import_item(config.Global.controller_launcher)
375 375 self.controller_launcher = cl_class(
376 376 work_dir=self.cluster_dir, config=config,
377 377 logname=self.log.name
378 378 )
379 379 # Setup the observing of stopping. If the controller dies, shut
380 380 # everything down as that will be completely fatal for the engines.
381 381 self.controller_launcher.on_stop(self.stop_launchers)
382 382 # But, we don't monitor the stopping of engines. An engine dying
383 383 # is just fine and in principle a user could start a new engine.
384 384 # Also, if we did monitor engine stopping, it is difficult to
385 385 # know what to do when only some engines die. Currently, the
386 386 # observing of engine stopping is inconsistent. Some launchers
387 387 # might trigger on a single engine stopping, other wait until
388 388 # all stop. TODO: think more about how to handle this.
389 389 else:
390 390 self.controller_launcher = None
391 391
392 392 el_class = import_item(config.Global.engine_launcher)
393 393 self.engine_launcher = el_class(
394 394 work_dir=self.cluster_dir, config=config, logname=self.log.name
395 395 )
396 396
397 397 # Setup signals
398 398 signal.signal(signal.SIGINT, self.sigint_handler)
399 399
400 400 # Start the controller and engines
401 401 self._stopping = False # Make sure stop_launchers is not called 2x.
402 402 if controller:
403 403 self.start_controller()
404 404 dc = ioloop.DelayedCallback(self.start_engines, 1000*config.Global.delay*controller, self.loop)
405 405 dc.start()
406 406 self.startup_message()
407 407
408 408 def startup_message(self, r=None):
409 409 self.log.info("IPython cluster: started")
410 410 return r
411 411
412 412 def start_controller(self, r=None):
413 413 # self.log.info("In start_controller")
414 414 config = self.master_config
415 415 d = self.controller_launcher.start(
416 416 cluster_dir=config.Global.cluster_dir
417 417 )
418 418 return d
419 419
420 420 def start_engines(self, r=None):
421 421 # self.log.info("In start_engines")
422 422 config = self.master_config
423 423
424 424 d = self.engine_launcher.start(
425 425 config.Global.n,
426 426 cluster_dir=config.Global.cluster_dir
427 427 )
428 428 return d
429 429
430 430 def stop_controller(self, r=None):
431 431 # self.log.info("In stop_controller")
432 432 if self.controller_launcher and self.controller_launcher.running:
433 433 return self.controller_launcher.stop()
434 434
435 435 def stop_engines(self, r=None):
436 436 # self.log.info("In stop_engines")
437 437 if self.engine_launcher.running:
438 438 d = self.engine_launcher.stop()
439 439 # d.addErrback(self.log_err)
440 440 return d
441 441 else:
442 442 return None
443 443
444 444 def log_err(self, f):
445 445 self.log.error(f.getTraceback())
446 446 return None
447 447
448 448 def stop_launchers(self, r=None):
449 449 if not self._stopping:
450 450 self._stopping = True
451 451 # if isinstance(r, failure.Failure):
452 452 # self.log.error('Unexpected error in ipcluster:')
453 453 # self.log.info(r.getTraceback())
454 454 self.log.error("IPython cluster: stopping")
455 455 # These return deferreds. We are not doing anything with them
456 456 # but we are holding refs to them as a reminder that they
457 457 # do return deferreds.
458 458 d1 = self.stop_engines()
459 459 d2 = self.stop_controller()
460 460 # Wait a few seconds to let things shut down.
461 461 dc = ioloop.DelayedCallback(self.loop.stop, 4000, self.loop)
462 462 dc.start()
463 463 # reactor.callLater(4.0, reactor.stop)
464 464
465 465 def sigint_handler(self, signum, frame):
466 466 self.stop_launchers()
467 467
468 468 def start_logging(self):
469 469 # Remove old log files of the controller and engine
470 470 if self.master_config.Global.clean_logs:
471 471 log_dir = self.master_config.Global.log_dir
472 472 for f in os.listdir(log_dir):
473 473 if re.match(r'ip(engine|controller)z-\d+\.(log|err|out)',f):
474 474 os.remove(os.path.join(log_dir, f))
475 475 # This will remove old log files for ipcluster itself
476 476 super(IPClusterApp, self).start_logging()
477 477
478 478 def start_app(self):
479 479 """Start the application, depending on what subcommand is used."""
480 480 subcmd = self.master_config.Global.subcommand
481 481 if subcmd=='create' or subcmd=='list':
482 482 return
483 483 elif subcmd=='start':
484 484 self.start_app_start()
485 485 elif subcmd=='stop':
486 486 self.start_app_stop()
487 487 elif subcmd=='engines':
488 488 self.start_app_engines()
489 489
490 490 def start_app_start(self):
491 491 """Start the app for the start subcommand."""
492 492 config = self.master_config
493 493 # First see if the cluster is already running
494 494 try:
495 495 pid = self.get_pid_from_file()
496 496 except PIDFileError:
497 497 pass
498 498 else:
499 499 self.log.critical(
500 500 'Cluster is already running with [pid=%s]. '
501 'use "ipclusterz stop" to stop the cluster.' % pid
501 'use "ipcluster stop" to stop the cluster.' % pid
502 502 )
503 503 # Here I exit with a unusual exit status that other processes
504 504 # can watch for to learn how I existed.
505 505 self.exit(ALREADY_STARTED)
506 506
507 507 # Now log and daemonize
508 508 self.log.info(
509 'Starting ipclusterz with [daemon=%r]' % config.Global.daemonize
509 'Starting ipcluster with [daemon=%r]' % config.Global.daemonize
510 510 )
511 511 # TODO: Get daemonize working on Windows or as a Windows Server.
512 512 if config.Global.daemonize:
513 513 if os.name=='posix':
514 514 from twisted.scripts._twistd_unix import daemonize
515 515 daemonize()
516 516
517 517 # Now write the new pid file AFTER our new forked pid is active.
518 518 self.write_pid_file()
519 519 try:
520 520 self.loop.start()
521 521 except KeyboardInterrupt:
522 522 pass
523 523 except zmq.ZMQError as e:
524 524 if e.errno == errno.EINTR:
525 525 pass
526 526 else:
527 527 raise
528 528 self.remove_pid_file()
529 529
530 530 def start_app_engines(self):
531 531 """Start the app for the start subcommand."""
532 532 config = self.master_config
533 533 # First see if the cluster is already running
534 534
535 535 # Now log and daemonize
536 536 self.log.info(
537 537 'Starting engines with [daemon=%r]' % config.Global.daemonize
538 538 )
539 539 # TODO: Get daemonize working on Windows or as a Windows Server.
540 540 if config.Global.daemonize:
541 541 if os.name=='posix':
542 542 from twisted.scripts._twistd_unix import daemonize
543 543 daemonize()
544 544
545 545 # Now write the new pid file AFTER our new forked pid is active.
546 546 # self.write_pid_file()
547 547 try:
548 548 self.loop.start()
549 549 except KeyboardInterrupt:
550 550 pass
551 551 except zmq.ZMQError as e:
552 552 if e.errno == errno.EINTR:
553 553 pass
554 554 else:
555 555 raise
556 556 # self.remove_pid_file()
557 557
558 558 def start_app_stop(self):
559 559 """Start the app for the stop subcommand."""
560 560 config = self.master_config
561 561 try:
562 562 pid = self.get_pid_from_file()
563 563 except PIDFileError:
564 564 self.log.critical(
565 565 'Problem reading pid file, cluster is probably not running.'
566 566 )
567 567 # Here I exit with a unusual exit status that other processes
568 568 # can watch for to learn how I existed.
569 569 self.exit(ALREADY_STOPPED)
570 570 else:
571 571 if os.name=='posix':
572 572 sig = config.Global.signal
573 573 self.log.info(
574 574 "Stopping cluster [pid=%r] with [signal=%r]" % (pid, sig)
575 575 )
576 576 os.kill(pid, sig)
577 577 elif os.name=='nt':
578 578 # As of right now, we don't support daemonize on Windows, so
579 579 # stop will not do anything. Minimally, it should clean up the
580 580 # old .pid files.
581 581 self.remove_pid_file()
582 582
583 583
584 584 def launch_new_instance():
585 585 """Create and run the IPython cluster."""
586 586 app = IPClusterApp()
587 587 app.start()
588 588
589 589
590 590 if __name__ == '__main__':
591 591 launch_new_instance()
592 592
@@ -1,431 +1,431 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 """
4 4 The IPython controller application.
5 5 """
6 6
7 7 #-----------------------------------------------------------------------------
8 8 # Copyright (C) 2008-2009 The IPython Development Team
9 9 #
10 10 # Distributed under the terms of the BSD License. The full license is in
11 11 # the file COPYING, distributed as part of this software.
12 12 #-----------------------------------------------------------------------------
13 13
14 14 #-----------------------------------------------------------------------------
15 15 # Imports
16 16 #-----------------------------------------------------------------------------
17 17
18 18 from __future__ import with_statement
19 19
20 20 import copy
21 21 import os
22 22 import logging
23 23 import socket
24 24 import stat
25 25 import sys
26 26 import uuid
27 27
28 28 import zmq
29 29 from zmq.log.handlers import PUBHandler
30 30 from zmq.utils import jsonapi as json
31 31
32 32 from IPython.config.loader import Config
33 33 from IPython.parallel import factory
34 34 from IPython.parallel.controller import ControllerFactory
35 35 from IPython.parallel.clusterdir import (
36 36 ApplicationWithClusterDir,
37 37 ClusterDirConfigLoader
38 38 )
39 39 from IPython.parallel.util import disambiguate_ip_address, split_url
40 40 # from IPython.kernel.fcutil import FCServiceFactory, FURLError
41 41 from IPython.utils.traitlets import Instance, Unicode
42 42
43 43
44 44
45 45 #-----------------------------------------------------------------------------
46 46 # Module level variables
47 47 #-----------------------------------------------------------------------------
48 48
49 49
50 50 #: The default config file name for this application
51 default_config_file_name = u'ipcontrollerz_config.py'
51 default_config_file_name = u'ipcontroller_config.py'
52 52
53 53
54 54 _description = """Start the IPython controller for parallel computing.
55 55
56 56 The IPython controller provides a gateway between the IPython engines and
57 57 clients. The controller needs to be started before the engines and can be
58 58 configured using command line options or using a cluster directory. Cluster
59 59 directories contain config, log and security files and are usually located in
60 your ipython directory and named as "clusterz_<profile>". See the --profile
60 your ipython directory and named as "cluster_<profile>". See the --profile
61 61 and --cluster-dir options for details.
62 62 """
63 63
64 64 #-----------------------------------------------------------------------------
65 65 # Default interfaces
66 66 #-----------------------------------------------------------------------------
67 67
68 68 # The default client interfaces for FCClientServiceFactory.interfaces
69 69 default_client_interfaces = Config()
70 70 default_client_interfaces.Default.url_file = 'ipcontroller-client.url'
71 71
72 72 # Make this a dict we can pass to Config.__init__ for the default
73 73 default_client_interfaces = dict(copy.deepcopy(default_client_interfaces.items()))
74 74
75 75
76 76
77 77 # The default engine interfaces for FCEngineServiceFactory.interfaces
78 78 default_engine_interfaces = Config()
79 79 default_engine_interfaces.Default.url_file = u'ipcontroller-engine.url'
80 80
81 81 # Make this a dict we can pass to Config.__init__ for the default
82 82 default_engine_interfaces = dict(copy.deepcopy(default_engine_interfaces.items()))
83 83
84 84
85 85 #-----------------------------------------------------------------------------
86 86 # Service factories
87 87 #-----------------------------------------------------------------------------
88 88
89 89 #
90 90 # class FCClientServiceFactory(FCServiceFactory):
91 91 # """A Foolscap implementation of the client services."""
92 92 #
93 93 # cert_file = Unicode(u'ipcontroller-client.pem', config=True)
94 94 # interfaces = Instance(klass=Config, kw=default_client_interfaces,
95 95 # allow_none=False, config=True)
96 96 #
97 97 #
98 98 # class FCEngineServiceFactory(FCServiceFactory):
99 99 # """A Foolscap implementation of the engine services."""
100 100 #
101 101 # cert_file = Unicode(u'ipcontroller-engine.pem', config=True)
102 102 # interfaces = Instance(klass=dict, kw=default_engine_interfaces,
103 103 # allow_none=False, config=True)
104 104 #
105 105
106 106 #-----------------------------------------------------------------------------
107 107 # Command line options
108 108 #-----------------------------------------------------------------------------
109 109
110 110
111 111 class IPControllerAppConfigLoader(ClusterDirConfigLoader):
112 112
113 113 def _add_arguments(self):
114 114 super(IPControllerAppConfigLoader, self)._add_arguments()
115 115 paa = self.parser.add_argument
116 116
117 117 ## Hub Config:
118 118 paa('--mongodb',
119 119 dest='HubFactory.db_class', action='store_const',
120 120 const='IPython.parallel.mongodb.MongoDB',
121 121 help='Use MongoDB for task storage [default: in-memory]')
122 122 paa('--sqlite',
123 123 dest='HubFactory.db_class', action='store_const',
124 124 const='IPython.parallel.sqlitedb.SQLiteDB',
125 125 help='Use SQLite3 for DB task storage [default: in-memory]')
126 126 paa('--hb',
127 127 type=int, dest='HubFactory.hb', nargs=2,
128 128 help='The (2) ports the Hub\'s Heartmonitor will use for the heartbeat '
129 129 'connections [default: random]',
130 130 metavar='Hub.hb_ports')
131 131 paa('--ping',
132 132 type=int, dest='HubFactory.ping',
133 133 help='The frequency at which the Hub pings the engines for heartbeats '
134 134 ' (in ms) [default: 100]',
135 135 metavar='Hub.ping')
136 136
137 137 # Client config
138 138 paa('--client-ip',
139 139 type=str, dest='HubFactory.client_ip',
140 140 help='The IP address or hostname the Hub will listen on for '
141 141 'client connections. Both engine-ip and client-ip can be set simultaneously '
142 142 'via --ip [default: loopback]',
143 143 metavar='Hub.client_ip')
144 144 paa('--client-transport',
145 145 type=str, dest='HubFactory.client_transport',
146 146 help='The ZeroMQ transport the Hub will use for '
147 147 'client connections. Both engine-transport and client-transport can be set simultaneously '
148 148 'via --transport [default: tcp]',
149 149 metavar='Hub.client_transport')
150 150 paa('--query',
151 151 type=int, dest='HubFactory.query_port',
152 152 help='The port on which the Hub XREP socket will listen for result queries from clients [default: random]',
153 153 metavar='Hub.query_port')
154 154 paa('--notifier',
155 155 type=int, dest='HubFactory.notifier_port',
156 156 help='The port on which the Hub PUB socket will listen for notification connections [default: random]',
157 157 metavar='Hub.notifier_port')
158 158
159 159 # Engine config
160 160 paa('--engine-ip',
161 161 type=str, dest='HubFactory.engine_ip',
162 162 help='The IP address or hostname the Hub will listen on for '
163 163 'engine connections. This applies to the Hub and its schedulers'
164 164 'engine-ip and client-ip can be set simultaneously '
165 165 'via --ip [default: loopback]',
166 166 metavar='Hub.engine_ip')
167 167 paa('--engine-transport',
168 168 type=str, dest='HubFactory.engine_transport',
169 169 help='The ZeroMQ transport the Hub will use for '
170 170 'client connections. Both engine-transport and client-transport can be set simultaneously '
171 171 'via --transport [default: tcp]',
172 172 metavar='Hub.engine_transport')
173 173
174 174 # Scheduler config
175 175 paa('--mux',
176 176 type=int, dest='ControllerFactory.mux', nargs=2,
177 177 help='The (2) ports the MUX scheduler will listen on for client,engine '
178 178 'connections, respectively [default: random]',
179 179 metavar='Scheduler.mux_ports')
180 180 paa('--task',
181 181 type=int, dest='ControllerFactory.task', nargs=2,
182 182 help='The (2) ports the Task scheduler will listen on for client,engine '
183 183 'connections, respectively [default: random]',
184 184 metavar='Scheduler.task_ports')
185 185 paa('--control',
186 186 type=int, dest='ControllerFactory.control', nargs=2,
187 187 help='The (2) ports the Control scheduler will listen on for client,engine '
188 188 'connections, respectively [default: random]',
189 189 metavar='Scheduler.control_ports')
190 190 paa('--iopub',
191 191 type=int, dest='ControllerFactory.iopub', nargs=2,
192 192 help='The (2) ports the IOPub scheduler will listen on for client,engine '
193 193 'connections, respectively [default: random]',
194 194 metavar='Scheduler.iopub_ports')
195 195
196 196 paa('--scheme',
197 197 type=str, dest='HubFactory.scheme',
198 198 choices = ['pure', 'lru', 'plainrandom', 'weighted', 'twobin','leastload'],
199 199 help='select the task scheduler scheme [default: Python LRU]',
200 200 metavar='Scheduler.scheme')
201 201 paa('--usethreads',
202 202 dest='ControllerFactory.usethreads', action="store_true",
203 203 help='Use threads instead of processes for the schedulers',
204 204 )
205 205 paa('--hwm',
206 206 dest='ControllerFactory.hwm', type=int,
207 207 help='specify the High Water Mark (HWM) for the downstream '
208 208 'socket in the pure ZMQ scheduler. This is the maximum number '
209 209 'of allowed outstanding tasks on each engine.',
210 210 )
211 211
212 212 ## Global config
213 213 paa('--log-to-file',
214 214 action='store_true', dest='Global.log_to_file',
215 215 help='Log to a file in the log directory (default is stdout)')
216 216 paa('--log-url',
217 217 type=str, dest='Global.log_url',
218 218 help='Broadcast logs to an iploggerz process [default: disabled]')
219 219 paa('-r','--reuse-files',
220 220 action='store_true', dest='Global.reuse_files',
221 221 help='Try to reuse existing json connection files.')
222 222 paa('--no-secure',
223 223 action='store_false', dest='Global.secure',
224 224 help='Turn off execution keys (default).')
225 225 paa('--secure',
226 226 action='store_true', dest='Global.secure',
227 227 help='Turn on execution keys.')
228 228 paa('--execkey',
229 229 type=str, dest='Global.exec_key',
230 230 help='path to a file containing an execution key.',
231 231 metavar='keyfile')
232 232 paa('--ssh',
233 233 type=str, dest='Global.sshserver',
234 234 help='ssh url for clients to use when connecting to the Controller '
235 235 'processes. It should be of the form: [user@]server[:port]. The '
236 236 'Controller\'s listening addresses must be accessible from the ssh server',
237 237 metavar='Global.sshserver')
238 238 paa('--location',
239 239 type=str, dest='Global.location',
240 240 help="The external IP or domain name of this machine, used for disambiguating "
241 241 "engine and client connections.",
242 242 metavar='Global.location')
243 243 factory.add_session_arguments(self.parser)
244 244 factory.add_registration_arguments(self.parser)
245 245
246 246
247 247 #-----------------------------------------------------------------------------
248 248 # The main application
249 249 #-----------------------------------------------------------------------------
250 250
251 251
252 252 class IPControllerApp(ApplicationWithClusterDir):
253 253
254 name = u'ipcontrollerz'
254 name = u'ipcontroller'
255 255 description = _description
256 256 command_line_loader = IPControllerAppConfigLoader
257 257 default_config_file_name = default_config_file_name
258 258 auto_create_cluster_dir = True
259 259
260 260
261 261 def create_default_config(self):
262 262 super(IPControllerApp, self).create_default_config()
263 263 # Don't set defaults for Global.secure or Global.reuse_furls
264 264 # as those are set in a component.
265 265 self.default_config.Global.import_statements = []
266 266 self.default_config.Global.clean_logs = True
267 267 self.default_config.Global.secure = True
268 268 self.default_config.Global.reuse_files = False
269 269 self.default_config.Global.exec_key = "exec_key.key"
270 270 self.default_config.Global.sshserver = None
271 271 self.default_config.Global.location = None
272 272
273 273 def pre_construct(self):
274 274 super(IPControllerApp, self).pre_construct()
275 275 c = self.master_config
276 276 # The defaults for these are set in FCClientServiceFactory and
277 277 # FCEngineServiceFactory, so we only set them here if the global
278 278 # options have be set to override the class level defaults.
279 279
280 280 # if hasattr(c.Global, 'reuse_furls'):
281 281 # c.FCClientServiceFactory.reuse_furls = c.Global.reuse_furls
282 282 # c.FCEngineServiceFactory.reuse_furls = c.Global.reuse_furls
283 283 # del c.Global.reuse_furls
284 284 # if hasattr(c.Global, 'secure'):
285 285 # c.FCClientServiceFactory.secure = c.Global.secure
286 286 # c.FCEngineServiceFactory.secure = c.Global.secure
287 287 # del c.Global.secure
288 288
289 289 def save_connection_dict(self, fname, cdict):
290 290 """save a connection dict to json file."""
291 291 c = self.master_config
292 292 url = cdict['url']
293 293 location = cdict['location']
294 294 if not location:
295 295 try:
296 296 proto,ip,port = split_url(url)
297 297 except AssertionError:
298 298 pass
299 299 else:
300 300 location = socket.gethostbyname_ex(socket.gethostname())[2][-1]
301 301 cdict['location'] = location
302 302 fname = os.path.join(c.Global.security_dir, fname)
303 303 with open(fname, 'w') as f:
304 304 f.write(json.dumps(cdict, indent=2))
305 305 os.chmod(fname, stat.S_IRUSR|stat.S_IWUSR)
306 306
307 307 def load_config_from_json(self):
308 308 """load config from existing json connector files."""
309 309 c = self.master_config
310 310 # load from engine config
311 311 with open(os.path.join(c.Global.security_dir, 'ipcontroller-engine.json')) as f:
312 312 cfg = json.loads(f.read())
313 313 key = c.SessionFactory.exec_key = cfg['exec_key']
314 314 xport,addr = cfg['url'].split('://')
315 315 c.HubFactory.engine_transport = xport
316 316 ip,ports = addr.split(':')
317 317 c.HubFactory.engine_ip = ip
318 318 c.HubFactory.regport = int(ports)
319 319 c.Global.location = cfg['location']
320 320
321 321 # load client config
322 322 with open(os.path.join(c.Global.security_dir, 'ipcontroller-client.json')) as f:
323 323 cfg = json.loads(f.read())
324 324 assert key == cfg['exec_key'], "exec_key mismatch between engine and client keys"
325 325 xport,addr = cfg['url'].split('://')
326 326 c.HubFactory.client_transport = xport
327 327 ip,ports = addr.split(':')
328 328 c.HubFactory.client_ip = ip
329 329 c.Global.sshserver = cfg['ssh']
330 330 assert int(ports) == c.HubFactory.regport, "regport mismatch"
331 331
332 332 def construct(self):
333 333 # This is the working dir by now.
334 334 sys.path.insert(0, '')
335 335 c = self.master_config
336 336
337 337 self.import_statements()
338 338 reusing = c.Global.reuse_files
339 339 if reusing:
340 340 try:
341 341 self.load_config_from_json()
342 342 except (AssertionError,IOError):
343 343 reusing=False
344 344 # check again, because reusing may have failed:
345 345 if reusing:
346 346 pass
347 347 elif c.Global.secure:
348 348 keyfile = os.path.join(c.Global.security_dir, c.Global.exec_key)
349 349 key = str(uuid.uuid4())
350 350 with open(keyfile, 'w') as f:
351 351 f.write(key)
352 352 os.chmod(keyfile, stat.S_IRUSR|stat.S_IWUSR)
353 353 c.SessionFactory.exec_key = key
354 354 else:
355 355 c.SessionFactory.exec_key = ''
356 356 key = None
357 357
358 358 try:
359 359 self.factory = ControllerFactory(config=c, logname=self.log.name)
360 360 self.start_logging()
361 361 self.factory.construct()
362 362 except:
363 363 self.log.error("Couldn't construct the Controller", exc_info=True)
364 364 self.exit(1)
365 365
366 366 if not reusing:
367 367 # save to new json config files
368 368 f = self.factory
369 369 cdict = {'exec_key' : key,
370 370 'ssh' : c.Global.sshserver,
371 371 'url' : "%s://%s:%s"%(f.client_transport, f.client_ip, f.regport),
372 372 'location' : c.Global.location
373 373 }
374 374 self.save_connection_dict('ipcontroller-client.json', cdict)
375 375 edict = cdict
376 376 edict['url']="%s://%s:%s"%((f.client_transport, f.client_ip, f.regport))
377 377 self.save_connection_dict('ipcontroller-engine.json', edict)
378 378
379 379
380 380 def save_urls(self):
381 381 """save the registration urls to files."""
382 382 c = self.master_config
383 383
384 384 sec_dir = c.Global.security_dir
385 385 cf = self.factory
386 386
387 387 with open(os.path.join(sec_dir, 'ipcontroller-engine.url'), 'w') as f:
388 388 f.write("%s://%s:%s"%(cf.engine_transport, cf.engine_ip, cf.regport))
389 389
390 390 with open(os.path.join(sec_dir, 'ipcontroller-client.url'), 'w') as f:
391 391 f.write("%s://%s:%s"%(cf.client_transport, cf.client_ip, cf.regport))
392 392
393 393
394 394 def import_statements(self):
395 395 statements = self.master_config.Global.import_statements
396 396 for s in statements:
397 397 try:
398 398 self.log.msg("Executing statement: '%s'" % s)
399 399 exec s in globals(), locals()
400 400 except:
401 401 self.log.msg("Error running statement: %s" % s)
402 402
403 403 def start_logging(self):
404 404 super(IPControllerApp, self).start_logging()
405 405 if self.master_config.Global.log_url:
406 406 context = self.factory.context
407 407 lsock = context.socket(zmq.PUB)
408 408 lsock.connect(self.master_config.Global.log_url)
409 409 handler = PUBHandler(lsock)
410 410 handler.root_topic = 'controller'
411 411 handler.setLevel(self.log_level)
412 412 self.log.addHandler(handler)
413 413 #
414 414 def start_app(self):
415 415 # Start the subprocesses:
416 416 self.factory.start()
417 417 self.write_pid_file(overwrite=True)
418 418 try:
419 419 self.factory.loop.start()
420 420 except KeyboardInterrupt:
421 421 self.log.critical("Interrupted, Exiting...\n")
422 422
423 423
424 424 def launch_new_instance():
425 425 """Create and run the IPython controller"""
426 426 app = IPControllerApp()
427 427 app.start()
428 428
429 429
430 430 if __name__ == '__main__':
431 431 launch_new_instance()
@@ -1,294 +1,294 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 """
4 4 The IPython engine application
5 5 """
6 6
7 7 #-----------------------------------------------------------------------------
8 8 # Copyright (C) 2008-2009 The IPython Development Team
9 9 #
10 10 # Distributed under the terms of the BSD License. The full license is in
11 11 # the file COPYING, distributed as part of this software.
12 12 #-----------------------------------------------------------------------------
13 13
14 14 #-----------------------------------------------------------------------------
15 15 # Imports
16 16 #-----------------------------------------------------------------------------
17 17
18 18 import json
19 19 import os
20 20 import sys
21 21
22 22 import zmq
23 23 from zmq.eventloop import ioloop
24 24
25 25 from IPython.parallel.clusterdir import (
26 26 ApplicationWithClusterDir,
27 27 ClusterDirConfigLoader
28 28 )
29 29 from IPython.zmq.log import EnginePUBHandler
30 30
31 31 from IPython.parallel import factory
32 32 from IPython.parallel.engine import EngineFactory
33 33 from IPython.parallel.streamkernel import Kernel
34 34 from IPython.parallel.util import disambiguate_url
35 35 from IPython.utils.importstring import import_item
36 36
37 37
38 38 #-----------------------------------------------------------------------------
39 39 # Module level variables
40 40 #-----------------------------------------------------------------------------
41 41
42 42 #: The default config file name for this application
43 default_config_file_name = u'ipenginez_config.py'
43 default_config_file_name = u'ipengine_config.py'
44 44
45 45
46 46 mpi4py_init = """from mpi4py import MPI as mpi
47 47 mpi.size = mpi.COMM_WORLD.Get_size()
48 48 mpi.rank = mpi.COMM_WORLD.Get_rank()
49 49 """
50 50
51 51
52 52 pytrilinos_init = """from PyTrilinos import Epetra
53 53 class SimpleStruct:
54 54 pass
55 55 mpi = SimpleStruct()
56 56 mpi.rank = 0
57 57 mpi.size = 0
58 58 """
59 59
60 60
61 61 _description = """Start an IPython engine for parallel computing.\n\n
62 62
63 63 IPython engines run in parallel and perform computations on behalf of a client
64 64 and controller. A controller needs to be started before the engines. The
65 65 engine can be configured using command line options or using a cluster
66 66 directory. Cluster directories contain config, log and security files and are
67 usually located in your ipython directory and named as "clusterz_<profile>".
67 usually located in your ipython directory and named as "cluster_<profile>".
68 68 See the --profile and --cluster-dir options for details.
69 69 """
70 70
71 71 #-----------------------------------------------------------------------------
72 72 # Command line options
73 73 #-----------------------------------------------------------------------------
74 74
75 75
76 76 class IPEngineAppConfigLoader(ClusterDirConfigLoader):
77 77
78 78 def _add_arguments(self):
79 79 super(IPEngineAppConfigLoader, self)._add_arguments()
80 80 paa = self.parser.add_argument
81 81 # Controller config
82 82 paa('--file', '-f',
83 83 type=unicode, dest='Global.url_file',
84 84 help='The full location of the file containing the connection information fo '
85 85 'controller. If this is not given, the file must be in the '
86 86 'security directory of the cluster directory. This location is '
87 87 'resolved using the --profile and --app-dir options.',
88 88 metavar='Global.url_file')
89 89 # MPI
90 90 paa('--mpi',
91 91 type=str, dest='MPI.use',
92 92 help='How to enable MPI (mpi4py, pytrilinos, or empty string to disable).',
93 93 metavar='MPI.use')
94 94 # Global config
95 95 paa('--log-to-file',
96 96 action='store_true', dest='Global.log_to_file',
97 97 help='Log to a file in the log directory (default is stdout)')
98 98 paa('--log-url',
99 99 dest='Global.log_url',
100 100 help="url of ZMQ logger, as started with iploggerz")
101 101 # paa('--execkey',
102 102 # type=str, dest='Global.exec_key',
103 103 # help='path to a file containing an execution key.',
104 104 # metavar='keyfile')
105 105 # paa('--no-secure',
106 106 # action='store_false', dest='Global.secure',
107 107 # help='Turn off execution keys.')
108 108 # paa('--secure',
109 109 # action='store_true', dest='Global.secure',
110 110 # help='Turn on execution keys (default).')
111 111 # init command
112 112 paa('-c',
113 113 type=str, dest='Global.extra_exec_lines',
114 114 help='specify a command to be run at startup')
115 115
116 116 factory.add_session_arguments(self.parser)
117 117 factory.add_registration_arguments(self.parser)
118 118
119 119
120 120 #-----------------------------------------------------------------------------
121 121 # Main application
122 122 #-----------------------------------------------------------------------------
123 123
124 124
125 125 class IPEngineApp(ApplicationWithClusterDir):
126 126
127 name = u'ipenginez'
127 name = u'ipengine'
128 128 description = _description
129 129 command_line_loader = IPEngineAppConfigLoader
130 130 default_config_file_name = default_config_file_name
131 131 auto_create_cluster_dir = True
132 132
133 133 def create_default_config(self):
134 134 super(IPEngineApp, self).create_default_config()
135 135
136 136 # The engine should not clean logs as we don't want to remove the
137 137 # active log files of other running engines.
138 138 self.default_config.Global.clean_logs = False
139 139 self.default_config.Global.secure = True
140 140
141 141 # Global config attributes
142 142 self.default_config.Global.exec_lines = []
143 143 self.default_config.Global.extra_exec_lines = ''
144 144
145 145 # Configuration related to the controller
146 146 # This must match the filename (path not included) that the controller
147 147 # used for the FURL file.
148 148 self.default_config.Global.url_file = u''
149 149 self.default_config.Global.url_file_name = u'ipcontroller-engine.json'
150 150 # If given, this is the actual location of the controller's FURL file.
151 151 # If not, this is computed using the profile, app_dir and furl_file_name
152 152 # self.default_config.Global.key_file_name = u'exec_key.key'
153 153 # self.default_config.Global.key_file = u''
154 154
155 155 # MPI related config attributes
156 156 self.default_config.MPI.use = ''
157 157 self.default_config.MPI.mpi4py = mpi4py_init
158 158 self.default_config.MPI.pytrilinos = pytrilinos_init
159 159
160 160 def post_load_command_line_config(self):
161 161 pass
162 162
163 163 def pre_construct(self):
164 164 super(IPEngineApp, self).pre_construct()
165 165 # self.find_cont_url_file()
166 166 self.find_url_file()
167 167 if self.master_config.Global.extra_exec_lines:
168 168 self.master_config.Global.exec_lines.append(self.master_config.Global.extra_exec_lines)
169 169
170 170 # def find_key_file(self):
171 171 # """Set the key file.
172 172 #
173 173 # Here we don't try to actually see if it exists for is valid as that
174 174 # is hadled by the connection logic.
175 175 # """
176 176 # config = self.master_config
177 177 # # Find the actual controller key file
178 178 # if not config.Global.key_file:
179 179 # try_this = os.path.join(
180 180 # config.Global.cluster_dir,
181 181 # config.Global.security_dir,
182 182 # config.Global.key_file_name
183 183 # )
184 184 # config.Global.key_file = try_this
185 185
186 186 def find_url_file(self):
187 187 """Set the key file.
188 188
189 189 Here we don't try to actually see if it exists for is valid as that
190 190 is hadled by the connection logic.
191 191 """
192 192 config = self.master_config
193 193 # Find the actual controller key file
194 194 if not config.Global.url_file:
195 195 try_this = os.path.join(
196 196 config.Global.cluster_dir,
197 197 config.Global.security_dir,
198 198 config.Global.url_file_name
199 199 )
200 200 config.Global.url_file = try_this
201 201
202 202 def construct(self):
203 203 # This is the working dir by now.
204 204 sys.path.insert(0, '')
205 205 config = self.master_config
206 206 # if os.path.exists(config.Global.key_file) and config.Global.secure:
207 207 # config.SessionFactory.exec_key = config.Global.key_file
208 208 if os.path.exists(config.Global.url_file):
209 209 with open(config.Global.url_file) as f:
210 210 d = json.loads(f.read())
211 211 for k,v in d.iteritems():
212 212 if isinstance(v, unicode):
213 213 d[k] = v.encode()
214 214 if d['exec_key']:
215 215 config.SessionFactory.exec_key = d['exec_key']
216 216 d['url'] = disambiguate_url(d['url'], d['location'])
217 217 config.RegistrationFactory.url=d['url']
218 218 config.EngineFactory.location = d['location']
219 219
220 220
221 221
222 222 config.Kernel.exec_lines = config.Global.exec_lines
223 223
224 224 self.start_mpi()
225 225
226 226 # Create the underlying shell class and EngineService
227 227 # shell_class = import_item(self.master_config.Global.shell_class)
228 228 try:
229 229 self.engine = EngineFactory(config=config, logname=self.log.name)
230 230 except:
231 231 self.log.error("Couldn't start the Engine", exc_info=True)
232 232 self.exit(1)
233 233
234 234 self.start_logging()
235 235
236 236 # Create the service hierarchy
237 237 # self.main_service = service.MultiService()
238 238 # self.engine_service.setServiceParent(self.main_service)
239 239 # self.tub_service = Tub()
240 240 # self.tub_service.setServiceParent(self.main_service)
241 241 # # This needs to be called before the connection is initiated
242 242 # self.main_service.startService()
243 243
244 244 # This initiates the connection to the controller and calls
245 245 # register_engine to tell the controller we are ready to do work
246 246 # self.engine_connector = EngineConnector(self.tub_service)
247 247
248 248 # self.log.info("Using furl file: %s" % self.master_config.Global.furl_file)
249 249
250 250 # reactor.callWhenRunning(self.call_connect)
251 251
252 252
253 253 def start_logging(self):
254 254 super(IPEngineApp, self).start_logging()
255 255 if self.master_config.Global.log_url:
256 256 context = self.engine.context
257 257 lsock = context.socket(zmq.PUB)
258 258 lsock.connect(self.master_config.Global.log_url)
259 259 handler = EnginePUBHandler(self.engine, lsock)
260 260 handler.setLevel(self.log_level)
261 261 self.log.addHandler(handler)
262 262
263 263 def start_mpi(self):
264 264 global mpi
265 265 mpikey = self.master_config.MPI.use
266 266 mpi_import_statement = self.master_config.MPI.get(mpikey, None)
267 267 if mpi_import_statement is not None:
268 268 try:
269 269 self.log.info("Initializing MPI:")
270 270 self.log.info(mpi_import_statement)
271 271 exec mpi_import_statement in globals()
272 272 except:
273 273 mpi = None
274 274 else:
275 275 mpi = None
276 276
277 277
278 278 def start_app(self):
279 279 self.engine.start()
280 280 try:
281 281 self.engine.loop.start()
282 282 except KeyboardInterrupt:
283 283 self.log.critical("Engine Interrupted, shutting down...\n")
284 284
285 285
286 286 def launch_new_instance():
287 287 """Create and run the IPython controller"""
288 288 app = IPEngineApp()
289 289 app.start()
290 290
291 291
292 292 if __name__ == '__main__':
293 293 launch_new_instance()
294 294
@@ -1,132 +1,132 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 """
4 4 A simple IPython logger application
5 5 """
6 6
7 7 #-----------------------------------------------------------------------------
8 8 # Copyright (C) 2011 The IPython Development Team
9 9 #
10 10 # Distributed under the terms of the BSD License. The full license is in
11 11 # the file COPYING, distributed as part of this software.
12 12 #-----------------------------------------------------------------------------
13 13
14 14 #-----------------------------------------------------------------------------
15 15 # Imports
16 16 #-----------------------------------------------------------------------------
17 17
18 18 import os
19 19 import sys
20 20
21 21 import zmq
22 22
23 23 from IPython.parallel.clusterdir import (
24 24 ApplicationWithClusterDir,
25 25 ClusterDirConfigLoader
26 26 )
27 27 from .logwatcher import LogWatcher
28 28
29 29 #-----------------------------------------------------------------------------
30 30 # Module level variables
31 31 #-----------------------------------------------------------------------------
32 32
33 33 #: The default config file name for this application
34 34 default_config_file_name = u'iplogger_config.py'
35 35
36 36 _description = """Start an IPython logger for parallel computing.\n\n
37 37
38 38 IPython controllers and engines (and your own processes) can broadcast log messages
39 39 by registering a `zmq.log.handlers.PUBHandler` with the `logging` module. The
40 40 logger can be configured using command line options or using a cluster
41 41 directory. Cluster directories contain config, log and security files and are
42 usually located in your ipython directory and named as "clusterz_<profile>".
42 usually located in your ipython directory and named as "cluster_<profile>".
43 43 See the --profile and --cluster-dir options for details.
44 44 """
45 45
46 46 #-----------------------------------------------------------------------------
47 47 # Command line options
48 48 #-----------------------------------------------------------------------------
49 49
50 50
51 51 class IPLoggerAppConfigLoader(ClusterDirConfigLoader):
52 52
53 53 def _add_arguments(self):
54 54 super(IPLoggerAppConfigLoader, self)._add_arguments()
55 55 paa = self.parser.add_argument
56 56 # Controller config
57 57 paa('--url',
58 58 type=str, dest='LogWatcher.url',
59 59 help='The url the LogWatcher will listen on',
60 60 )
61 61 # MPI
62 62 paa('--topics',
63 63 type=str, dest='LogWatcher.topics', nargs='+',
64 64 help='What topics to subscribe to',
65 65 metavar='topics')
66 66 # Global config
67 67 paa('--log-to-file',
68 68 action='store_true', dest='Global.log_to_file',
69 69 help='Log to a file in the log directory (default is stdout)')
70 70
71 71
72 72 #-----------------------------------------------------------------------------
73 73 # Main application
74 74 #-----------------------------------------------------------------------------
75 75
76 76
77 77 class IPLoggerApp(ApplicationWithClusterDir):
78 78
79 79 name = u'iploggerz'
80 80 description = _description
81 81 command_line_loader = IPLoggerAppConfigLoader
82 82 default_config_file_name = default_config_file_name
83 83 auto_create_cluster_dir = True
84 84
85 85 def create_default_config(self):
86 86 super(IPLoggerApp, self).create_default_config()
87 87
88 88 # The engine should not clean logs as we don't want to remove the
89 89 # active log files of other running engines.
90 90 self.default_config.Global.clean_logs = False
91 91
92 92 # If given, this is the actual location of the logger's URL file.
93 93 # If not, this is computed using the profile, app_dir and furl_file_name
94 94 self.default_config.Global.url_file_name = u'iplogger.url'
95 95 self.default_config.Global.url_file = u''
96 96
97 97 def post_load_command_line_config(self):
98 98 pass
99 99
100 100 def pre_construct(self):
101 101 super(IPLoggerApp, self).pre_construct()
102 102
103 103 def construct(self):
104 104 # This is the working dir by now.
105 105 sys.path.insert(0, '')
106 106
107 107 self.start_logging()
108 108
109 109 try:
110 110 self.watcher = LogWatcher(config=self.master_config, logname=self.log.name)
111 111 except:
112 112 self.log.error("Couldn't start the LogWatcher", exc_info=True)
113 113 self.exit(1)
114 114
115 115
116 116 def start_app(self):
117 117 try:
118 118 self.watcher.start()
119 119 self.watcher.loop.start()
120 120 except KeyboardInterrupt:
121 121 self.log.critical("Logging Interrupted, shutting down...\n")
122 122
123 123
124 124 def launch_new_instance():
125 125 """Create and run the IPython LogWatcher"""
126 126 app = IPLoggerApp()
127 127 app.start()
128 128
129 129
130 130 if __name__ == '__main__':
131 131 launch_new_instance()
132 132
@@ -1,971 +1,971 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 """
4 4 Facilities for launching IPython processes asynchronously.
5 5 """
6 6
7 7 #-----------------------------------------------------------------------------
8 8 # Copyright (C) 2008-2009 The IPython Development Team
9 9 #
10 10 # Distributed under the terms of the BSD License. The full license is in
11 11 # the file COPYING, distributed as part of this software.
12 12 #-----------------------------------------------------------------------------
13 13
14 14 #-----------------------------------------------------------------------------
15 15 # Imports
16 16 #-----------------------------------------------------------------------------
17 17
18 18 import copy
19 19 import logging
20 20 import os
21 21 import re
22 22 import stat
23 23
24 24 from signal import SIGINT, SIGTERM
25 25 try:
26 26 from signal import SIGKILL
27 27 except ImportError:
28 28 SIGKILL=SIGTERM
29 29
30 30 from subprocess import Popen, PIPE, STDOUT
31 31 try:
32 32 from subprocess import check_output
33 33 except ImportError:
34 34 # pre-2.7, define check_output with Popen
35 35 def check_output(*args, **kwargs):
36 36 kwargs.update(dict(stdout=PIPE))
37 37 p = Popen(*args, **kwargs)
38 38 out,err = p.communicate()
39 39 return out
40 40
41 41 from zmq.eventloop import ioloop
42 42
43 43 from IPython.external import Itpl
44 44 # from IPython.config.configurable import Configurable
45 45 from IPython.utils.traitlets import Any, Str, Int, List, Unicode, Dict, Instance, CUnicode
46 46 from IPython.utils.path import get_ipython_module_path
47 47 from IPython.utils.process import find_cmd, pycmd2argv, FindCmdError
48 48
49 49 from .factory import LoggingFactory
50 50
51 51 # load winhpcjob only on Windows
52 52 try:
53 53 from .winhpcjob import (
54 54 IPControllerTask, IPEngineTask,
55 55 IPControllerJob, IPEngineSetJob
56 56 )
57 57 except ImportError:
58 58 pass
59 59
60 60
61 61 #-----------------------------------------------------------------------------
62 62 # Paths to the kernel apps
63 63 #-----------------------------------------------------------------------------
64 64
65 65
66 ipclusterz_cmd_argv = pycmd2argv(get_ipython_module_path(
66 ipcluster_cmd_argv = pycmd2argv(get_ipython_module_path(
67 67 'IPython.parallel.ipclusterapp'
68 68 ))
69 69
70 ipenginez_cmd_argv = pycmd2argv(get_ipython_module_path(
70 ipengine_cmd_argv = pycmd2argv(get_ipython_module_path(
71 71 'IPython.parallel.ipengineapp'
72 72 ))
73 73
74 ipcontrollerz_cmd_argv = pycmd2argv(get_ipython_module_path(
74 ipcontroller_cmd_argv = pycmd2argv(get_ipython_module_path(
75 75 'IPython.parallel.ipcontrollerapp'
76 76 ))
77 77
78 78 #-----------------------------------------------------------------------------
79 79 # Base launchers and errors
80 80 #-----------------------------------------------------------------------------
81 81
82 82
83 83 class LauncherError(Exception):
84 84 pass
85 85
86 86
87 87 class ProcessStateError(LauncherError):
88 88 pass
89 89
90 90
91 91 class UnknownStatus(LauncherError):
92 92 pass
93 93
94 94
95 95 class BaseLauncher(LoggingFactory):
96 96 """An asbtraction for starting, stopping and signaling a process."""
97 97
98 98 # In all of the launchers, the work_dir is where child processes will be
99 99 # run. This will usually be the cluster_dir, but may not be. any work_dir
100 100 # passed into the __init__ method will override the config value.
101 101 # This should not be used to set the work_dir for the actual engine
102 102 # and controller. Instead, use their own config files or the
103 103 # controller_args, engine_args attributes of the launchers to add
104 104 # the --work-dir option.
105 105 work_dir = Unicode(u'.')
106 106 loop = Instance('zmq.eventloop.ioloop.IOLoop')
107 107
108 108 start_data = Any()
109 109 stop_data = Any()
110 110
111 111 def _loop_default(self):
112 112 return ioloop.IOLoop.instance()
113 113
114 114 def __init__(self, work_dir=u'.', config=None, **kwargs):
115 115 super(BaseLauncher, self).__init__(work_dir=work_dir, config=config, **kwargs)
116 116 self.state = 'before' # can be before, running, after
117 117 self.stop_callbacks = []
118 118 self.start_data = None
119 119 self.stop_data = None
120 120
121 121 @property
122 122 def args(self):
123 123 """A list of cmd and args that will be used to start the process.
124 124
125 125 This is what is passed to :func:`spawnProcess` and the first element
126 126 will be the process name.
127 127 """
128 128 return self.find_args()
129 129
130 130 def find_args(self):
131 131 """The ``.args`` property calls this to find the args list.
132 132
133 133 Subcommand should implement this to construct the cmd and args.
134 134 """
135 135 raise NotImplementedError('find_args must be implemented in a subclass')
136 136
137 137 @property
138 138 def arg_str(self):
139 139 """The string form of the program arguments."""
140 140 return ' '.join(self.args)
141 141
142 142 @property
143 143 def running(self):
144 144 """Am I running."""
145 145 if self.state == 'running':
146 146 return True
147 147 else:
148 148 return False
149 149
150 150 def start(self):
151 151 """Start the process.
152 152
153 153 This must return a deferred that fires with information about the
154 154 process starting (like a pid, job id, etc.).
155 155 """
156 156 raise NotImplementedError('start must be implemented in a subclass')
157 157
158 158 def stop(self):
159 159 """Stop the process and notify observers of stopping.
160 160
161 161 This must return a deferred that fires with information about the
162 162 processing stopping, like errors that occur while the process is
163 163 attempting to be shut down. This deferred won't fire when the process
164 164 actually stops. To observe the actual process stopping, see
165 165 :func:`observe_stop`.
166 166 """
167 167 raise NotImplementedError('stop must be implemented in a subclass')
168 168
169 169 def on_stop(self, f):
170 170 """Get a deferred that will fire when the process stops.
171 171
172 172 The deferred will fire with data that contains information about
173 173 the exit status of the process.
174 174 """
175 175 if self.state=='after':
176 176 return f(self.stop_data)
177 177 else:
178 178 self.stop_callbacks.append(f)
179 179
180 180 def notify_start(self, data):
181 181 """Call this to trigger startup actions.
182 182
183 183 This logs the process startup and sets the state to 'running'. It is
184 184 a pass-through so it can be used as a callback.
185 185 """
186 186
187 187 self.log.info('Process %r started: %r' % (self.args[0], data))
188 188 self.start_data = data
189 189 self.state = 'running'
190 190 return data
191 191
192 192 def notify_stop(self, data):
193 193 """Call this to trigger process stop actions.
194 194
195 195 This logs the process stopping and sets the state to 'after'. Call
196 196 this to trigger all the deferreds from :func:`observe_stop`."""
197 197
198 198 self.log.info('Process %r stopped: %r' % (self.args[0], data))
199 199 self.stop_data = data
200 200 self.state = 'after'
201 201 for i in range(len(self.stop_callbacks)):
202 202 d = self.stop_callbacks.pop()
203 203 d(data)
204 204 return data
205 205
206 206 def signal(self, sig):
207 207 """Signal the process.
208 208
209 209 Return a semi-meaningless deferred after signaling the process.
210 210
211 211 Parameters
212 212 ----------
213 213 sig : str or int
214 214 'KILL', 'INT', etc., or any signal number
215 215 """
216 216 raise NotImplementedError('signal must be implemented in a subclass')
217 217
218 218
219 219 #-----------------------------------------------------------------------------
220 220 # Local process launchers
221 221 #-----------------------------------------------------------------------------
222 222
223 223
224 224 class LocalProcessLauncher(BaseLauncher):
225 225 """Start and stop an external process in an asynchronous manner.
226 226
227 227 This will launch the external process with a working directory of
228 228 ``self.work_dir``.
229 229 """
230 230
231 231 # This is used to to construct self.args, which is passed to
232 232 # spawnProcess.
233 233 cmd_and_args = List([])
234 234 poll_frequency = Int(100) # in ms
235 235
236 236 def __init__(self, work_dir=u'.', config=None, **kwargs):
237 237 super(LocalProcessLauncher, self).__init__(
238 238 work_dir=work_dir, config=config, **kwargs
239 239 )
240 240 self.process = None
241 241 self.start_deferred = None
242 242 self.poller = None
243 243
244 244 def find_args(self):
245 245 return self.cmd_and_args
246 246
247 247 def start(self):
248 248 if self.state == 'before':
249 249 self.process = Popen(self.args,
250 250 stdout=PIPE,stderr=PIPE,stdin=PIPE,
251 251 env=os.environ,
252 252 cwd=self.work_dir
253 253 )
254 254
255 255 self.loop.add_handler(self.process.stdout.fileno(), self.handle_stdout, self.loop.READ)
256 256 self.loop.add_handler(self.process.stderr.fileno(), self.handle_stderr, self.loop.READ)
257 257 self.poller = ioloop.PeriodicCallback(self.poll, self.poll_frequency, self.loop)
258 258 self.poller.start()
259 259 self.notify_start(self.process.pid)
260 260 else:
261 261 s = 'The process was already started and has state: %r' % self.state
262 262 raise ProcessStateError(s)
263 263
264 264 def stop(self):
265 265 return self.interrupt_then_kill()
266 266
267 267 def signal(self, sig):
268 268 if self.state == 'running':
269 269 self.process.send_signal(sig)
270 270
271 271 def interrupt_then_kill(self, delay=2.0):
272 272 """Send INT, wait a delay and then send KILL."""
273 273 self.signal(SIGINT)
274 274 self.killer = ioloop.DelayedCallback(lambda : self.signal(SIGKILL), delay*1000, self.loop)
275 275 self.killer.start()
276 276
277 277 # callbacks, etc:
278 278
279 279 def handle_stdout(self, fd, events):
280 280 line = self.process.stdout.readline()
281 281 # a stopped process will be readable but return empty strings
282 282 if line:
283 283 self.log.info(line[:-1])
284 284 else:
285 285 self.poll()
286 286
287 287 def handle_stderr(self, fd, events):
288 288 line = self.process.stderr.readline()
289 289 # a stopped process will be readable but return empty strings
290 290 if line:
291 291 self.log.error(line[:-1])
292 292 else:
293 293 self.poll()
294 294
295 295 def poll(self):
296 296 status = self.process.poll()
297 297 if status is not None:
298 298 self.poller.stop()
299 299 self.loop.remove_handler(self.process.stdout.fileno())
300 300 self.loop.remove_handler(self.process.stderr.fileno())
301 301 self.notify_stop(dict(exit_code=status, pid=self.process.pid))
302 302 return status
303 303
304 304 class LocalControllerLauncher(LocalProcessLauncher):
305 305 """Launch a controller as a regular external process."""
306 306
307 controller_cmd = List(ipcontrollerz_cmd_argv, config=True)
307 controller_cmd = List(ipcontroller_cmd_argv, config=True)
308 308 # Command line arguments to ipcontroller.
309 309 controller_args = List(['--log-to-file','--log-level', str(logging.INFO)], config=True)
310 310
311 311 def find_args(self):
312 312 return self.controller_cmd + self.controller_args
313 313
314 314 def start(self, cluster_dir):
315 315 """Start the controller by cluster_dir."""
316 316 self.controller_args.extend(['--cluster-dir', cluster_dir])
317 317 self.cluster_dir = unicode(cluster_dir)
318 318 self.log.info("Starting LocalControllerLauncher: %r" % self.args)
319 319 return super(LocalControllerLauncher, self).start()
320 320
321 321
322 322 class LocalEngineLauncher(LocalProcessLauncher):
323 323 """Launch a single engine as a regular externall process."""
324 324
325 engine_cmd = List(ipenginez_cmd_argv, config=True)
325 engine_cmd = List(ipengine_cmd_argv, config=True)
326 326 # Command line arguments for ipengine.
327 327 engine_args = List(
328 328 ['--log-to-file','--log-level', str(logging.INFO)], config=True
329 329 )
330 330
331 331 def find_args(self):
332 332 return self.engine_cmd + self.engine_args
333 333
334 334 def start(self, cluster_dir):
335 335 """Start the engine by cluster_dir."""
336 336 self.engine_args.extend(['--cluster-dir', cluster_dir])
337 337 self.cluster_dir = unicode(cluster_dir)
338 338 return super(LocalEngineLauncher, self).start()
339 339
340 340
341 341 class LocalEngineSetLauncher(BaseLauncher):
342 342 """Launch a set of engines as regular external processes."""
343 343
344 344 # Command line arguments for ipengine.
345 345 engine_args = List(
346 346 ['--log-to-file','--log-level', str(logging.INFO)], config=True
347 347 )
348 348 # launcher class
349 349 launcher_class = LocalEngineLauncher
350 350
351 351 launchers = Dict()
352 352 stop_data = Dict()
353 353
354 354 def __init__(self, work_dir=u'.', config=None, **kwargs):
355 355 super(LocalEngineSetLauncher, self).__init__(
356 356 work_dir=work_dir, config=config, **kwargs
357 357 )
358 358 self.stop_data = {}
359 359
360 360 def start(self, n, cluster_dir):
361 361 """Start n engines by profile or cluster_dir."""
362 362 self.cluster_dir = unicode(cluster_dir)
363 363 dlist = []
364 364 for i in range(n):
365 365 el = self.launcher_class(work_dir=self.work_dir, config=self.config, logname=self.log.name)
366 366 # Copy the engine args over to each engine launcher.
367 367 el.engine_args = copy.deepcopy(self.engine_args)
368 368 el.on_stop(self._notice_engine_stopped)
369 369 d = el.start(cluster_dir)
370 370 if i==0:
371 371 self.log.info("Starting LocalEngineSetLauncher: %r" % el.args)
372 372 self.launchers[i] = el
373 373 dlist.append(d)
374 374 self.notify_start(dlist)
375 375 # The consumeErrors here could be dangerous
376 376 # dfinal = gatherBoth(dlist, consumeErrors=True)
377 377 # dfinal.addCallback(self.notify_start)
378 378 return dlist
379 379
380 380 def find_args(self):
381 381 return ['engine set']
382 382
383 383 def signal(self, sig):
384 384 dlist = []
385 385 for el in self.launchers.itervalues():
386 386 d = el.signal(sig)
387 387 dlist.append(d)
388 388 # dfinal = gatherBoth(dlist, consumeErrors=True)
389 389 return dlist
390 390
391 391 def interrupt_then_kill(self, delay=1.0):
392 392 dlist = []
393 393 for el in self.launchers.itervalues():
394 394 d = el.interrupt_then_kill(delay)
395 395 dlist.append(d)
396 396 # dfinal = gatherBoth(dlist, consumeErrors=True)
397 397 return dlist
398 398
399 399 def stop(self):
400 400 return self.interrupt_then_kill()
401 401
402 402 def _notice_engine_stopped(self, data):
403 403 pid = data['pid']
404 404 for idx,el in self.launchers.iteritems():
405 405 if el.process.pid == pid:
406 406 break
407 407 self.launchers.pop(idx)
408 408 self.stop_data[idx] = data
409 409 if not self.launchers:
410 410 self.notify_stop(self.stop_data)
411 411
412 412
413 413 #-----------------------------------------------------------------------------
414 414 # MPIExec launchers
415 415 #-----------------------------------------------------------------------------
416 416
417 417
418 418 class MPIExecLauncher(LocalProcessLauncher):
419 419 """Launch an external process using mpiexec."""
420 420
421 421 # The mpiexec command to use in starting the process.
422 422 mpi_cmd = List(['mpiexec'], config=True)
423 423 # The command line arguments to pass to mpiexec.
424 424 mpi_args = List([], config=True)
425 425 # The program to start using mpiexec.
426 426 program = List(['date'], config=True)
427 427 # The command line argument to the program.
428 428 program_args = List([], config=True)
429 429 # The number of instances of the program to start.
430 430 n = Int(1, config=True)
431 431
432 432 def find_args(self):
433 433 """Build self.args using all the fields."""
434 434 return self.mpi_cmd + ['-n', str(self.n)] + self.mpi_args + \
435 435 self.program + self.program_args
436 436
437 437 def start(self, n):
438 438 """Start n instances of the program using mpiexec."""
439 439 self.n = n
440 440 return super(MPIExecLauncher, self).start()
441 441
442 442
443 443 class MPIExecControllerLauncher(MPIExecLauncher):
444 444 """Launch a controller using mpiexec."""
445 445
446 controller_cmd = List(ipcontrollerz_cmd_argv, config=True)
446 controller_cmd = List(ipcontroller_cmd_argv, config=True)
447 447 # Command line arguments to ipcontroller.
448 448 controller_args = List(['--log-to-file','--log-level', str(logging.INFO)], config=True)
449 449 n = Int(1, config=False)
450 450
451 451 def start(self, cluster_dir):
452 452 """Start the controller by cluster_dir."""
453 453 self.controller_args.extend(['--cluster-dir', cluster_dir])
454 454 self.cluster_dir = unicode(cluster_dir)
455 455 self.log.info("Starting MPIExecControllerLauncher: %r" % self.args)
456 456 return super(MPIExecControllerLauncher, self).start(1)
457 457
458 458 def find_args(self):
459 459 return self.mpi_cmd + ['-n', self.n] + self.mpi_args + \
460 460 self.controller_cmd + self.controller_args
461 461
462 462
463 463 class MPIExecEngineSetLauncher(MPIExecLauncher):
464 464
465 program = List(ipenginez_cmd_argv, config=True)
465 program = List(ipengine_cmd_argv, config=True)
466 466 # Command line arguments for ipengine.
467 467 program_args = List(
468 468 ['--log-to-file','--log-level', str(logging.INFO)], config=True
469 469 )
470 470 n = Int(1, config=True)
471 471
472 472 def start(self, n, cluster_dir):
473 473 """Start n engines by profile or cluster_dir."""
474 474 self.program_args.extend(['--cluster-dir', cluster_dir])
475 475 self.cluster_dir = unicode(cluster_dir)
476 476 self.n = n
477 477 self.log.info('Starting MPIExecEngineSetLauncher: %r' % self.args)
478 478 return super(MPIExecEngineSetLauncher, self).start(n)
479 479
480 480 #-----------------------------------------------------------------------------
481 481 # SSH launchers
482 482 #-----------------------------------------------------------------------------
483 483
484 484 # TODO: Get SSH Launcher working again.
485 485
486 486 class SSHLauncher(LocalProcessLauncher):
487 487 """A minimal launcher for ssh.
488 488
489 489 To be useful this will probably have to be extended to use the ``sshx``
490 490 idea for environment variables. There could be other things this needs
491 491 as well.
492 492 """
493 493
494 494 ssh_cmd = List(['ssh'], config=True)
495 495 ssh_args = List(['-tt'], config=True)
496 496 program = List(['date'], config=True)
497 497 program_args = List([], config=True)
498 498 hostname = CUnicode('', config=True)
499 499 user = CUnicode('', config=True)
500 500 location = CUnicode('')
501 501
502 502 def _hostname_changed(self, name, old, new):
503 503 if self.user:
504 504 self.location = u'%s@%s' % (self.user, new)
505 505 else:
506 506 self.location = new
507 507
508 508 def _user_changed(self, name, old, new):
509 509 self.location = u'%s@%s' % (new, self.hostname)
510 510
511 511 def find_args(self):
512 512 return self.ssh_cmd + self.ssh_args + [self.location] + \
513 513 self.program + self.program_args
514 514
515 515 def start(self, cluster_dir, hostname=None, user=None):
516 516 self.cluster_dir = unicode(cluster_dir)
517 517 if hostname is not None:
518 518 self.hostname = hostname
519 519 if user is not None:
520 520 self.user = user
521 521
522 522 return super(SSHLauncher, self).start()
523 523
524 524 def signal(self, sig):
525 525 if self.state == 'running':
526 526 # send escaped ssh connection-closer
527 527 self.process.stdin.write('~.')
528 528 self.process.stdin.flush()
529 529
530 530
531 531
532 532 class SSHControllerLauncher(SSHLauncher):
533 533
534 program = List(ipcontrollerz_cmd_argv, config=True)
534 program = List(ipcontroller_cmd_argv, config=True)
535 535 # Command line arguments to ipcontroller.
536 536 program_args = List(['-r', '--log-to-file','--log-level', str(logging.INFO)], config=True)
537 537
538 538
539 539 class SSHEngineLauncher(SSHLauncher):
540 program = List(ipenginez_cmd_argv, config=True)
540 program = List(ipengine_cmd_argv, config=True)
541 541 # Command line arguments for ipengine.
542 542 program_args = List(
543 543 ['--log-to-file','--log-level', str(logging.INFO)], config=True
544 544 )
545 545
546 546 class SSHEngineSetLauncher(LocalEngineSetLauncher):
547 547 launcher_class = SSHEngineLauncher
548 548 engines = Dict(config=True)
549 549
550 550 def start(self, n, cluster_dir):
551 551 """Start engines by profile or cluster_dir.
552 552 `n` is ignored, and the `engines` config property is used instead.
553 553 """
554 554
555 555 self.cluster_dir = unicode(cluster_dir)
556 556 dlist = []
557 557 for host, n in self.engines.iteritems():
558 558 if isinstance(n, (tuple, list)):
559 559 n, args = n
560 560 else:
561 561 args = copy.deepcopy(self.engine_args)
562 562
563 563 if '@' in host:
564 564 user,host = host.split('@',1)
565 565 else:
566 566 user=None
567 567 for i in range(n):
568 568 el = self.launcher_class(work_dir=self.work_dir, config=self.config, logname=self.log.name)
569 569
570 570 # Copy the engine args over to each engine launcher.
571 571 i
572 572 el.program_args = args
573 573 el.on_stop(self._notice_engine_stopped)
574 574 d = el.start(cluster_dir, user=user, hostname=host)
575 575 if i==0:
576 576 self.log.info("Starting SSHEngineSetLauncher: %r" % el.args)
577 577 self.launchers[host+str(i)] = el
578 578 dlist.append(d)
579 579 self.notify_start(dlist)
580 580 return dlist
581 581
582 582
583 583
584 584 #-----------------------------------------------------------------------------
585 585 # Windows HPC Server 2008 scheduler launchers
586 586 #-----------------------------------------------------------------------------
587 587
588 588
589 589 # This is only used on Windows.
590 590 def find_job_cmd():
591 591 if os.name=='nt':
592 592 try:
593 593 return find_cmd('job')
594 594 except FindCmdError:
595 595 return 'job'
596 596 else:
597 597 return 'job'
598 598
599 599
600 600 class WindowsHPCLauncher(BaseLauncher):
601 601
602 602 # A regular expression used to get the job id from the output of the
603 603 # submit_command.
604 604 job_id_regexp = Str(r'\d+', config=True)
605 605 # The filename of the instantiated job script.
606 606 job_file_name = CUnicode(u'ipython_job.xml', config=True)
607 607 # The full path to the instantiated job script. This gets made dynamically
608 608 # by combining the work_dir with the job_file_name.
609 609 job_file = CUnicode(u'')
610 610 # The hostname of the scheduler to submit the job to
611 611 scheduler = CUnicode('', config=True)
612 612 job_cmd = CUnicode(find_job_cmd(), config=True)
613 613
614 614 def __init__(self, work_dir=u'.', config=None, **kwargs):
615 615 super(WindowsHPCLauncher, self).__init__(
616 616 work_dir=work_dir, config=config, **kwargs
617 617 )
618 618
619 619 @property
620 620 def job_file(self):
621 621 return os.path.join(self.work_dir, self.job_file_name)
622 622
623 623 def write_job_file(self, n):
624 624 raise NotImplementedError("Implement write_job_file in a subclass.")
625 625
626 626 def find_args(self):
627 627 return [u'job.exe']
628 628
629 629 def parse_job_id(self, output):
630 630 """Take the output of the submit command and return the job id."""
631 631 m = re.search(self.job_id_regexp, output)
632 632 if m is not None:
633 633 job_id = m.group()
634 634 else:
635 635 raise LauncherError("Job id couldn't be determined: %s" % output)
636 636 self.job_id = job_id
637 637 self.log.info('Job started with job id: %r' % job_id)
638 638 return job_id
639 639
640 640 def start(self, n):
641 641 """Start n copies of the process using the Win HPC job scheduler."""
642 642 self.write_job_file(n)
643 643 args = [
644 644 'submit',
645 645 '/jobfile:%s' % self.job_file,
646 646 '/scheduler:%s' % self.scheduler
647 647 ]
648 648 self.log.info("Starting Win HPC Job: %s" % (self.job_cmd + ' ' + ' '.join(args),))
649 649 # Twisted will raise DeprecationWarnings if we try to pass unicode to this
650 650 output = check_output([self.job_cmd]+args,
651 651 env=os.environ,
652 652 cwd=self.work_dir,
653 653 stderr=STDOUT
654 654 )
655 655 job_id = self.parse_job_id(output)
656 656 self.notify_start(job_id)
657 657 return job_id
658 658
659 659 def stop(self):
660 660 args = [
661 661 'cancel',
662 662 self.job_id,
663 663 '/scheduler:%s' % self.scheduler
664 664 ]
665 665 self.log.info("Stopping Win HPC Job: %s" % (self.job_cmd + ' ' + ' '.join(args),))
666 666 try:
667 667 output = check_output([self.job_cmd]+args,
668 668 env=os.environ,
669 669 cwd=self.work_dir,
670 670 stderr=STDOUT
671 671 )
672 672 except:
673 673 output = 'The job already appears to be stoppped: %r' % self.job_id
674 674 self.notify_stop(dict(job_id=self.job_id, output=output)) # Pass the output of the kill cmd
675 675 return output
676 676
677 677
678 678 class WindowsHPCControllerLauncher(WindowsHPCLauncher):
679 679
680 680 job_file_name = CUnicode(u'ipcontroller_job.xml', config=True)
681 681 extra_args = List([], config=False)
682 682
683 683 def write_job_file(self, n):
684 684 job = IPControllerJob(config=self.config)
685 685
686 686 t = IPControllerTask(config=self.config)
687 687 # The tasks work directory is *not* the actual work directory of
688 688 # the controller. It is used as the base path for the stdout/stderr
689 689 # files that the scheduler redirects to.
690 690 t.work_directory = self.cluster_dir
691 691 # Add the --cluster-dir and from self.start().
692 692 t.controller_args.extend(self.extra_args)
693 693 job.add_task(t)
694 694
695 695 self.log.info("Writing job description file: %s" % self.job_file)
696 696 job.write(self.job_file)
697 697
698 698 @property
699 699 def job_file(self):
700 700 return os.path.join(self.cluster_dir, self.job_file_name)
701 701
702 702 def start(self, cluster_dir):
703 703 """Start the controller by cluster_dir."""
704 704 self.extra_args = ['--cluster-dir', cluster_dir]
705 705 self.cluster_dir = unicode(cluster_dir)
706 706 return super(WindowsHPCControllerLauncher, self).start(1)
707 707
708 708
709 709 class WindowsHPCEngineSetLauncher(WindowsHPCLauncher):
710 710
711 711 job_file_name = CUnicode(u'ipengineset_job.xml', config=True)
712 712 extra_args = List([], config=False)
713 713
714 714 def write_job_file(self, n):
715 715 job = IPEngineSetJob(config=self.config)
716 716
717 717 for i in range(n):
718 718 t = IPEngineTask(config=self.config)
719 719 # The tasks work directory is *not* the actual work directory of
720 720 # the engine. It is used as the base path for the stdout/stderr
721 721 # files that the scheduler redirects to.
722 722 t.work_directory = self.cluster_dir
723 723 # Add the --cluster-dir and from self.start().
724 724 t.engine_args.extend(self.extra_args)
725 725 job.add_task(t)
726 726
727 727 self.log.info("Writing job description file: %s" % self.job_file)
728 728 job.write(self.job_file)
729 729
730 730 @property
731 731 def job_file(self):
732 732 return os.path.join(self.cluster_dir, self.job_file_name)
733 733
734 734 def start(self, n, cluster_dir):
735 735 """Start the controller by cluster_dir."""
736 736 self.extra_args = ['--cluster-dir', cluster_dir]
737 737 self.cluster_dir = unicode(cluster_dir)
738 738 return super(WindowsHPCEngineSetLauncher, self).start(n)
739 739
740 740
741 741 #-----------------------------------------------------------------------------
742 742 # Batch (PBS) system launchers
743 743 #-----------------------------------------------------------------------------
744 744
745 745 class BatchSystemLauncher(BaseLauncher):
746 746 """Launch an external process using a batch system.
747 747
748 748 This class is designed to work with UNIX batch systems like PBS, LSF,
749 749 GridEngine, etc. The overall model is that there are different commands
750 750 like qsub, qdel, etc. that handle the starting and stopping of the process.
751 751
752 752 This class also has the notion of a batch script. The ``batch_template``
753 753 attribute can be set to a string that is a template for the batch script.
754 754 This template is instantiated using Itpl. Thus the template can use
755 755 ${n} fot the number of instances. Subclasses can add additional variables
756 756 to the template dict.
757 757 """
758 758
759 759 # Subclasses must fill these in. See PBSEngineSet
760 760 # The name of the command line program used to submit jobs.
761 761 submit_command = List([''], config=True)
762 762 # The name of the command line program used to delete jobs.
763 763 delete_command = List([''], config=True)
764 764 # A regular expression used to get the job id from the output of the
765 765 # submit_command.
766 766 job_id_regexp = CUnicode('', config=True)
767 767 # The string that is the batch script template itself.
768 768 batch_template = CUnicode('', config=True)
769 769 # The file that contains the batch template
770 770 batch_template_file = CUnicode(u'', config=True)
771 771 # The filename of the instantiated batch script.
772 772 batch_file_name = CUnicode(u'batch_script', config=True)
773 773 # The PBS Queue
774 774 queue = CUnicode(u'', config=True)
775 775
776 776 # not configurable, override in subclasses
777 777 # PBS Job Array regex
778 778 job_array_regexp = CUnicode('')
779 779 job_array_template = CUnicode('')
780 780 # PBS Queue regex
781 781 queue_regexp = CUnicode('')
782 782 queue_template = CUnicode('')
783 783 # The default batch template, override in subclasses
784 784 default_template = CUnicode('')
785 785 # The full path to the instantiated batch script.
786 786 batch_file = CUnicode(u'')
787 787 # the format dict used with batch_template:
788 788 context = Dict()
789 789
790 790
791 791 def find_args(self):
792 792 return self.submit_command + [self.batch_file]
793 793
794 794 def __init__(self, work_dir=u'.', config=None, **kwargs):
795 795 super(BatchSystemLauncher, self).__init__(
796 796 work_dir=work_dir, config=config, **kwargs
797 797 )
798 798 self.batch_file = os.path.join(self.work_dir, self.batch_file_name)
799 799
800 800 def parse_job_id(self, output):
801 801 """Take the output of the submit command and return the job id."""
802 802 m = re.search(self.job_id_regexp, output)
803 803 if m is not None:
804 804 job_id = m.group()
805 805 else:
806 806 raise LauncherError("Job id couldn't be determined: %s" % output)
807 807 self.job_id = job_id
808 808 self.log.info('Job submitted with job id: %r' % job_id)
809 809 return job_id
810 810
811 811 def write_batch_script(self, n):
812 812 """Instantiate and write the batch script to the work_dir."""
813 813 self.context['n'] = n
814 814 self.context['queue'] = self.queue
815 815 print self.context
816 816 # first priority is batch_template if set
817 817 if self.batch_template_file and not self.batch_template:
818 818 # second priority is batch_template_file
819 819 with open(self.batch_template_file) as f:
820 820 self.batch_template = f.read()
821 821 if not self.batch_template:
822 822 # third (last) priority is default_template
823 823 self.batch_template = self.default_template
824 824
825 825 regex = re.compile(self.job_array_regexp)
826 826 # print regex.search(self.batch_template)
827 827 if not regex.search(self.batch_template):
828 828 self.log.info("adding job array settings to batch script")
829 829 firstline, rest = self.batch_template.split('\n',1)
830 830 self.batch_template = u'\n'.join([firstline, self.job_array_template, rest])
831 831
832 832 regex = re.compile(self.queue_regexp)
833 833 # print regex.search(self.batch_template)
834 834 if self.queue and not regex.search(self.batch_template):
835 835 self.log.info("adding PBS queue settings to batch script")
836 836 firstline, rest = self.batch_template.split('\n',1)
837 837 self.batch_template = u'\n'.join([firstline, self.queue_template, rest])
838 838
839 839 script_as_string = Itpl.itplns(self.batch_template, self.context)
840 840 self.log.info('Writing instantiated batch script: %s' % self.batch_file)
841 841
842 842 with open(self.batch_file, 'w') as f:
843 843 f.write(script_as_string)
844 844 os.chmod(self.batch_file, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR)
845 845
846 846 def start(self, n, cluster_dir):
847 847 """Start n copies of the process using a batch system."""
848 848 # Here we save profile and cluster_dir in the context so they
849 849 # can be used in the batch script template as ${profile} and
850 850 # ${cluster_dir}
851 851 self.context['cluster_dir'] = cluster_dir
852 852 self.cluster_dir = unicode(cluster_dir)
853 853 self.write_batch_script(n)
854 854 output = check_output(self.args, env=os.environ)
855 855
856 856 job_id = self.parse_job_id(output)
857 857 self.notify_start(job_id)
858 858 return job_id
859 859
860 860 def stop(self):
861 861 output = check_output(self.delete_command+[self.job_id], env=os.environ)
862 862 self.notify_stop(dict(job_id=self.job_id, output=output)) # Pass the output of the kill cmd
863 863 return output
864 864
865 865
866 866 class PBSLauncher(BatchSystemLauncher):
867 867 """A BatchSystemLauncher subclass for PBS."""
868 868
869 869 submit_command = List(['qsub'], config=True)
870 870 delete_command = List(['qdel'], config=True)
871 871 job_id_regexp = CUnicode(r'\d+', config=True)
872 872
873 873 batch_file = CUnicode(u'')
874 874 job_array_regexp = CUnicode('#PBS\W+-t\W+[\w\d\-\$]+')
875 875 job_array_template = CUnicode('#PBS -t 1-$n')
876 876 queue_regexp = CUnicode('#PBS\W+-q\W+\$?\w+')
877 877 queue_template = CUnicode('#PBS -q $queue')
878 878
879 879
880 880 class PBSControllerLauncher(PBSLauncher):
881 881 """Launch a controller using PBS."""
882 882
883 883 batch_file_name = CUnicode(u'pbs_controller', config=True)
884 884 default_template= CUnicode("""#!/bin/sh
885 885 #PBS -V
886 #PBS -N ipcontrollerz
886 #PBS -N ipcontroller
887 887 %s --log-to-file --cluster-dir $cluster_dir
888 """%(' '.join(ipcontrollerz_cmd_argv)))
888 """%(' '.join(ipcontroller_cmd_argv)))
889 889
890 890 def start(self, cluster_dir):
891 891 """Start the controller by profile or cluster_dir."""
892 892 self.log.info("Starting PBSControllerLauncher: %r" % self.args)
893 893 return super(PBSControllerLauncher, self).start(1, cluster_dir)
894 894
895 895
896 896 class PBSEngineSetLauncher(PBSLauncher):
897 897 """Launch Engines using PBS"""
898 898 batch_file_name = CUnicode(u'pbs_engines', config=True)
899 899 default_template= CUnicode(u"""#!/bin/sh
900 900 #PBS -V
901 #PBS -N ipenginez
901 #PBS -N ipengine
902 902 %s --cluster-dir $cluster_dir
903 """%(' '.join(ipenginez_cmd_argv)))
903 """%(' '.join(ipengine_cmd_argv)))
904 904
905 905 def start(self, n, cluster_dir):
906 906 """Start n engines by profile or cluster_dir."""
907 907 self.log.info('Starting %i engines with PBSEngineSetLauncher: %r' % (n, self.args))
908 908 return super(PBSEngineSetLauncher, self).start(n, cluster_dir)
909 909
910 910 #SGE is very similar to PBS
911 911
912 912 class SGELauncher(PBSLauncher):
913 913 """Sun GridEngine is a PBS clone with slightly different syntax"""
914 914 job_array_regexp = CUnicode('#$$\W+-t\W+[\w\d\-\$]+')
915 915 job_array_template = CUnicode('#$$ -t 1-$n')
916 916 queue_regexp = CUnicode('#$$\W+-q\W+\$?\w+')
917 917 queue_template = CUnicode('#$$ -q $queue')
918 918
919 919 class SGEControllerLauncher(SGELauncher):
920 920 """Launch a controller using SGE."""
921 921
922 922 batch_file_name = CUnicode(u'sge_controller', config=True)
923 923 default_template= CUnicode(u"""#$$ -V
924 924 #$$ -S /bin/sh
925 #$$ -N ipcontrollerz
925 #$$ -N ipcontroller
926 926 %s --log-to-file --cluster-dir $cluster_dir
927 """%(' '.join(ipcontrollerz_cmd_argv)))
927 """%(' '.join(ipcontroller_cmd_argv)))
928 928
929 929 def start(self, cluster_dir):
930 930 """Start the controller by profile or cluster_dir."""
931 931 self.log.info("Starting PBSControllerLauncher: %r" % self.args)
932 932 return super(PBSControllerLauncher, self).start(1, cluster_dir)
933 933
934 934 class SGEEngineSetLauncher(SGELauncher):
935 935 """Launch Engines with SGE"""
936 936 batch_file_name = CUnicode(u'sge_engines', config=True)
937 937 default_template = CUnicode("""#$$ -V
938 938 #$$ -S /bin/sh
939 #$$ -N ipenginez
939 #$$ -N ipengine
940 940 %s --cluster-dir $cluster_dir
941 """%(' '.join(ipenginez_cmd_argv)))
941 """%(' '.join(ipengine_cmd_argv)))
942 942
943 943 def start(self, n, cluster_dir):
944 944 """Start n engines by profile or cluster_dir."""
945 945 self.log.info('Starting %i engines with SGEEngineSetLauncher: %r' % (n, self.args))
946 946 return super(SGEEngineSetLauncher, self).start(n, cluster_dir)
947 947
948 948
949 949 #-----------------------------------------------------------------------------
950 950 # A launcher for ipcluster itself!
951 951 #-----------------------------------------------------------------------------
952 952
953 953
954 954 class IPClusterLauncher(LocalProcessLauncher):
955 955 """Launch the ipcluster program in an external process."""
956 956
957 ipcluster_cmd = List(ipclusterz_cmd_argv, config=True)
957 ipcluster_cmd = List(ipcluster_cmd_argv, config=True)
958 958 # Command line arguments to pass to ipcluster.
959 959 ipcluster_args = List(
960 960 ['--clean-logs', '--log-to-file', '--log-level', str(logging.INFO)], config=True)
961 961 ipcluster_subcommand = Str('start')
962 962 ipcluster_n = Int(2)
963 963
964 964 def find_args(self):
965 965 return self.ipcluster_cmd + [self.ipcluster_subcommand] + \
966 966 ['-n', repr(self.ipcluster_n)] + self.ipcluster_args
967 967
968 968 def start(self):
969 969 self.log.info("Starting ipcluster: %r" % self.args)
970 970 return super(IPClusterLauncher, self).start()
971 971
@@ -1,98 +1,98 b''
1 1 #!/usr/bin/env python
2 """A simple logger object that consolidates messages incoming from ipclusterz processes."""
2 """A simple logger object that consolidates messages incoming from ipcluster processes."""
3 3
4 4 #-----------------------------------------------------------------------------
5 5 # Copyright (C) 2011 The IPython Development Team
6 6 #
7 7 # Distributed under the terms of the BSD License. The full license is in
8 8 # the file COPYING, distributed as part of this software.
9 9 #-----------------------------------------------------------------------------
10 10
11 11 #-----------------------------------------------------------------------------
12 12 # Imports
13 13 #-----------------------------------------------------------------------------
14 14
15 15
16 16 import logging
17 17 import sys
18 18
19 19 import zmq
20 20 from zmq.eventloop import ioloop, zmqstream
21 21
22 22 from IPython.utils.traitlets import Int, Str, Instance, List
23 23
24 24 from .factory import LoggingFactory
25 25
26 26 #-----------------------------------------------------------------------------
27 27 # Classes
28 28 #-----------------------------------------------------------------------------
29 29
30 30
31 31 class LogWatcher(LoggingFactory):
32 32 """A simple class that receives messages on a SUB socket, as published
33 33 by subclasses of `zmq.log.handlers.PUBHandler`, and logs them itself.
34 34
35 35 This can subscribe to multiple topics, but defaults to all topics.
36 36 """
37 37 # configurables
38 38 topics = List([''], config=True)
39 39 url = Str('tcp://127.0.0.1:20202', config=True)
40 40
41 41 # internals
42 42 context = Instance(zmq.Context, (), {})
43 43 stream = Instance('zmq.eventloop.zmqstream.ZMQStream')
44 44 loop = Instance('zmq.eventloop.ioloop.IOLoop')
45 45 def _loop_default(self):
46 46 return ioloop.IOLoop.instance()
47 47
48 48 def __init__(self, **kwargs):
49 49 super(LogWatcher, self).__init__(**kwargs)
50 50 s = self.context.socket(zmq.SUB)
51 51 s.bind(self.url)
52 52 self.stream = zmqstream.ZMQStream(s, self.loop)
53 53 self.subscribe()
54 54 self.on_trait_change(self.subscribe, 'topics')
55 55
56 56 def start(self):
57 57 self.stream.on_recv(self.log_message)
58 58
59 59 def stop(self):
60 60 self.stream.stop_on_recv()
61 61
62 62 def subscribe(self):
63 63 """Update our SUB socket's subscriptions."""
64 64 self.stream.setsockopt(zmq.UNSUBSCRIBE, '')
65 65 for topic in self.topics:
66 66 self.log.debug("Subscribing to: %r"%topic)
67 67 self.stream.setsockopt(zmq.SUBSCRIBE, topic)
68 68
69 69 def _extract_level(self, topic_str):
70 70 """Turn 'engine.0.INFO.extra' into (logging.INFO, 'engine.0.extra')"""
71 71 topics = topic_str.split('.')
72 72 for idx,t in enumerate(topics):
73 73 level = getattr(logging, t, None)
74 74 if level is not None:
75 75 break
76 76
77 77 if level is None:
78 78 level = logging.INFO
79 79 else:
80 80 topics.pop(idx)
81 81
82 82 return level, '.'.join(topics)
83 83
84 84
85 85 def log_message(self, raw):
86 86 """receive and parse a message, then log it."""
87 87 if len(raw) != 2 or '.' not in raw[0]:
88 88 self.log.error("Invalid log message: %s"%raw)
89 89 return
90 90 else:
91 91 topic, msg = raw
92 92 # don't newline, since log messages always newline:
93 93 topic,level_name = topic.rsplit('.',1)
94 94 level,topic = self._extract_level(topic)
95 95 if msg[-1] == '\n':
96 96 msg = msg[:-1]
97 97 logging.log(level, "[%s] %s" % (topic, msg))
98 98
1 NO CONTENT: file renamed from IPython/parallel/scripts/ipclusterz to IPython/parallel/scripts/ipcluster
1 NO CONTENT: file renamed from IPython/parallel/scripts/ipcontrollerz to IPython/parallel/scripts/ipcontroller
1 NO CONTENT: file renamed from IPython/parallel/scripts/ipenginez to IPython/parallel/scripts/ipengine
1 NO CONTENT: file renamed from IPython/parallel/scripts/iploggerz to IPython/parallel/scripts/iplogger
@@ -1,69 +1,69 b''
1 1 """toplevel setup/teardown for parallel tests."""
2 2
3 3 #-------------------------------------------------------------------------------
4 4 # Copyright (C) 2011 The IPython Development Team
5 5 #
6 6 # Distributed under the terms of the BSD License. The full license is in
7 7 # the file COPYING, distributed as part of this software.
8 8 #-------------------------------------------------------------------------------
9 9
10 10 #-------------------------------------------------------------------------------
11 11 # Imports
12 12 #-------------------------------------------------------------------------------
13 13
14 14 import tempfile
15 15 import time
16 16 from subprocess import Popen, PIPE, STDOUT
17 17
18 18 from IPython.parallel import client
19 19
20 20 processes = []
21 21 blackhole = tempfile.TemporaryFile()
22 22
23 23 # nose setup/teardown
24 24
25 25 def setup():
26 cp = Popen('ipcontrollerz --profile iptest -r --log-level 10 --log-to-file'.split(), stdout=blackhole, stderr=STDOUT)
26 cp = Popen('ipcontroller --profile iptest -r --log-level 10 --log-to-file'.split(), stdout=blackhole, stderr=STDOUT)
27 27 processes.append(cp)
28 28 time.sleep(.5)
29 29 add_engines(1)
30 30 c = client.Client(profile='iptest')
31 31 while not c.ids:
32 32 time.sleep(.1)
33 33 c.spin()
34 34 c.close()
35 35
36 36 def add_engines(n=1, profile='iptest'):
37 37 rc = client.Client(profile=profile)
38 38 base = len(rc)
39 39 eps = []
40 40 for i in range(n):
41 ep = Popen(['ipenginez']+ ['--profile', profile, '--log-level', '10', '--log-to-file'], stdout=blackhole, stderr=STDOUT)
41 ep = Popen(['ipengine']+ ['--profile', profile, '--log-level', '10', '--log-to-file'], stdout=blackhole, stderr=STDOUT)
42 42 # ep.start()
43 43 processes.append(ep)
44 44 eps.append(ep)
45 45 while len(rc) < base+n:
46 46 time.sleep(.1)
47 47 rc.spin()
48 48 rc.close()
49 49 return eps
50 50
51 51 def teardown():
52 52 time.sleep(1)
53 53 while processes:
54 54 p = processes.pop()
55 55 if p.poll() is None:
56 56 try:
57 57 p.terminate()
58 58 except Exception, e:
59 59 print e
60 60 pass
61 61 if p.poll() is None:
62 62 time.sleep(.25)
63 63 if p.poll() is None:
64 64 try:
65 65 print 'killing'
66 66 p.kill()
67 67 except:
68 68 print "couldn't shutdown process: ", p
69 69
@@ -1,108 +1,108 b''
1 1 """test serialization with newserialized"""
2 2
3 3 #-------------------------------------------------------------------------------
4 4 # Copyright (C) 2011 The IPython Development Team
5 5 #
6 6 # Distributed under the terms of the BSD License. The full license is in
7 7 # the file COPYING, distributed as part of this software.
8 8 #-------------------------------------------------------------------------------
9 9
10 10 #-------------------------------------------------------------------------------
11 11 # Imports
12 12 #-------------------------------------------------------------------------------
13 13
14 14 from unittest import TestCase
15 15
16 from IPython.testing.parametric import parametric
16 from IPython.testing.decorators import parametric
17 17 from IPython.utils import newserialized as ns
18 18 from IPython.utils.pickleutil import can, uncan, CannedObject, CannedFunction
19 19 from IPython.parallel.tests.clienttest import skip_without
20 20
21 21
22 22 class CanningTestCase(TestCase):
23 23 def test_canning(self):
24 24 d = dict(a=5,b=6)
25 25 cd = can(d)
26 26 self.assertTrue(isinstance(cd, dict))
27 27
28 28 def test_canned_function(self):
29 29 f = lambda : 7
30 30 cf = can(f)
31 31 self.assertTrue(isinstance(cf, CannedFunction))
32 32
33 33 @parametric
34 34 def test_can_roundtrip(cls):
35 35 objs = [
36 36 dict(),
37 37 set(),
38 38 list(),
39 39 ['a',1,['a',1],u'e'],
40 40 ]
41 41 return map(cls.run_roundtrip, objs)
42 42
43 43 @classmethod
44 44 def run_roundtrip(self, obj):
45 45 o = uncan(can(obj))
46 46 assert o == obj, "failed assertion: %r == %r"%(o,obj)
47 47
48 48 def test_serialized_interfaces(self):
49 49
50 50 us = {'a':10, 'b':range(10)}
51 51 s = ns.serialize(us)
52 52 uus = ns.unserialize(s)
53 53 self.assertTrue(isinstance(s, ns.SerializeIt))
54 54 self.assertEquals(uus, us)
55 55
56 56 def test_pickle_serialized(self):
57 57 obj = {'a':1.45345, 'b':'asdfsdf', 'c':10000L}
58 58 original = ns.UnSerialized(obj)
59 59 originalSer = ns.SerializeIt(original)
60 60 firstData = originalSer.getData()
61 61 firstTD = originalSer.getTypeDescriptor()
62 62 firstMD = originalSer.getMetadata()
63 63 self.assertEquals(firstTD, 'pickle')
64 64 self.assertEquals(firstMD, {})
65 65 unSerialized = ns.UnSerializeIt(originalSer)
66 66 secondObj = unSerialized.getObject()
67 67 for k, v in secondObj.iteritems():
68 68 self.assertEquals(obj[k], v)
69 69 secondSer = ns.SerializeIt(ns.UnSerialized(secondObj))
70 70 self.assertEquals(firstData, secondSer.getData())
71 71 self.assertEquals(firstTD, secondSer.getTypeDescriptor() )
72 72 self.assertEquals(firstMD, secondSer.getMetadata())
73 73
74 74 @skip_without('numpy')
75 75 def test_ndarray_serialized(self):
76 76 import numpy
77 77 a = numpy.linspace(0.0, 1.0, 1000)
78 78 unSer1 = ns.UnSerialized(a)
79 79 ser1 = ns.SerializeIt(unSer1)
80 80 td = ser1.getTypeDescriptor()
81 81 self.assertEquals(td, 'ndarray')
82 82 md = ser1.getMetadata()
83 83 self.assertEquals(md['shape'], a.shape)
84 84 self.assertEquals(md['dtype'], a.dtype.str)
85 85 buff = ser1.getData()
86 86 self.assertEquals(buff, numpy.getbuffer(a))
87 87 s = ns.Serialized(buff, td, md)
88 88 final = ns.unserialize(s)
89 89 self.assertEquals(numpy.getbuffer(a), numpy.getbuffer(final))
90 90 self.assertTrue((a==final).all())
91 91 self.assertEquals(a.dtype.str, final.dtype.str)
92 92 self.assertEquals(a.shape, final.shape)
93 93 # test non-copying:
94 94 a[2] = 1e9
95 95 self.assertTrue((a==final).all())
96 96
97 97 def test_uncan_function_globals(self):
98 98 """test that uncanning a module function restores it into its module"""
99 99 from re import search
100 100 cf = can(search)
101 101 csearch = uncan(cf)
102 102 self.assertEqual(csearch.__module__, search.__module__)
103 103 self.assertNotEqual(csearch('asd', 'asdf'), None)
104 104 csearch = uncan(cf, dict(a=5))
105 105 self.assertEqual(csearch.__module__, search.__module__)
106 106 self.assertNotEqual(csearch('asd', 'asdf'), None)
107 107
108 108 No newline at end of file
@@ -1,419 +1,420 b''
1 1 # -*- coding: utf-8 -*-
2 2 """IPython Test Suite Runner.
3 3
4 4 This module provides a main entry point to a user script to test IPython
5 5 itself from the command line. There are two ways of running this script:
6 6
7 7 1. With the syntax `iptest all`. This runs our entire test suite by
8 8 calling this script (with different arguments) recursively. This
9 9 causes modules and package to be tested in different processes, using nose
10 10 or trial where appropriate.
11 11 2. With the regular nose syntax, like `iptest -vvs IPython`. In this form
12 12 the script simply calls nose, but with special command line flags and
13 13 plugins loaded.
14 14
15 15 """
16 16
17 17 #-----------------------------------------------------------------------------
18 18 # Copyright (C) 2009 The IPython Development Team
19 19 #
20 20 # Distributed under the terms of the BSD License. The full license is in
21 21 # the file COPYING, distributed as part of this software.
22 22 #-----------------------------------------------------------------------------
23 23
24 24 #-----------------------------------------------------------------------------
25 25 # Imports
26 26 #-----------------------------------------------------------------------------
27 27
28 28 # Stdlib
29 29 import os
30 30 import os.path as path
31 31 import signal
32 32 import sys
33 33 import subprocess
34 34 import tempfile
35 35 import time
36 36 import warnings
37 37
38 38 # Note: monkeypatch!
39 39 # We need to monkeypatch a small problem in nose itself first, before importing
40 40 # it for actual use. This should get into nose upstream, but its release cycle
41 41 # is slow and we need it for our parametric tests to work correctly.
42 42 from IPython.testing import nosepatch
43 43 # Now, proceed to import nose itself
44 44 import nose.plugins.builtin
45 45 from nose.core import TestProgram
46 46
47 47 # Our own imports
48 48 from IPython.utils.path import get_ipython_module_path
49 49 from IPython.utils.process import find_cmd, pycmd2argv
50 50 from IPython.utils.sysinfo import sys_info
51 51
52 52 from IPython.testing import globalipapp
53 53 from IPython.testing.plugin.ipdoctest import IPythonDoctest
54 54 from IPython.external.decorators import KnownFailure
55 55
56 56 pjoin = path.join
57 57
58 58
59 59 #-----------------------------------------------------------------------------
60 60 # Globals
61 61 #-----------------------------------------------------------------------------
62 62
63 63
64 64 #-----------------------------------------------------------------------------
65 65 # Warnings control
66 66 #-----------------------------------------------------------------------------
67 67
68 68 # Twisted generates annoying warnings with Python 2.6, as will do other code
69 69 # that imports 'sets' as of today
70 70 warnings.filterwarnings('ignore', 'the sets module is deprecated',
71 71 DeprecationWarning )
72 72
73 73 # This one also comes from Twisted
74 74 warnings.filterwarnings('ignore', 'the sha module is deprecated',
75 75 DeprecationWarning)
76 76
77 77 # Wx on Fedora11 spits these out
78 78 warnings.filterwarnings('ignore', 'wxPython/wxWidgets release number mismatch',
79 79 UserWarning)
80 80
81 81 #-----------------------------------------------------------------------------
82 82 # Logic for skipping doctests
83 83 #-----------------------------------------------------------------------------
84 84
85 85 def test_for(mod, min_version=None):
86 86 """Test to see if mod is importable."""
87 87 try:
88 88 __import__(mod)
89 89 except (ImportError, RuntimeError):
90 90 # GTK reports Runtime error if it can't be initialized even if it's
91 91 # importable.
92 92 return False
93 93 else:
94 94 if min_version:
95 95 return sys.modules[mod].__version__ >= min_version
96 96 else:
97 97 return True
98 98
99 99 # Global dict where we can store information on what we have and what we don't
100 100 # have available at test run time
101 101 have = {}
102 102
103 103 have['curses'] = test_for('_curses')
104 104 have['wx'] = test_for('wx')
105 105 have['wx.aui'] = test_for('wx.aui')
106 106 have['pexpect'] = test_for('pexpect')
107 107 have['zmq'] = test_for('zmq', '2.0.10')
108 108
109 109 #-----------------------------------------------------------------------------
110 110 # Functions and classes
111 111 #-----------------------------------------------------------------------------
112 112
113 113 def report():
114 114 """Return a string with a summary report of test-related variables."""
115 115
116 116 out = [ sys_info(), '\n']
117 117
118 118 avail = []
119 119 not_avail = []
120 120
121 121 for k, is_avail in have.items():
122 122 if is_avail:
123 123 avail.append(k)
124 124 else:
125 125 not_avail.append(k)
126 126
127 127 if avail:
128 128 out.append('\nTools and libraries available at test time:\n')
129 129 avail.sort()
130 130 out.append(' ' + ' '.join(avail)+'\n')
131 131
132 132 if not_avail:
133 133 out.append('\nTools and libraries NOT available at test time:\n')
134 134 not_avail.sort()
135 135 out.append(' ' + ' '.join(not_avail)+'\n')
136 136
137 137 return ''.join(out)
138 138
139 139
140 140 def make_exclude():
141 141 """Make patterns of modules and packages to exclude from testing.
142 142
143 143 For the IPythonDoctest plugin, we need to exclude certain patterns that
144 144 cause testing problems. We should strive to minimize the number of
145 145 skipped modules, since this means untested code.
146 146
147 147 These modules and packages will NOT get scanned by nose at all for tests.
148 148 """
149 149 # Simple utility to make IPython paths more readably, we need a lot of
150 150 # these below
151 151 ipjoin = lambda *paths: pjoin('IPython', *paths)
152 152
153 153 exclusions = [ipjoin('external'),
154 154 pjoin('IPython_doctest_plugin'),
155 155 ipjoin('quarantine'),
156 156 ipjoin('deathrow'),
157 157 ipjoin('testing', 'attic'),
158 158 # This guy is probably attic material
159 159 ipjoin('testing', 'mkdoctests'),
160 160 # Testing inputhook will need a lot of thought, to figure out
161 161 # how to have tests that don't lock up with the gui event
162 162 # loops in the picture
163 163 ipjoin('lib', 'inputhook'),
164 164 # Config files aren't really importable stand-alone
165 165 ipjoin('config', 'default'),
166 166 ipjoin('config', 'profile'),
167 167 ]
168 168
169 169 if not have['wx']:
170 170 exclusions.append(ipjoin('lib', 'inputhookwx'))
171 171
172 172 # We do this unconditionally, so that the test suite doesn't import
173 173 # gtk, changing the default encoding and masking some unicode bugs.
174 174 exclusions.append(ipjoin('lib', 'inputhookgtk'))
175 175
176 176 # These have to be skipped on win32 because the use echo, rm, cd, etc.
177 177 # See ticket https://bugs.launchpad.net/bugs/366982
178 178 if sys.platform == 'win32':
179 179 exclusions.append(ipjoin('testing', 'plugin', 'test_exampleip'))
180 180 exclusions.append(ipjoin('testing', 'plugin', 'dtexample'))
181 181
182 182 if not have['pexpect']:
183 183 exclusions.extend([ipjoin('scripts', 'irunner'),
184 184 ipjoin('lib', 'irunner')])
185 185
186 186 if not have['zmq']:
187 187 exclusions.append(ipjoin('zmq'))
188 exclusions.append(ipjoin('parallel'))
188 189
189 190 # This is needed for the reg-exp to match on win32 in the ipdoctest plugin.
190 191 if sys.platform == 'win32':
191 192 exclusions = [s.replace('\\','\\\\') for s in exclusions]
192 193
193 194 return exclusions
194 195
195 196
196 197 class IPTester(object):
197 198 """Call that calls iptest or trial in a subprocess.
198 199 """
199 200 #: string, name of test runner that will be called
200 201 runner = None
201 202 #: list, parameters for test runner
202 203 params = None
203 204 #: list, arguments of system call to be made to call test runner
204 205 call_args = None
205 206 #: list, process ids of subprocesses we start (for cleanup)
206 207 pids = None
207 208
208 209 def __init__(self, runner='iptest', params=None):
209 210 """Create new test runner."""
210 211 p = os.path
211 212 if runner == 'iptest':
212 213 iptest_app = get_ipython_module_path('IPython.testing.iptest')
213 214 self.runner = pycmd2argv(iptest_app) + sys.argv[1:]
214 215 else:
215 216 raise Exception('Not a valid test runner: %s' % repr(runner))
216 217 if params is None:
217 218 params = []
218 219 if isinstance(params, str):
219 220 params = [params]
220 221 self.params = params
221 222
222 223 # Assemble call
223 224 self.call_args = self.runner+self.params
224 225
225 226 # Store pids of anything we start to clean up on deletion, if possible
226 227 # (on posix only, since win32 has no os.kill)
227 228 self.pids = []
228 229
229 230 if sys.platform == 'win32':
230 231 def _run_cmd(self):
231 232 # On Windows, use os.system instead of subprocess.call, because I
232 233 # was having problems with subprocess and I just don't know enough
233 234 # about win32 to debug this reliably. Os.system may be the 'old
234 235 # fashioned' way to do it, but it works just fine. If someone
235 236 # later can clean this up that's fine, as long as the tests run
236 237 # reliably in win32.
237 238 # What types of problems are you having. They may be related to
238 239 # running Python in unboffered mode. BG.
239 240 return os.system(' '.join(self.call_args))
240 241 else:
241 242 def _run_cmd(self):
242 243 # print >> sys.stderr, '*** CMD:', ' '.join(self.call_args) # dbg
243 244 subp = subprocess.Popen(self.call_args)
244 245 self.pids.append(subp.pid)
245 246 # If this fails, the pid will be left in self.pids and cleaned up
246 247 # later, but if the wait call succeeds, then we can clear the
247 248 # stored pid.
248 249 retcode = subp.wait()
249 250 self.pids.pop()
250 251 return retcode
251 252
252 253 def run(self):
253 254 """Run the stored commands"""
254 255 try:
255 256 return self._run_cmd()
256 257 except:
257 258 import traceback
258 259 traceback.print_exc()
259 260 return 1 # signal failure
260 261
261 262 def __del__(self):
262 263 """Cleanup on exit by killing any leftover processes."""
263 264
264 265 if not hasattr(os, 'kill'):
265 266 return
266 267
267 268 for pid in self.pids:
268 269 try:
269 270 print 'Cleaning stale PID:', pid
270 271 os.kill(pid, signal.SIGKILL)
271 272 except OSError:
272 273 # This is just a best effort, if we fail or the process was
273 274 # really gone, ignore it.
274 275 pass
275 276
276 277
277 278 def make_runners():
278 279 """Define the top-level packages that need to be tested.
279 280 """
280 281
281 282 # Packages to be tested via nose, that only depend on the stdlib
282 283 nose_pkg_names = ['config', 'core', 'extensions', 'frontend', 'lib',
283 284 'scripts', 'testing', 'utils' ]
284 285
285 286 # For debugging this code, only load quick stuff
286 287 #nose_pkg_names = ['core', 'extensions'] # dbg
287 288
288 289 # Make fully qualified package names prepending 'IPython.' to our name lists
289 290 nose_packages = ['IPython.%s' % m for m in nose_pkg_names ]
290 291
291 292 # Make runners
292 293 runners = [ (v, IPTester('iptest', params=v)) for v in nose_packages ]
293 294
294 295 return runners
295 296
296 297
297 298 def run_iptest():
298 299 """Run the IPython test suite using nose.
299 300
300 301 This function is called when this script is **not** called with the form
301 302 `iptest all`. It simply calls nose with appropriate command line flags
302 303 and accepts all of the standard nose arguments.
303 304 """
304 305
305 306 warnings.filterwarnings('ignore',
306 307 'This will be removed soon. Use IPython.testing.util instead')
307 308
308 309 argv = sys.argv + [ '--detailed-errors', # extra info in tracebacks
309 310
310 311 # Loading ipdoctest causes problems with Twisted, but
311 312 # our test suite runner now separates things and runs
312 313 # all Twisted tests with trial.
313 314 '--with-ipdoctest',
314 315 '--ipdoctest-tests','--ipdoctest-extension=txt',
315 316
316 317 # We add --exe because of setuptools' imbecility (it
317 318 # blindly does chmod +x on ALL files). Nose does the
318 319 # right thing and it tries to avoid executables,
319 320 # setuptools unfortunately forces our hand here. This
320 321 # has been discussed on the distutils list and the
321 322 # setuptools devs refuse to fix this problem!
322 323 '--exe',
323 324 ]
324 325
325 326 if nose.__version__ >= '0.11':
326 327 # I don't fully understand why we need this one, but depending on what
327 328 # directory the test suite is run from, if we don't give it, 0 tests
328 329 # get run. Specifically, if the test suite is run from the source dir
329 330 # with an argument (like 'iptest.py IPython.core', 0 tests are run,
330 331 # even if the same call done in this directory works fine). It appears
331 332 # that if the requested package is in the current dir, nose bails early
332 333 # by default. Since it's otherwise harmless, leave it in by default
333 334 # for nose >= 0.11, though unfortunately nose 0.10 doesn't support it.
334 335 argv.append('--traverse-namespace')
335 336
336 337 # Construct list of plugins, omitting the existing doctest plugin, which
337 338 # ours replaces (and extends).
338 339 plugins = [IPythonDoctest(make_exclude()), KnownFailure()]
339 340 for p in nose.plugins.builtin.plugins:
340 341 plug = p()
341 342 if plug.name == 'doctest':
342 343 continue
343 344 plugins.append(plug)
344 345
345 346 # We need a global ipython running in this process
346 347 globalipapp.start_ipython()
347 348 # Now nose can run
348 349 TestProgram(argv=argv, plugins=plugins)
349 350
350 351
351 352 def run_iptestall():
352 353 """Run the entire IPython test suite by calling nose and trial.
353 354
354 355 This function constructs :class:`IPTester` instances for all IPython
355 356 modules and package and then runs each of them. This causes the modules
356 357 and packages of IPython to be tested each in their own subprocess using
357 358 nose or twisted.trial appropriately.
358 359 """
359 360
360 361 runners = make_runners()
361 362
362 363 # Run the test runners in a temporary dir so we can nuke it when finished
363 364 # to clean up any junk files left over by accident. This also makes it
364 365 # robust against being run in non-writeable directories by mistake, as the
365 366 # temp dir will always be user-writeable.
366 367 curdir = os.getcwd()
367 368 testdir = tempfile.gettempdir()
368 369 os.chdir(testdir)
369 370
370 371 # Run all test runners, tracking execution time
371 372 failed = []
372 373 t_start = time.time()
373 374 try:
374 375 for (name, runner) in runners:
375 376 print '*'*70
376 377 print 'IPython test group:',name
377 378 res = runner.run()
378 379 if res:
379 380 failed.append( (name, runner) )
380 381 finally:
381 382 os.chdir(curdir)
382 383 t_end = time.time()
383 384 t_tests = t_end - t_start
384 385 nrunners = len(runners)
385 386 nfail = len(failed)
386 387 # summarize results
387 388 print
388 389 print '*'*70
389 390 print 'Test suite completed for system with the following information:'
390 391 print report()
391 392 print 'Ran %s test groups in %.3fs' % (nrunners, t_tests)
392 393 print
393 394 print 'Status:'
394 395 if not failed:
395 396 print 'OK'
396 397 else:
397 398 # If anything went wrong, point out what command to rerun manually to
398 399 # see the actual errors and individual summary
399 400 print 'ERROR - %s out of %s test groups failed.' % (nfail, nrunners)
400 401 for name, failed_runner in failed:
401 402 print '-'*40
402 403 print 'Runner failed:',name
403 404 print 'You may wish to rerun this one individually, with:'
404 405 print ' '.join(failed_runner.call_args)
405 406 print
406 407
407 408
408 409 def main():
409 410 for arg in sys.argv[1:]:
410 411 if arg.startswith('IPython'):
411 412 # This is in-process
412 413 run_iptest()
413 414 else:
414 415 # This starts subprocesses
415 416 run_iptestall()
416 417
417 418
418 419 if __name__ == '__main__':
419 420 main()
@@ -1,34 +1,33 b''
1 1 =====================
2 2 IPython Documentation
3 3 =====================
4 4
5 5 .. htmlonly::
6 6
7 7 :Release: |release|
8 8 :Date: |today|
9 9
10 10 Welcome to the official IPython documentation.
11 11
12 12 Contents
13 13 ========
14 14
15 15 .. toctree::
16 16 :maxdepth: 1
17 17
18 18 overview.txt
19 19 whatsnew/index.txt
20 20 install/index.txt
21 21 interactive/index.txt
22 .. parallel/index.txt
23 parallelz/index.txt
22 parallel/index.txt
24 23 config/index.txt
25 24 development/index.txt
26 25 api/index.txt
27 26 faq.txt
28 27 about/index.txt
29 28
30 29 .. htmlonly::
31 30 * :ref:`genindex`
32 31 * :ref:`modindex`
33 32 * :ref:`search`
34 33
@@ -1,387 +1,328 b''
1 1 Overview
2 2 ========
3 3
4 4 This document describes the steps required to install IPython. IPython is
5 5 organized into a number of subpackages, each of which has its own dependencies.
6 6 All of the subpackages come with IPython, so you don't need to download and
7 7 install them separately. However, to use a given subpackage, you will need to
8 8 install all of its dependencies.
9 9
10 10
11 11 Please let us know if you have problems installing IPython or any of its
12 dependencies. Officially, IPython requires Python version 2.5 or 2.6. We
13 have *not* yet started to port IPython to Python 3.0.
12 dependencies. Officially, IPython requires Python version 2.6 or 2.7. There
13 is an experimental port of IPython for Python3 `on GitHub
14 <https://github.com/ipython/ipython-py3k>`_
14 15
15 16 .. warning::
16 17
17 Officially, IPython supports Python versions 2.5 and 2.6.
18 Officially, IPython supports Python versions 2.6 and 2.7.
18 19
19 IPython 0.10 has only been well tested with Python 2.5 and 2.6. Parts of
20 it may work with Python 2.4, but we do not officially support Python 2.4
21 anymore. If you need to use 2.4, you can still run IPython 0.9.
20 IPython 0.11 has a hard syntax dependency on 2.6, and will no longer work
21 on Python <= 2.5.
22 22
23 23 Some of the installation approaches use the :mod:`setuptools` package and its
24 24 :command:`easy_install` command line program. In many scenarios, this provides
25 25 the most simple method of installing IPython and its dependencies. It is not
26 26 required though. More information about :mod:`setuptools` can be found on its
27 27 website.
28 28
29 29 More general information about installing Python packages can be found in
30 30 Python's documentation at http://www.python.org/doc/.
31 31
32 32 Quickstart
33 33 ==========
34 34
35 35 If you have :mod:`setuptools` installed and you are on OS X or Linux (not
36 36 Windows), the following will download and install IPython *and* the main
37 37 optional dependencies:
38 38
39 39 .. code-block:: bash
40 40
41 $ easy_install ipython[kernel,security,test]
41 $ easy_install ipython[zmq,test]
42 42
43 This will get Twisted, zope.interface and Foolscap, which are needed for
43 This will get pyzmq, which is needed for
44 44 IPython's parallel computing features as well as the nose package, which will
45 45 enable you to run IPython's test suite.
46 46
47 47 .. warning::
48 48
49 49 IPython's test system is being refactored and currently the
50 50 :command:`iptest` shown below does not work. More details about the
51 51 testing situation can be found :ref:`here <testing>`
52 52
53 53 To run IPython's test suite, use the :command:`iptest` command:
54 54
55 55 .. code-block:: bash
56 56
57 57 $ iptest
58 58
59 59 Read on for more specific details and instructions for Windows.
60 60
61 61 Installing IPython itself
62 62 =========================
63 63
64 64 Given a properly built Python, the basic interactive IPython shell will work
65 65 with no external dependencies. However, some Python distributions
66 66 (particularly on Windows and OS X), don't come with a working :mod:`readline`
67 67 module. The IPython shell will work without :mod:`readline`, but will lack
68 68 many features that users depend on, such as tab completion and command line
69 69 editing. See below for details of how to make sure you have a working
70 70 :mod:`readline`.
71 71
72 72 Installation using easy_install
73 73 -------------------------------
74 74
75 75 If you have :mod:`setuptools` installed, the easiest way of getting IPython is
76 76 to simple use :command:`easy_install`:
77 77
78 78 .. code-block:: bash
79 79
80 80 $ easy_install ipython
81 81
82 82 That's it.
83 83
84 84 Installation from source
85 85 ------------------------
86 86
87 87 If you don't want to use :command:`easy_install`, or don't have it installed,
88 88 just grab the latest stable build of IPython from `here
89 89 <http://ipython.scipy.org/dist/>`_. Then do the following:
90 90
91 91 .. code-block:: bash
92 92
93 93 $ tar -xzf ipython.tar.gz
94 94 $ cd ipython
95 95 $ python setup.py install
96 96
97 97 If you are installing to a location (like ``/usr/local``) that requires higher
98 98 permissions, you may need to run the last command with :command:`sudo`.
99 99
100 100 Windows
101 101 -------
102 102
103 103 There are a few caveats for Windows users. The main issue is that a basic
104 104 ``python setup.py install`` approach won't create ``.bat`` file or Start Menu
105 105 shortcuts, which most users want. To get an installation with these, you can
106 106 use any of the following alternatives:
107 107
108 108 1. Install using :command:`easy_install`.
109 109
110 110 2. Install using our binary ``.exe`` Windows installer, which can be found at
111 111 `here <http://ipython.scipy.org/dist/>`_
112 112
113 113 3. Install from source, but using :mod:`setuptools` (``python setupegg.py
114 114 install``).
115 115
116 116 IPython by default runs in a terminal window, but the normal terminal
117 117 application supplied by Microsoft Windows is very primitive. You may want to
118 118 download the excellent and free Console_ application instead, which is a far
119 119 superior tool. You can even configure Console to give you by default an
120 120 IPython tab, which is very convenient to create new IPython sessions directly
121 121 from the working terminal.
122 122
123 123 .. _Console: http://sourceforge.net/projects/console
124 124
125 125 Note for Windows 64 bit users: you may have difficulties with the stock
126 126 installer on 64 bit systems; in this case (since we currently do not have 64
127 127 bit builds of the Windows installer) your best bet is to install from source
128 128 with the setuptools method indicated in #3 above. See `this bug report`_ for
129 129 further details.
130 130
131 131 .. _this bug report: https://bugs.launchpad.net/ipython/+bug/382214
132 132
133 133
134 134 Installing the development version
135 135 ----------------------------------
136 136
137 137 It is also possible to install the development version of IPython from our
138 138 `Bazaar <http://bazaar-vcs.org/>`_ source code repository. To do this you will
139 139 need to have Bazaar installed on your system. Then just do:
140 140
141 141 .. code-block:: bash
142 142
143 143 $ bzr branch lp:ipython
144 144 $ cd ipython
145 145 $ python setup.py install
146 146
147 147 Again, this last step on Windows won't create ``.bat`` files or Start Menu
148 148 shortcuts, so you will have to use one of the other approaches listed above.
149 149
150 150 Some users want to be able to follow the development branch as it changes. If
151 151 you have :mod:`setuptools` installed, this is easy. Simply replace the last
152 152 step by:
153 153
154 154 .. code-block:: bash
155 155
156 156 $ python setupegg.py develop
157 157
158 158 This creates links in the right places and installs the command line script to
159 159 the appropriate places. Then, if you want to update your IPython at any time,
160 160 just do:
161 161
162 162 .. code-block:: bash
163 163
164 164 $ bzr pull
165 165
166 166 Basic optional dependencies
167 167 ===========================
168 168
169 169 There are a number of basic optional dependencies that most users will want to
170 170 get. These are:
171 171
172 172 * readline (for command line editing, tab completion, etc.)
173 173 * nose (to run the IPython test suite)
174 174 * pexpect (to use things like irunner)
175 175
176 176 If you are comfortable installing these things yourself, have at it, otherwise
177 177 read on for more details.
178 178
179 179 readline
180 180 --------
181 181
182 182 In principle, all Python distributions should come with a working
183 183 :mod:`readline` module. But, reality is not quite that simple. There are two
184 184 common situations where you won't have a working :mod:`readline` module:
185 185
186 186 * If you are using the built-in Python on Mac OS X.
187 187
188 188 * If you are running Windows, which doesn't have a :mod:`readline` module.
189 189
190 190
191 191 On OS X, the built-in Python doesn't not have :mod:`readline` because of
192 192 license issues. Starting with OS X 10.5 (Leopard), Apple's built-in Python has
193 193 a BSD-licensed not-quite-compatible readline replacement. As of IPython 0.9,
194 194 many of the issues related to the differences between readline and libedit seem
195 195 to have been resolved. While you may find libedit sufficient, we have
196 196 occasional reports of bugs with it and several developers who use OS X as their
197 197 main environment consider libedit unacceptable for productive, regular use with
198 198 IPython.
199 199
200 200 Therefore, we *strongly* recommend that on OS X you get the full
201 201 :mod:`readline` module. We will *not* consider completion/history problems to
202 202 be bugs for IPython if you are using libedit.
203 203
204 204 To get a working :mod:`readline` module, just do (with :mod:`setuptools`
205 205 installed):
206 206
207 207 .. code-block:: bash
208 208
209 209 $ easy_install readline
210 210
211 211 .. note::
212 212
213 213 Other Python distributions on OS X (such as fink, MacPorts and the official
214 214 python.org binaries) already have readline installed so you likely don't
215 215 have to do this step.
216 216
217 217 If needed, the readline egg can be build and installed from source (see the
218 218 wiki page at http://ipython.scipy.org/moin/InstallationOSXLeopard).
219 219
220 220 On Windows, you will need the PyReadline module. PyReadline is a separate,
221 221 Windows only implementation of readline that uses native Windows calls through
222 222 :mod:`ctypes`. The easiest way of installing PyReadline is you use the binary
223 223 installer available `here <http://ipython.scipy.org/dist/>`_. The :mod:`ctypes`
224 module, which comes with Python 2.5 and greater, is required by PyReadline. It
225 is available for Python 2.4 at http://python.net/crew/theller/ctypes.
224 module, which comes with Python 2.5 and greater, is required by PyReadline.
226 225
227 226 nose
228 227 ----
229 228
230 229 To run the IPython test suite you will need the :mod:`nose` package. Nose
231 230 provides a great way of sniffing out and running all of the IPython tests. The
232 231 simplest way of getting nose, is to use :command:`easy_install`:
233 232
234 233 .. code-block:: bash
235 234
236 235 $ easy_install nose
237 236
238 237 Another way of getting this is to do:
239 238
240 239 .. code-block:: bash
241 240
242 241 $ easy_install ipython[test]
243 242
244 243 For more installation options, see the `nose website
245 244 <http://somethingaboutorange.com/mrl/projects/nose/>`_.
246 245
247 246 .. warning::
248 247
249 248 As described above, the :command:`iptest` command currently doesn't work.
250 249
251 250 Once you have nose installed, you can run IPython's test suite using the
252 251 iptest command:
253 252
254 253 .. code-block:: bash
255 254
256 255 $ iptest
257 256
258 257 pexpect
259 258 -------
260 259
261 260 The `pexpect <http://www.noah.org/wiki/Pexpect>`_ package is used in IPython's
262 261 :command:`irunner` script. On Unix platforms (including OS X), just do:
263 262
264 263 .. code-block:: bash
265 264
266 265 $ easy_install pexpect
267 266
268 267 Windows users are out of luck as pexpect does not run there.
269 268
270 Dependencies for IPython.kernel (parallel computing)
271 ====================================================
269 Dependencies for IPython.parallel (parallel computing)
270 ======================================================
272 271
273 The IPython kernel provides a nice architecture for parallel computing. The
274 main focus of this architecture is on interactive parallel computing. These
275 features require a number of additional packages:
272 :mod:`IPython.kernel` has been replaced by :mod:`IPython.parallel`,
273 which uses ZeroMQ for all communication.
276 274
277 * zope.interface (yep, we use interfaces)
278 * Twisted (asynchronous networking framework)
279 * Foolscap (a nice, secure network protocol)
280 * pyOpenSSL (security for network connections)
275 IPython.parallel provides a nice architecture for parallel computing. The
276 main focus of this architecture is on interactive parallel computing. These
277 features require just one package: pyzmq. See the next section for pyzmq
278 details.
281 279
282 280 On a Unix style platform (including OS X), if you want to use
283 281 :mod:`setuptools`, you can just do:
284 282
285 283 .. code-block:: bash
286 284
287 $ easy_install ipython[kernel] # the first three
288 $ easy_install ipython[security] # pyOpenSSL
289
290 zope.interface and Twisted
291 --------------------------
292
293 Twisted [Twisted]_ and zope.interface [ZopeInterface]_ are used for networking
294 related things. On Unix style platforms (including OS X), the simplest way of
295 getting the these is to use :command:`easy_install`:
296
297 .. code-block:: bash
298
299 $ easy_install zope.interface
300 $ easy_install Twisted
285 $ easy_install ipython[zmq] # will include pyzmq
301 286
302 Of course, you can also download the source tarballs from the Twisted website
303 [Twisted]_ and the
304 `zope.interface page at PyPI <http://pypi.python.org/pypi/zope.interface>`_
305 and do the usual ``python setup.py install`` if you prefer.
287 Security in IPython.parallel is provided by SSH tunnels. By default, Linux
288 and OSX clients will use the shell ssh command, but on Windows, we also
289 support tunneling with paramiko [paramiko]_.
306 290
307 Windows is a bit different. For zope.interface and Twisted, simply get the
308 latest binary ``.exe`` installer from the Twisted website. This installer
309 includes both zope.interface and Twisted and should just work.
310
311 Foolscap
312 --------
313
314 Foolscap [Foolscap]_ uses Twisted to provide a very nice secure RPC protocol that we use to implement our parallel computing features.
315
316 On all platforms a simple:
317
318 .. code-block:: bash
319
320 $ easy_install foolscap
321
322 should work. You can also download the source tarballs from the `Foolscap
323 website <http://foolscap.lothar.com/trac>`_ and do ``python setup.py install``
324 if you prefer.
325
326 pyOpenSSL
327 ---------
328
329 IPython does not work with version 0.7 of pyOpenSSL [pyOpenSSL]_. It is known
330 to work with version 0.6 and will likely work with the more recent 0.8 and 0.9
331 versions. There are a couple of options for getting this:
332
333 1. Most Linux distributions have packages for pyOpenSSL.
334 2. The built-in Python 2.5 on OS X 10.5 already has it installed.
335 3. There are source tarballs on the pyOpenSSL website. On Unix-like
336 platforms, these can be built using ``python seutp.py install``.
337 4. There is also a binary ``.exe`` Windows installer on the
338 `pyOpenSSL website <http://pyopenssl.sourceforge.net/>`_.
339
340 Dependencies for IPython.frontend (the IPython GUI)
341 ===================================================
342
343 wxPython
344 --------
345
346 Starting with IPython 0.9, IPython has a new :mod:`IPython.frontend` package
347 that has a nice wxPython based IPython GUI. As you would expect, this GUI
348 requires wxPython. Most Linux distributions have wxPython packages available
349 and the built-in Python on OS X comes with wxPython preinstalled. For Windows,
350 a binary installer is available on the `wxPython website
351 <http://www.wxpython.org/>`_.
352
353 Dependencies for IPython.zmq (new parallel)
354 ===========================================
291 Dependencies for IPython.zmq
292 ============================
355 293
356 294 pyzmq
357 295 -----
358 296
359 297 IPython 0.11 introduced some new functionality, including a two-process
360 298 execution model using ZeroMQ for communication [ZeroMQ]_. The Python bindings
361 299 to ZeroMQ are found in the pyzmq project, which is easy_install-able once you
362 have ZeroMQ installed. :mod:`IPython.kernel` is also in the process of being
363 replaced by :mod:`IPython.zmq.parallel`, which uses ZeroMQ for all
364 communication.
300 have ZeroMQ installed (or even if you don't).
301
302 IPython.zmq depends on pyzmq >= 2.0.10.1, but IPython.parallel requires the more
303 recent 2.1.4. 2.1.4 also has binary releases for OSX and Windows, that do not
304 require prior installation of libzmq.
365 305
366 306 Dependencies for ipython-qtconsole (new GUI)
367 307 ============================================
368 308
369 309 PyQt
370 310 ----
371 311
372 312 Also with 0.11, a new GUI was added using the work in :mod:`IPython.zmq`,
373 313 which can be launched with ``ipython-qtconsole``. The GUI is built on PyQt ,
374 314 which can be installed from the
375 315 `PyQt website <http://www.riverbankcomputing.co.uk/>`_.
376 316
377 317 pygments
378 318 --------
379 319
380 The syntax-highlighting in ``ipython-qtconsole`` is done with the pygments project, which is easy_install-able.
320 The syntax-highlighting in ``ipython-qtconsole`` is done with the pygments project,
321 which is easy_install-able.
381 322
382 323 .. [Twisted] Twisted matrix. http://twistedmatrix.org
383 324 .. [ZopeInterface] http://pypi.python.org/pypi/zope.interface
384 325 .. [Foolscap] Foolscap network protocol. http://foolscap.lothar.com/trac
385 326 .. [pyOpenSSL] pyOpenSSL. http://pyopenssl.sourceforge.net
386 327 .. [ZeroMQ] ZeroMQ. http://www.zeromq.org
387
328 .. [paramiko] paramiko. https://github.com/robey/paramiko
1 NO CONTENT: file renamed from docs/source/parallelz/asian_call.pdf to docs/source/parallel/asian_call.pdf
1 NO CONTENT: file renamed from docs/source/parallelz/asian_call.png to docs/source/parallel/asian_call.png
1 NO CONTENT: file renamed from docs/source/parallelz/asian_put.pdf to docs/source/parallel/asian_put.pdf
1 NO CONTENT: file renamed from docs/source/parallelz/asian_put.png to docs/source/parallel/asian_put.png
1 NO CONTENT: file renamed from docs/source/parallelz/dag_dependencies.txt to docs/source/parallel/dag_dependencies.txt
1 NO CONTENT: file renamed from docs/source/parallelz/dagdeps.pdf to docs/source/parallel/dagdeps.pdf
1 NO CONTENT: file renamed from docs/source/parallelz/dagdeps.png to docs/source/parallel/dagdeps.png
1 NO CONTENT: file renamed from docs/source/parallelz/hpc_job_manager.pdf to docs/source/parallel/hpc_job_manager.pdf
1 NO CONTENT: file renamed from docs/source/parallelz/hpc_job_manager.png to docs/source/parallel/hpc_job_manager.png
@@ -1,12 +1,22 b''
1 1 .. _parallel_index:
2 2
3 3 ====================================
4 4 Using IPython for parallel computing
5 5 ====================================
6 6
7 The twisted-based :mod:`IPython.kernel` has been removed, in favor of
8 the new 0MQ-based :mod:`IPython.parallel`, whose merge into master is imminent.
7 .. toctree::
8 :maxdepth: 2
9
10 parallel_intro.txt
11 parallel_process.txt
12 parallel_multiengine.txt
13 parallel_task.txt
14 parallel_mpi.txt
15 parallel_security.txt
16 parallel_winhpc.txt
17 parallel_demos.txt
18 dag_dependencies.txt
19 parallel_details.txt
20 parallel_transition.txt
21
9 22
10 Until that code is merged, it can be found in the `newparallel branch
11 <https://github.com/ipython/ipython/tree/newparallel>`_, and its draft documentation can be
12 found `here <http://minrk.github.com/ipython-doc/newparallel>`_. No newline at end of file
1 NO CONTENT: file renamed from docs/source/parallelz/ipcluster_create.pdf to docs/source/parallel/ipcluster_create.pdf
1 NO CONTENT: file renamed from docs/source/parallelz/ipcluster_create.png to docs/source/parallel/ipcluster_create.png
1 NO CONTENT: file renamed from docs/source/parallelz/ipcluster_start.pdf to docs/source/parallel/ipcluster_start.pdf
1 NO CONTENT: file renamed from docs/source/parallelz/ipcluster_start.png to docs/source/parallel/ipcluster_start.png
1 NO CONTENT: file renamed from docs/source/parallelz/ipython_shell.pdf to docs/source/parallel/ipython_shell.pdf
1 NO CONTENT: file renamed from docs/source/parallelz/ipython_shell.png to docs/source/parallel/ipython_shell.png
1 NO CONTENT: file renamed from docs/source/parallelz/mec_simple.pdf to docs/source/parallel/mec_simple.pdf
1 NO CONTENT: file renamed from docs/source/parallelz/mec_simple.png to docs/source/parallel/mec_simple.png
@@ -1,284 +1,284 b''
1 1 =================
2 2 Parallel examples
3 3 =================
4 4
5 5 .. note::
6 6
7 7 Performance numbers from ``IPython.kernel``, not newparallel.
8 8
9 9 In this section we describe two more involved examples of using an IPython
10 10 cluster to perform a parallel computation. In these examples, we will be using
11 11 IPython's "pylab" mode, which enables interactive plotting using the
12 12 Matplotlib package. IPython can be started in this mode by typing::
13 13
14 14 ipython --pylab
15 15
16 16 at the system command line.
17 17
18 18 150 million digits of pi
19 19 ========================
20 20
21 21 In this example we would like to study the distribution of digits in the
22 22 number pi (in base 10). While it is not known if pi is a normal number (a
23 23 number is normal in base 10 if 0-9 occur with equal likelihood) numerical
24 24 investigations suggest that it is. We will begin with a serial calculation on
25 25 10,000 digits of pi and then perform a parallel calculation involving 150
26 26 million digits.
27 27
28 28 In both the serial and parallel calculation we will be using functions defined
29 29 in the :file:`pidigits.py` file, which is available in the
30 30 :file:`docs/examples/newparallel` directory of the IPython source distribution.
31 31 These functions provide basic facilities for working with the digits of pi and
32 32 can be loaded into IPython by putting :file:`pidigits.py` in your current
33 33 working directory and then doing:
34 34
35 35 .. sourcecode:: ipython
36 36
37 37 In [1]: run pidigits.py
38 38
39 39 Serial calculation
40 40 ------------------
41 41
42 42 For the serial calculation, we will use `SymPy <http://www.sympy.org>`_ to
43 43 calculate 10,000 digits of pi and then look at the frequencies of the digits
44 44 0-9. Out of 10,000 digits, we expect each digit to occur 1,000 times. While
45 45 SymPy is capable of calculating many more digits of pi, our purpose here is to
46 46 set the stage for the much larger parallel calculation.
47 47
48 48 In this example, we use two functions from :file:`pidigits.py`:
49 49 :func:`one_digit_freqs` (which calculates how many times each digit occurs)
50 50 and :func:`plot_one_digit_freqs` (which uses Matplotlib to plot the result).
51 51 Here is an interactive IPython session that uses these functions with
52 52 SymPy:
53 53
54 54 .. sourcecode:: ipython
55 55
56 56 In [7]: import sympy
57 57
58 58 In [8]: pi = sympy.pi.evalf(40)
59 59
60 60 In [9]: pi
61 61 Out[9]: 3.141592653589793238462643383279502884197
62 62
63 63 In [10]: pi = sympy.pi.evalf(10000)
64 64
65 65 In [11]: digits = (d for d in str(pi)[2:]) # create a sequence of digits
66 66
67 67 In [12]: run pidigits.py # load one_digit_freqs/plot_one_digit_freqs
68 68
69 69 In [13]: freqs = one_digit_freqs(digits)
70 70
71 71 In [14]: plot_one_digit_freqs(freqs)
72 72 Out[14]: [<matplotlib.lines.Line2D object at 0x18a55290>]
73 73
74 74 The resulting plot of the single digit counts shows that each digit occurs
75 75 approximately 1,000 times, but that with only 10,000 digits the
76 76 statistical fluctuations are still rather large:
77 77
78 78 .. image:: single_digits.*
79 79
80 80 It is clear that to reduce the relative fluctuations in the counts, we need
81 81 to look at many more digits of pi. That brings us to the parallel calculation.
82 82
83 83 Parallel calculation
84 84 --------------------
85 85
86 86 Calculating many digits of pi is a challenging computational problem in itself.
87 87 Because we want to focus on the distribution of digits in this example, we
88 88 will use pre-computed digit of pi from the website of Professor Yasumasa
89 89 Kanada at the University of Tokyo (http://www.super-computing.org). These
90 90 digits come in a set of text files (ftp://pi.super-computing.org/.2/pi200m/)
91 91 that each have 10 million digits of pi.
92 92
93 93 For the parallel calculation, we have copied these files to the local hard
94 94 drives of the compute nodes. A total of 15 of these files will be used, for a
95 95 total of 150 million digits of pi. To make things a little more interesting we
96 96 will calculate the frequencies of all 2 digits sequences (00-99) and then plot
97 97 the result using a 2D matrix in Matplotlib.
98 98
99 99 The overall idea of the calculation is simple: each IPython engine will
100 100 compute the two digit counts for the digits in a single file. Then in a final
101 101 step the counts from each engine will be added up. To perform this
102 102 calculation, we will need two top-level functions from :file:`pidigits.py`:
103 103
104 104 .. literalinclude:: ../../examples/newparallel/pidigits.py
105 105 :language: python
106 106 :lines: 41-56
107 107
108 108 We will also use the :func:`plot_two_digit_freqs` function to plot the
109 109 results. The code to run this calculation in parallel is contained in
110 110 :file:`docs/examples/newparallel/parallelpi.py`. This code can be run in parallel
111 111 using IPython by following these steps:
112 112
113 1. Use :command:`ipclusterz` to start 15 engines. We used an 8 core (2 quad
113 1. Use :command:`ipcluster` to start 15 engines. We used an 8 core (2 quad
114 114 core CPUs) cluster with hyperthreading enabled which makes the 8 cores
115 115 looks like 16 (1 controller + 15 engines) in the OS. However, the maximum
116 116 speedup we can observe is still only 8x.
117 117 2. With the file :file:`parallelpi.py` in your current working directory, open
118 118 up IPython in pylab mode and type ``run parallelpi.py``. This will download
119 119 the pi files via ftp the first time you run it, if they are not
120 120 present in the Engines' working directory.
121 121
122 122 When run on our 8 core cluster, we observe a speedup of 7.7x. This is slightly
123 123 less than linear scaling (8x) because the controller is also running on one of
124 124 the cores.
125 125
126 126 To emphasize the interactive nature of IPython, we now show how the
127 127 calculation can also be run by simply typing the commands from
128 128 :file:`parallelpi.py` interactively into IPython:
129 129
130 130 .. sourcecode:: ipython
131 131
132 132 In [1]: from IPython.parallel import Client
133 133
134 134 # The Client allows us to use the engines interactively.
135 135 # We simply pass Client the name of the cluster profile we
136 136 # are using.
137 137 In [2]: c = Client(profile='mycluster')
138 138 In [3]: view = c.load_balanced_view()
139 139
140 140 In [3]: c.ids
141 141 Out[3]: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
142 142
143 143 In [4]: run pidigits.py
144 144
145 145 In [5]: filestring = 'pi200m.ascii.%(i)02dof20'
146 146
147 147 # Create the list of files to process.
148 148 In [6]: files = [filestring % {'i':i} for i in range(1,16)]
149 149
150 150 In [7]: files
151 151 Out[7]:
152 152 ['pi200m.ascii.01of20',
153 153 'pi200m.ascii.02of20',
154 154 'pi200m.ascii.03of20',
155 155 'pi200m.ascii.04of20',
156 156 'pi200m.ascii.05of20',
157 157 'pi200m.ascii.06of20',
158 158 'pi200m.ascii.07of20',
159 159 'pi200m.ascii.08of20',
160 160 'pi200m.ascii.09of20',
161 161 'pi200m.ascii.10of20',
162 162 'pi200m.ascii.11of20',
163 163 'pi200m.ascii.12of20',
164 164 'pi200m.ascii.13of20',
165 165 'pi200m.ascii.14of20',
166 166 'pi200m.ascii.15of20']
167 167
168 168 # download the data files if they don't already exist:
169 169 In [8]: v.map(fetch_pi_file, files)
170 170
171 171 # This is the parallel calculation using the Client.map method
172 172 # which applies compute_two_digit_freqs to each file in files in parallel.
173 173 In [9]: freqs_all = v.map(compute_two_digit_freqs, files)
174 174
175 175 # Add up the frequencies from each engine.
176 176 In [10]: freqs = reduce_freqs(freqs_all)
177 177
178 178 In [11]: plot_two_digit_freqs(freqs)
179 179 Out[11]: <matplotlib.image.AxesImage object at 0x18beb110>
180 180
181 181 In [12]: plt.title('2 digit counts of 150m digits of pi')
182 182 Out[12]: <matplotlib.text.Text object at 0x18d1f9b0>
183 183
184 184 The resulting plot generated by Matplotlib is shown below. The colors indicate
185 185 which two digit sequences are more (red) or less (blue) likely to occur in the
186 186 first 150 million digits of pi. We clearly see that the sequence "41" is
187 187 most likely and that "06" and "07" are least likely. Further analysis would
188 188 show that the relative size of the statistical fluctuations have decreased
189 189 compared to the 10,000 digit calculation.
190 190
191 191 .. image:: two_digit_counts.*
192 192
193 193
194 194 Parallel options pricing
195 195 ========================
196 196
197 197 An option is a financial contract that gives the buyer of the contract the
198 198 right to buy (a "call") or sell (a "put") a secondary asset (a stock for
199 199 example) at a particular date in the future (the expiration date) for a
200 200 pre-agreed upon price (the strike price). For this right, the buyer pays the
201 201 seller a premium (the option price). There are a wide variety of flavors of
202 202 options (American, European, Asian, etc.) that are useful for different
203 203 purposes: hedging against risk, speculation, etc.
204 204
205 205 Much of modern finance is driven by the need to price these contracts
206 206 accurately based on what is known about the properties (such as volatility) of
207 207 the underlying asset. One method of pricing options is to use a Monte Carlo
208 208 simulation of the underlying asset price. In this example we use this approach
209 209 to price both European and Asian (path dependent) options for various strike
210 210 prices and volatilities.
211 211
212 212 The code for this example can be found in the :file:`docs/examples/newparallel`
213 213 directory of the IPython source. The function :func:`price_options` in
214 214 :file:`mcpricer.py` implements the basic Monte Carlo pricing algorithm using
215 215 the NumPy package and is shown here:
216 216
217 217 .. literalinclude:: ../../examples/newparallel/mcpricer.py
218 218 :language: python
219 219
220 220 To run this code in parallel, we will use IPython's :class:`LoadBalancedView` class,
221 221 which distributes work to the engines using dynamic load balancing. This
222 222 view is a wrapper of the :class:`Client` class shown in
223 223 the previous example. The parallel calculation using :class:`LoadBalancedView` can
224 224 be found in the file :file:`mcpricer.py`. The code in this file creates a
225 225 :class:`TaskClient` instance and then submits a set of tasks using
226 226 :meth:`TaskClient.run` that calculate the option prices for different
227 227 volatilities and strike prices. The results are then plotted as a 2D contour
228 228 plot using Matplotlib.
229 229
230 230 .. literalinclude:: ../../examples/newparallel/mcdriver.py
231 231 :language: python
232 232
233 To use this code, start an IPython cluster using :command:`ipclusterz`, open
233 To use this code, start an IPython cluster using :command:`ipcluster`, open
234 234 IPython in the pylab mode with the file :file:`mcdriver.py` in your current
235 235 working directory and then type:
236 236
237 237 .. sourcecode:: ipython
238 238
239 239 In [7]: run mcdriver.py
240 240 Submitted tasks: [0, 1, 2, ...]
241 241
242 242 Once all the tasks have finished, the results can be plotted using the
243 243 :func:`plot_options` function. Here we make contour plots of the Asian
244 244 call and Asian put options as function of the volatility and strike price:
245 245
246 246 .. sourcecode:: ipython
247 247
248 248 In [8]: plot_options(sigma_vals, K_vals, prices['acall'])
249 249
250 250 In [9]: plt.figure()
251 251 Out[9]: <matplotlib.figure.Figure object at 0x18c178d0>
252 252
253 253 In [10]: plot_options(sigma_vals, K_vals, prices['aput'])
254 254
255 255 These results are shown in the two figures below. On a 8 core cluster the
256 256 entire calculation (10 strike prices, 10 volatilities, 100,000 paths for each)
257 257 took 30 seconds in parallel, giving a speedup of 7.7x, which is comparable
258 258 to the speedup observed in our previous example.
259 259
260 260 .. image:: asian_call.*
261 261
262 262 .. image:: asian_put.*
263 263
264 264 Conclusion
265 265 ==========
266 266
267 267 To conclude these examples, we summarize the key features of IPython's
268 268 parallel architecture that have been demonstrated:
269 269
270 270 * Serial code can be parallelized often with only a few extra lines of code.
271 271 We have used the :class:`DirectView` and :class:`LoadBalancedView` classes
272 272 for this purpose.
273 273 * The resulting parallel code can be run without ever leaving the IPython's
274 274 interactive shell.
275 275 * Any data computed in parallel can be explored interactively through
276 276 visualization or further numerical calculations.
277 277 * We have run these examples on a cluster running Windows HPC Server 2008.
278 278 IPython's built in support for the Windows HPC job scheduler makes it
279 279 easy to get started with IPython's parallel capabilities.
280 280
281 281 .. note::
282 282
283 283 The newparallel code has never been run on Windows HPC Server, so the last
284 284 conclusion is untested.
1 NO CONTENT: file renamed from docs/source/parallelz/parallel_details.txt to docs/source/parallel/parallel_details.txt
@@ -1,253 +1,253 b''
1 1 .. _ip1par:
2 2
3 3 ============================
4 4 Overview and getting started
5 5 ============================
6 6
7 7 Introduction
8 8 ============
9 9
10 10 This section gives an overview of IPython's sophisticated and powerful
11 11 architecture for parallel and distributed computing. This architecture
12 12 abstracts out parallelism in a very general way, which enables IPython to
13 13 support many different styles of parallelism including:
14 14
15 15 * Single program, multiple data (SPMD) parallelism.
16 16 * Multiple program, multiple data (MPMD) parallelism.
17 17 * Message passing using MPI.
18 18 * Task farming.
19 19 * Data parallel.
20 20 * Combinations of these approaches.
21 21 * Custom user defined approaches.
22 22
23 23 Most importantly, IPython enables all types of parallel applications to
24 24 be developed, executed, debugged and monitored *interactively*. Hence,
25 25 the ``I`` in IPython. The following are some example usage cases for IPython:
26 26
27 27 * Quickly parallelize algorithms that are embarrassingly parallel
28 28 using a number of simple approaches. Many simple things can be
29 29 parallelized interactively in one or two lines of code.
30 30
31 31 * Steer traditional MPI applications on a supercomputer from an
32 32 IPython session on your laptop.
33 33
34 34 * Analyze and visualize large datasets (that could be remote and/or
35 35 distributed) interactively using IPython and tools like
36 36 matplotlib/TVTK.
37 37
38 38 * Develop, test and debug new parallel algorithms
39 39 (that may use MPI) interactively.
40 40
41 41 * Tie together multiple MPI jobs running on different systems into
42 42 one giant distributed and parallel system.
43 43
44 44 * Start a parallel job on your cluster and then have a remote
45 45 collaborator connect to it and pull back data into their
46 46 local IPython session for plotting and analysis.
47 47
48 48 * Run a set of tasks on a set of CPUs using dynamic load balancing.
49 49
50 50 Architecture overview
51 51 =====================
52 52
53 53 The IPython architecture consists of four components:
54 54
55 55 * The IPython engine.
56 56 * The IPython hub.
57 57 * The IPython schedulers.
58 58 * The controller client.
59 59
60 60 These components live in the :mod:`IPython.parallel` package and are
61 61 installed with IPython. They do, however, have additional dependencies
62 62 that must be installed. For more information, see our
63 63 :ref:`installation documentation <install_index>`.
64 64
65 65 .. TODO: include zmq in install_index
66 66
67 67 IPython engine
68 68 ---------------
69 69
70 70 The IPython engine is a Python instance that takes Python commands over a
71 71 network connection. Eventually, the IPython engine will be a full IPython
72 72 interpreter, but for now, it is a regular Python interpreter. The engine
73 73 can also handle incoming and outgoing Python objects sent over a network
74 74 connection. When multiple engines are started, parallel and distributed
75 75 computing becomes possible. An important feature of an IPython engine is
76 76 that it blocks while user code is being executed. Read on for how the
77 77 IPython controller solves this problem to expose a clean asynchronous API
78 78 to the user.
79 79
80 80 IPython controller
81 81 ------------------
82 82
83 83 The IPython controller processes provide an interface for working with a set of engines.
84 84 At a general level, the controller is a collection of processes to which IPython engines
85 85 and clients can connect. The controller is composed of a :class:`Hub` and a collection of
86 86 :class:`Schedulers`. These Schedulers are typically run in separate processes but on the
87 87 same machine as the Hub, but can be run anywhere from local threads or on remote machines.
88 88
89 89 The controller also provides a single point of contact for users who wish to
90 90 utilize the engines connected to the controller. There are different ways of
91 91 working with a controller. In IPython, all of these models are implemented via
92 92 the client's :meth:`.View.apply` method, with various arguments, or
93 93 constructing :class:`.View` objects to represent subsets of engines. The two
94 94 primary models for interacting with engines are:
95 95
96 96 * A **Direct** interface, where engines are addressed explicitly.
97 97 * A **LoadBalanced** interface, where the Scheduler is trusted with assigning work to
98 98 appropriate engines.
99 99
100 100 Advanced users can readily extend the View models to enable other
101 101 styles of parallelism.
102 102
103 103 .. note::
104 104
105 105 A single controller and set of engines can be used with multiple models
106 106 simultaneously. This opens the door for lots of interesting things.
107 107
108 108
109 109 The Hub
110 110 *******
111 111
112 112 The center of an IPython cluster is the Hub. This is the process that keeps
113 113 track of engine connections, schedulers, clients, as well as all task requests and
114 114 results. The primary role of the Hub is to facilitate queries of the cluster state, and
115 115 minimize the necessary information required to establish the many connections involved in
116 116 connecting new clients and engines.
117 117
118 118
119 119 Schedulers
120 120 **********
121 121
122 122 All actions that can be performed on the engine go through a Scheduler. While the engines
123 123 themselves block when user code is run, the schedulers hide that from the user to provide
124 124 a fully asynchronous interface to a set of engines.
125 125
126 126
127 127 IPython client and views
128 128 ------------------------
129 129
130 130 There is one primary object, the :class:`~.parallel.Client`, for connecting to a cluster.
131 131 For each execution model, there is a corresponding :class:`~.parallel.view.View`. These views
132 132 allow users to interact with a set of engines through the interface. Here are the two default
133 133 views:
134 134
135 135 * The :class:`DirectView` class for explicit addressing.
136 136 * The :class:`LoadBalancedView` class for destination-agnostic scheduling.
137 137
138 138 Security
139 139 --------
140 140
141 141 IPython uses ZeroMQ for networking, which has provided many advantages, but
142 142 one of the setbacks is its utter lack of security [ZeroMQ]_. By default, no IPython
143 143 connections are encrypted, but open ports only listen on localhost. The only
144 144 source of security for IPython is via ssh-tunnel. IPython supports both shell
145 145 (`openssh`) and `paramiko` based tunnels for connections. There is a key necessary
146 146 to submit requests, but due to the lack of encryption, it does not provide
147 147 significant security if loopback traffic is compromised.
148 148
149 149 In our architecture, the controller is the only process that listens on
150 150 network ports, and is thus the main point of vulnerability. The standard model
151 151 for secure connections is to designate that the controller listen on
152 152 localhost, and use ssh-tunnels to connect clients and/or
153 153 engines.
154 154
155 155 To connect and authenticate to the controller an engine or client needs
156 156 some information that the controller has stored in a JSON file.
157 157 Thus, the JSON files need to be copied to a location where
158 158 the clients and engines can find them. Typically, this is the
159 :file:`~/.ipython/clusterz_default/security` directory on the host where the
159 :file:`~/.ipython/cluster_default/security` directory on the host where the
160 160 client/engine is running (which could be a different host than the controller).
161 161 Once the JSON files are copied over, everything should work fine.
162 162
163 163 Currently, there are two JSON files that the controller creates:
164 164
165 165 ipcontroller-engine.json
166 166 This JSON file has the information necessary for an engine to connect
167 167 to a controller.
168 168
169 169 ipcontroller-client.json
170 170 The client's connection information. This may not differ from the engine's,
171 171 but since the controller may listen on different ports for clients and
172 172 engines, it is stored separately.
173 173
174 174 More details of how these JSON files are used are given below.
175 175
176 176 A detailed description of the security model and its implementation in IPython
177 177 can be found :ref:`here <parallelsecurity>`.
178 178
179 179 .. warning::
180 180
181 181 Even at its most secure, the Controller listens on ports on localhost, and
182 182 every time you make a tunnel, you open a localhost port on the connecting
183 183 machine that points to the Controller. If localhost on the Controller's
184 184 machine, or the machine of any client or engine, is untrusted, then your
185 185 Controller is insecure. There is no way around this with ZeroMQ.
186 186
187 187
188 188
189 189 Getting Started
190 190 ===============
191 191
192 192 To use IPython for parallel computing, you need to start one instance of the
193 193 controller and one or more instances of the engine. Initially, it is best to
194 194 simply start a controller and engines on a single host using the
195 :command:`ipclusterz` command. To start a controller and 4 engines on your
195 :command:`ipcluster` command. To start a controller and 4 engines on your
196 196 localhost, just do::
197 197
198 $ ipclusterz start -n 4
198 $ ipcluster start -n 4
199 199
200 200 More details about starting the IPython controller and engines can be found
201 201 :ref:`here <parallel_process>`
202 202
203 203 Once you have started the IPython controller and one or more engines, you
204 204 are ready to use the engines to do something useful. To make sure
205 205 everything is working correctly, try the following commands:
206 206
207 207 .. sourcecode:: ipython
208 208
209 209 In [1]: from IPython.parallel import Client
210 210
211 211 In [2]: c = Client()
212 212
213 213 In [4]: c.ids
214 214 Out[4]: set([0, 1, 2, 3])
215 215
216 216 In [5]: c[:].apply_sync(lambda : "Hello, World")
217 217 Out[5]: [ 'Hello, World', 'Hello, World', 'Hello, World', 'Hello, World' ]
218 218
219 219
220 220 When a client is created with no arguments, the client tries to find the corresponding JSON file
221 in the local `~/.ipython/clusterz_default/security` directory. Or if you specified a profile,
221 in the local `~/.ipython/cluster_default/security` directory. Or if you specified a profile,
222 222 you can use that with the Client. This should cover most cases:
223 223
224 224 .. sourcecode:: ipython
225 225
226 226 In [2]: c = Client(profile='myprofile')
227 227
228 228 If you have put the JSON file in a different location or it has a different name, create the
229 229 client like this:
230 230
231 231 .. sourcecode:: ipython
232 232
233 233 In [2]: c = Client('/path/to/my/ipcontroller-client.json')
234 234
235 235 Remember, a client needs to be able to see the Hub's ports to connect. So if they are on a
236 236 different machine, you may need to use an ssh server to tunnel access to that machine,
237 237 then you would connect to it with:
238 238
239 239 .. sourcecode:: ipython
240 240
241 241 In [2]: c = Client(sshserver='myhub.example.com')
242 242
243 243 Where 'myhub.example.com' is the url or IP address of the machine on
244 244 which the Hub process is running (or another machine that has direct access to the Hub's ports).
245 245
246 246 The SSH server may already be specified in ipcontroller-client.json, if the controller was
247 247 instructed at its launch time.
248 248
249 249 You are now ready to learn more about the :ref:`Direct
250 250 <parallel_multiengine>` and :ref:`LoadBalanced <parallel_task>` interfaces to the
251 251 controller.
252 252
253 253 .. [ZeroMQ] ZeroMQ. http://www.zeromq.org
@@ -1,156 +1,156 b''
1 1 .. _parallelmpi:
2 2
3 3 =======================
4 4 Using MPI with IPython
5 5 =======================
6 6
7 7 .. note::
8 8
9 9 Not adapted to zmq yet
10 10 This is out of date wrt ipcluster in general as well
11 11
12 12 Often, a parallel algorithm will require moving data between the engines. One
13 13 way of accomplishing this is by doing a pull and then a push using the
14 14 multiengine client. However, this will be slow as all the data has to go
15 15 through the controller to the client and then back through the controller, to
16 16 its final destination.
17 17
18 18 A much better way of moving data between engines is to use a message passing
19 19 library, such as the Message Passing Interface (MPI) [MPI]_. IPython's
20 20 parallel computing architecture has been designed from the ground up to
21 21 integrate with MPI. This document describes how to use MPI with IPython.
22 22
23 23 Additional installation requirements
24 24 ====================================
25 25
26 26 If you want to use MPI with IPython, you will need to install:
27 27
28 28 * A standard MPI implementation such as OpenMPI [OpenMPI]_ or MPICH.
29 29 * The mpi4py [mpi4py]_ package.
30 30
31 31 .. note::
32 32
33 33 The mpi4py package is not a strict requirement. However, you need to
34 34 have *some* way of calling MPI from Python. You also need some way of
35 35 making sure that :func:`MPI_Init` is called when the IPython engines start
36 36 up. There are a number of ways of doing this and a good number of
37 37 associated subtleties. We highly recommend just using mpi4py as it
38 38 takes care of most of these problems. If you want to do something
39 39 different, let us know and we can help you get started.
40 40
41 41 Starting the engines with MPI enabled
42 42 =====================================
43 43
44 44 To use code that calls MPI, there are typically two things that MPI requires.
45 45
46 46 1. The process that wants to call MPI must be started using
47 47 :command:`mpiexec` or a batch system (like PBS) that has MPI support.
48 48 2. Once the process starts, it must call :func:`MPI_Init`.
49 49
50 50 There are a couple of ways that you can start the IPython engines and get
51 51 these things to happen.
52 52
53 Automatic starting using :command:`mpiexec` and :command:`ipclusterz`
53 Automatic starting using :command:`mpiexec` and :command:`ipcluster`
54 54 --------------------------------------------------------------------
55 55
56 The easiest approach is to use the `mpiexec` mode of :command:`ipclusterz`,
56 The easiest approach is to use the `mpiexec` mode of :command:`ipcluster`,
57 57 which will first start a controller and then a set of engines using
58 58 :command:`mpiexec`::
59 59
60 $ ipclusterz mpiexec -n 4
60 $ ipcluster mpiexec -n 4
61 61
62 This approach is best as interrupting :command:`ipclusterz` will automatically
62 This approach is best as interrupting :command:`ipcluster` will automatically
63 63 stop and clean up the controller and engines.
64 64
65 65 Manual starting using :command:`mpiexec`
66 66 ----------------------------------------
67 67
68 68 If you want to start the IPython engines using the :command:`mpiexec`, just
69 69 do::
70 70
71 $ mpiexec -n 4 ipenginez --mpi=mpi4py
71 $ mpiexec -n 4 ipengine --mpi=mpi4py
72 72
73 73 This requires that you already have a controller running and that the FURL
74 74 files for the engines are in place. We also have built in support for
75 75 PyTrilinos [PyTrilinos]_, which can be used (assuming is installed) by
76 76 starting the engines with::
77 77
78 $ mpiexec -n 4 ipenginez --mpi=pytrilinos
78 $ mpiexec -n 4 ipengine --mpi=pytrilinos
79 79
80 Automatic starting using PBS and :command:`ipclusterz`
80 Automatic starting using PBS and :command:`ipcluster`
81 81 ------------------------------------------------------
82 82
83 The :command:`ipclusterz` command also has built-in integration with PBS. For
84 more information on this approach, see our documentation on :ref:`ipclusterz
83 The :command:`ipcluster` command also has built-in integration with PBS. For
84 more information on this approach, see our documentation on :ref:`ipcluster
85 85 <parallel_process>`.
86 86
87 87 Actually using MPI
88 88 ==================
89 89
90 90 Once the engines are running with MPI enabled, you are ready to go. You can
91 91 now call any code that uses MPI in the IPython engines. And, all of this can
92 92 be done interactively. Here we show a simple example that uses mpi4py
93 93 [mpi4py]_ version 1.1.0 or later.
94 94
95 95 First, lets define a simply function that uses MPI to calculate the sum of a
96 96 distributed array. Save the following text in a file called :file:`psum.py`:
97 97
98 98 .. sourcecode:: python
99 99
100 100 from mpi4py import MPI
101 101 import numpy as np
102 102
103 103 def psum(a):
104 104 s = np.sum(a)
105 105 rcvBuf = np.array(0.0,'d')
106 106 MPI.COMM_WORLD.Allreduce([s, MPI.DOUBLE],
107 107 [rcvBuf, MPI.DOUBLE],
108 108 op=MPI.SUM)
109 109 return rcvBuf
110 110
111 111 Now, start an IPython cluster::
112 112
113 $ ipclusterz start -p mpi -n 4
113 $ ipcluster start -p mpi -n 4
114 114
115 115 .. note::
116 116
117 117 It is assumed here that the mpi profile has been set up, as described :ref:`here
118 118 <parallel_process>`.
119 119
120 120 Finally, connect to the cluster and use this function interactively. In this
121 121 case, we create a random array on each engine and sum up all the random arrays
122 122 using our :func:`psum` function:
123 123
124 124 .. sourcecode:: ipython
125 125
126 126 In [1]: from IPython.parallel import Client
127 127
128 128 In [2]: %load_ext parallel_magic
129 129
130 130 In [3]: c = Client(profile='mpi')
131 131
132 132 In [4]: view = c[:]
133 133
134 134 In [5]: view.activate()
135 135
136 136 # run the contents of the file on each engine:
137 137 In [6]: view.run('psum.py')
138 138
139 139 In [6]: px a = np.random.rand(100)
140 140 Parallel execution on engines: [0,1,2,3]
141 141
142 142 In [8]: px s = psum(a)
143 143 Parallel execution on engines: [0,1,2,3]
144 144
145 145 In [9]: view['s']
146 146 Out[9]: [187.451545803,187.451545803,187.451545803,187.451545803]
147 147
148 148 Any Python code that makes calls to MPI can be used in this manner, including
149 149 compiled C, C++ and Fortran libraries that have been exposed to Python.
150 150
151 151 .. [MPI] Message Passing Interface. http://www-unix.mcs.anl.gov/mpi/
152 152 .. [mpi4py] MPI for Python. mpi4py: http://mpi4py.scipy.org/
153 153 .. [OpenMPI] Open MPI. http://www.open-mpi.org/
154 154 .. [PyTrilinos] PyTrilinos. http://trilinos.sandia.gov/packages/pytrilinos/
155 155
156 156
@@ -1,843 +1,843 b''
1 1 .. _parallel_multiengine:
2 2
3 3 ==========================
4 4 IPython's Direct interface
5 5 ==========================
6 6
7 7 The direct, or multiengine, interface represents one possible way of working with a set of
8 8 IPython engines. The basic idea behind the multiengine interface is that the
9 9 capabilities of each engine are directly and explicitly exposed to the user.
10 10 Thus, in the multiengine interface, each engine is given an id that is used to
11 11 identify the engine and give it work to do. This interface is very intuitive
12 12 and is designed with interactive usage in mind, and is the best place for
13 13 new users of IPython to begin.
14 14
15 15 Starting the IPython controller and engines
16 16 ===========================================
17 17
18 18 To follow along with this tutorial, you will need to start the IPython
19 19 controller and four IPython engines. The simplest way of doing this is to use
20 the :command:`ipclusterz` command::
20 the :command:`ipcluster` command::
21 21
22 $ ipclusterz start -n 4
22 $ ipcluster start -n 4
23 23
24 24 For more detailed information about starting the controller and engines, see
25 25 our :ref:`introduction <ip1par>` to using IPython for parallel computing.
26 26
27 27 Creating a ``Client`` instance
28 28 ==============================
29 29
30 30 The first step is to import the IPython :mod:`IPython.parallel`
31 31 module and then create a :class:`.Client` instance:
32 32
33 33 .. sourcecode:: ipython
34 34
35 35 In [1]: from IPython.parallel import Client
36 36
37 37 In [2]: rc = Client()
38 38
39 39 This form assumes that the default connection information (stored in
40 :file:`ipcontroller-client.json` found in :file:`IPYTHON_DIR/clusterz_default/security`) is
40 :file:`ipcontroller-client.json` found in :file:`IPYTHON_DIR/cluster_default/security`) is
41 41 accurate. If the controller was started on a remote machine, you must copy that connection
42 42 file to the client machine, or enter its contents as arguments to the Client constructor:
43 43
44 44 .. sourcecode:: ipython
45 45
46 46 # If you have copied the json connector file from the controller:
47 47 In [2]: rc = Client('/path/to/ipcontroller-client.json')
48 48 # or to connect with a specific profile you have set up:
49 49 In [3]: rc = Client(profile='mpi')
50 50
51 51
52 52 To make sure there are engines connected to the controller, users can get a list
53 53 of engine ids:
54 54
55 55 .. sourcecode:: ipython
56 56
57 57 In [3]: rc.ids
58 58 Out[3]: [0, 1, 2, 3]
59 59
60 60 Here we see that there are four engines ready to do work for us.
61 61
62 62 For direct execution, we will make use of a :class:`DirectView` object, which can be
63 63 constructed via list-access to the client:
64 64
65 65 .. sourcecode:: ipython
66 66
67 67 In [4]: dview = rc[:] # use all engines
68 68
69 69 .. seealso::
70 70
71 71 For more information, see the in-depth explanation of :ref:`Views <parallel_details>`.
72 72
73 73
74 74 Quick and easy parallelism
75 75 ==========================
76 76
77 77 In many cases, you simply want to apply a Python function to a sequence of
78 78 objects, but *in parallel*. The client interface provides a simple way
79 79 of accomplishing this: using the DirectView's :meth:`~DirectView.map` method.
80 80
81 81 Parallel map
82 82 ------------
83 83
84 84 Python's builtin :func:`map` functions allows a function to be applied to a
85 85 sequence element-by-element. This type of code is typically trivial to
86 86 parallelize. In fact, since IPython's interface is all about functions anyway,
87 87 you can just use the builtin :func:`map` with a :class:`RemoteFunction`, or a
88 88 DirectView's :meth:`map` method:
89 89
90 90 .. sourcecode:: ipython
91 91
92 92 In [62]: serial_result = map(lambda x:x**10, range(32))
93 93
94 94 In [63]: parallel_result = dview.map_sync(lambda x: x**10, range(32))
95 95
96 96 In [67]: serial_result==parallel_result
97 97 Out[67]: True
98 98
99 99
100 100 .. note::
101 101
102 102 The :class:`DirectView`'s version of :meth:`map` does
103 103 not do dynamic load balancing. For a load balanced version, use a
104 104 :class:`LoadBalancedView`.
105 105
106 106 .. seealso::
107 107
108 108 :meth:`map` is implemented via :class:`ParallelFunction`.
109 109
110 110 Remote function decorators
111 111 --------------------------
112 112
113 113 Remote functions are just like normal functions, but when they are called,
114 114 they execute on one or more engines, rather than locally. IPython provides
115 115 two decorators:
116 116
117 117 .. sourcecode:: ipython
118 118
119 119 In [10]: @dview.remote(block=True)
120 120 ...: def getpid():
121 121 ...: import os
122 122 ...: return os.getpid()
123 123 ...:
124 124
125 125 In [11]: getpid()
126 126 Out[11]: [12345, 12346, 12347, 12348]
127 127
128 128 The ``@parallel`` decorator creates parallel functions, that break up an element-wise
129 129 operations and distribute them, reconstructing the result.
130 130
131 131 .. sourcecode:: ipython
132 132
133 133 In [12]: import numpy as np
134 134
135 135 In [13]: A = np.random.random((64,48))
136 136
137 137 In [14]: @dview.parallel(block=True)
138 138 ...: def pmul(A,B):
139 139 ...: return A*B
140 140
141 141 In [15]: C_local = A*A
142 142
143 143 In [16]: C_remote = pmul(A,A)
144 144
145 145 In [17]: (C_local == C_remote).all()
146 146 Out[17]: True
147 147
148 148 .. seealso::
149 149
150 150 See the docstrings for the :func:`parallel` and :func:`remote` decorators for
151 151 options.
152 152
153 153 Calling Python functions
154 154 ========================
155 155
156 156 The most basic type of operation that can be performed on the engines is to
157 157 execute Python code or call Python functions. Executing Python code can be
158 158 done in blocking or non-blocking mode (non-blocking is default) using the
159 159 :meth:`.View.execute` method, and calling functions can be done via the
160 160 :meth:`.View.apply` method.
161 161
162 162 apply
163 163 -----
164 164
165 165 The main method for doing remote execution (in fact, all methods that
166 166 communicate with the engines are built on top of it), is :meth:`View.apply`.
167 167
168 168 We strive to provide the cleanest interface we can, so `apply` has the following
169 169 signature:
170 170
171 171 .. sourcecode:: python
172 172
173 173 view.apply(f, *args, **kwargs)
174 174
175 175 There are various ways to call functions with IPython, and these flags are set as
176 176 attributes of the View. The ``DirectView`` has just two of these flags:
177 177
178 178 dv.block : bool
179 179 whether to wait for the result, or return an :class:`AsyncResult` object
180 180 immediately
181 181 dv.track : bool
182 182 whether to instruct pyzmq to track when
183 183 This is primarily useful for non-copying sends of numpy arrays that you plan to
184 184 edit in-place. You need to know when it becomes safe to edit the buffer
185 185 without corrupting the message.
186 186
187 187
188 188 Creating a view is simple: index-access on a client creates a :class:`.DirectView`.
189 189
190 190 .. sourcecode:: ipython
191 191
192 192 In [4]: view = rc[1:3]
193 193 Out[4]: <DirectView [1, 2]>
194 194
195 195 In [5]: view.apply<tab>
196 196 view.apply view.apply_async view.apply_sync
197 197
198 198 For convenience, you can set block temporarily for a single call with the extra sync/async methods.
199 199
200 200 Blocking execution
201 201 ------------------
202 202
203 203 In blocking mode, the :class:`.DirectView` object (called ``dview`` in
204 204 these examples) submits the command to the controller, which places the
205 205 command in the engines' queues for execution. The :meth:`apply` call then
206 206 blocks until the engines are done executing the command:
207 207
208 208 .. sourcecode:: ipython
209 209
210 210 In [2]: dview = rc[:] # A DirectView of all engines
211 211 In [3]: dview.block=True
212 212 In [4]: dview['a'] = 5
213 213
214 214 In [5]: dview['b'] = 10
215 215
216 216 In [6]: dview.apply(lambda x: a+b+x, 27)
217 217 Out[6]: [42, 42, 42, 42]
218 218
219 219 You can also select blocking execution on a call-by-call basis with the :meth:`apply_sync`
220 220 method:
221 221
222 222 In [7]: dview.block=False
223 223
224 224 In [8]: dview.apply_sync(lambda x: a+b+x, 27)
225 225 Out[8]: [42, 42, 42, 42]
226 226
227 227 Python commands can be executed as strings on specific engines by using a View's ``execute``
228 228 method:
229 229
230 230 .. sourcecode:: ipython
231 231
232 232 In [6]: rc[::2].execute('c=a+b')
233 233
234 234 In [7]: rc[1::2].execute('c=a-b')
235 235
236 236 In [8]: dview['c'] # shorthand for dview.pull('c', block=True)
237 237 Out[8]: [15, -5, 15, -5]
238 238
239 239
240 240 Non-blocking execution
241 241 ----------------------
242 242
243 243 In non-blocking mode, :meth:`apply` submits the command to be executed and
244 244 then returns a :class:`AsyncResult` object immediately. The
245 245 :class:`AsyncResult` object gives you a way of getting a result at a later
246 246 time through its :meth:`get` method.
247 247
248 248 .. Note::
249 249
250 250 The :class:`AsyncResult` object provides a superset of the interface in
251 251 :py:class:`multiprocessing.pool.AsyncResult`. See the
252 252 `official Python documentation <http://docs.python.org/library/multiprocessing#multiprocessing.pool.AsyncResult>`_
253 253 for more.
254 254
255 255
256 256 This allows you to quickly submit long running commands without blocking your
257 257 local Python/IPython session:
258 258
259 259 .. sourcecode:: ipython
260 260
261 261 # define our function
262 262 In [6]: def wait(t):
263 263 ...: import time
264 264 ...: tic = time.time()
265 265 ...: time.sleep(t)
266 266 ...: return time.time()-tic
267 267
268 268 # In non-blocking mode
269 269 In [7]: ar = dview.apply_async(wait, 2)
270 270
271 271 # Now block for the result
272 272 In [8]: ar.get()
273 273 Out[8]: [2.0006198883056641, 1.9997570514678955, 1.9996809959411621, 2.0003249645233154]
274 274
275 275 # Again in non-blocking mode
276 276 In [9]: ar = dview.apply_async(wait, 10)
277 277
278 278 # Poll to see if the result is ready
279 279 In [10]: ar.ready()
280 280 Out[10]: False
281 281
282 282 # ask for the result, but wait a maximum of 1 second:
283 283 In [45]: ar.get(1)
284 284 ---------------------------------------------------------------------------
285 285 TimeoutError Traceback (most recent call last)
286 286 /home/you/<ipython-input-45-7cd858bbb8e0> in <module>()
287 287 ----> 1 ar.get(1)
288 288
289 289 /path/to/site-packages/IPython/parallel/asyncresult.pyc in get(self, timeout)
290 290 62 raise self._exception
291 291 63 else:
292 292 ---> 64 raise error.TimeoutError("Result not ready.")
293 293 65
294 294 66 def ready(self):
295 295
296 296 TimeoutError: Result not ready.
297 297
298 298 .. Note::
299 299
300 300 Note the import inside the function. This is a common model, to ensure
301 301 that the appropriate modules are imported where the task is run. You can
302 302 also manually import modules into the engine(s) namespace(s) via
303 303 :meth:`view.execute('import numpy')`.
304 304
305 305 Often, it is desirable to wait until a set of :class:`AsyncResult` objects
306 306 are done. For this, there is a the method :meth:`wait`. This method takes a
307 307 tuple of :class:`AsyncResult` objects (or `msg_ids` or indices to the client's History),
308 308 and blocks until all of the associated results are ready:
309 309
310 310 .. sourcecode:: ipython
311 311
312 312 In [72]: dview.block=False
313 313
314 314 # A trivial list of AsyncResults objects
315 315 In [73]: pr_list = [dview.apply_async(wait, 3) for i in range(10)]
316 316
317 317 # Wait until all of them are done
318 318 In [74]: dview.wait(pr_list)
319 319
320 320 # Then, their results are ready using get() or the `.r` attribute
321 321 In [75]: pr_list[0].get()
322 322 Out[75]: [2.9982571601867676, 2.9982588291168213, 2.9987530708312988, 2.9990990161895752]
323 323
324 324
325 325
326 326 The ``block`` and ``targets`` keyword arguments and attributes
327 327 --------------------------------------------------------------
328 328
329 329 Most DirectView methods (excluding :meth:`apply` and :meth:`map`) accept ``block`` and
330 330 ``targets`` as keyword arguments. As we have seen above, these keyword arguments control the
331 331 blocking mode and which engines the command is applied to. The :class:`View` class also has
332 332 :attr:`block` and :attr:`targets` attributes that control the default behavior when the keyword
333 333 arguments are not provided. Thus the following logic is used for :attr:`block` and :attr:`targets`:
334 334
335 335 * If no keyword argument is provided, the instance attributes are used.
336 336 * Keyword argument, if provided override the instance attributes for
337 337 the duration of a single call.
338 338
339 339 The following examples demonstrate how to use the instance attributes:
340 340
341 341 .. sourcecode:: ipython
342 342
343 343 In [16]: dview.targets = [0,2]
344 344
345 345 In [17]: dview.block = False
346 346
347 347 In [18]: ar = dview.apply(lambda : 10)
348 348
349 349 In [19]: ar.get()
350 350 Out[19]: [10, 10]
351 351
352 352 In [16]: dview.targets = v.client.ids # all engines (4)
353 353
354 354 In [21]: dview.block = True
355 355
356 356 In [22]: dview.apply(lambda : 42)
357 357 Out[22]: [42, 42, 42, 42]
358 358
359 359 The :attr:`block` and :attr:`targets` instance attributes of the
360 360 :class:`.DirectView` also determine the behavior of the parallel magic commands.
361 361
362 362 Parallel magic commands
363 363 -----------------------
364 364
365 365 .. warning::
366 366
367 367 The magics have not been changed to work with the zeromq system. The
368 368 magics do work, but *do not* print stdin/out like they used to in IPython.kernel.
369 369
370 370 We provide a few IPython magic commands (``%px``, ``%autopx`` and ``%result``)
371 371 that make it more pleasant to execute Python commands on the engines
372 372 interactively. These are simply shortcuts to :meth:`execute` and
373 373 :meth:`get_result` of the :class:`DirectView`. The ``%px`` magic executes a single
374 374 Python command on the engines specified by the :attr:`targets` attribute of the
375 375 :class:`DirectView` instance:
376 376
377 377 .. sourcecode:: ipython
378 378
379 379 # load the parallel magic extension:
380 380 In [21]: %load_ext parallelmagic
381 381
382 382 # Create a DirectView for all targets
383 383 In [22]: dv = rc[:]
384 384
385 385 # Make this DirectView active for parallel magic commands
386 386 In [23]: dv.activate()
387 387
388 388 In [24]: dv.block=True
389 389
390 390 In [25]: import numpy
391 391
392 392 In [26]: %px import numpy
393 393 Parallel execution on engines: [0, 1, 2, 3]
394 394
395 395 In [27]: %px a = numpy.random.rand(2,2)
396 396 Parallel execution on engines: [0, 1, 2, 3]
397 397
398 398 In [28]: %px ev = numpy.linalg.eigvals(a)
399 399 Parallel execution on engines: [0, 1, 2, 3]
400 400
401 401 In [28]: dv['ev']
402 402 Out[28]: [ array([ 1.09522024, -0.09645227]),
403 403 array([ 1.21435496, -0.35546712]),
404 404 array([ 0.72180653, 0.07133042]),
405 405 array([ 1.46384341e+00, 1.04353244e-04])
406 406 ]
407 407
408 408 The ``%result`` magic gets the most recent result, or takes an argument
409 409 specifying the index of the result to be requested. It is simply a shortcut to the
410 410 :meth:`get_result` method:
411 411
412 412 .. sourcecode:: ipython
413 413
414 414 In [29]: dv.apply_async(lambda : ev)
415 415
416 416 In [30]: %result
417 417 Out[30]: [ [ 1.28167017 0.14197338],
418 418 [-0.14093616 1.27877273],
419 419 [-0.37023573 1.06779409],
420 420 [ 0.83664764 -0.25602658] ]
421 421
422 422 The ``%autopx`` magic switches to a mode where everything you type is executed
423 423 on the engines given by the :attr:`targets` attribute:
424 424
425 425 .. sourcecode:: ipython
426 426
427 427 In [30]: dv.block=False
428 428
429 429 In [31]: %autopx
430 430 Auto Parallel Enabled
431 431 Type %autopx to disable
432 432
433 433 In [32]: max_evals = []
434 434 <IPython.parallel.asyncresult.AsyncResult object at 0x17b8a70>
435 435
436 436 In [33]: for i in range(100):
437 437 ....: a = numpy.random.rand(10,10)
438 438 ....: a = a+a.transpose()
439 439 ....: evals = numpy.linalg.eigvals(a)
440 440 ....: max_evals.append(evals[0].real)
441 441 ....:
442 442 ....:
443 443 <IPython.parallel.asyncresult.AsyncResult object at 0x17af8f0>
444 444
445 445 In [34]: %autopx
446 446 Auto Parallel Disabled
447 447
448 448 In [35]: dv.block=True
449 449
450 450 In [36]: px ans= "Average max eigenvalue is: %f"%(sum(max_evals)/len(max_evals))
451 451 Parallel execution on engines: [0, 1, 2, 3]
452 452
453 453 In [37]: dv['ans']
454 454 Out[37]: [ 'Average max eigenvalue is: 10.1387247332',
455 455 'Average max eigenvalue is: 10.2076902286',
456 456 'Average max eigenvalue is: 10.1891484655',
457 457 'Average max eigenvalue is: 10.1158837784',]
458 458
459 459
460 460 Moving Python objects around
461 461 ============================
462 462
463 463 In addition to calling functions and executing code on engines, you can
464 464 transfer Python objects to and from your IPython session and the engines. In
465 465 IPython, these operations are called :meth:`push` (sending an object to the
466 466 engines) and :meth:`pull` (getting an object from the engines).
467 467
468 468 Basic push and pull
469 469 -------------------
470 470
471 471 Here are some examples of how you use :meth:`push` and :meth:`pull`:
472 472
473 473 .. sourcecode:: ipython
474 474
475 475 In [38]: dview.push(dict(a=1.03234,b=3453))
476 476 Out[38]: [None,None,None,None]
477 477
478 478 In [39]: dview.pull('a')
479 479 Out[39]: [ 1.03234, 1.03234, 1.03234, 1.03234]
480 480
481 481 In [40]: dview.pull('b', targets=0)
482 482 Out[40]: 3453
483 483
484 484 In [41]: dview.pull(('a','b'))
485 485 Out[41]: [ [1.03234, 3453], [1.03234, 3453], [1.03234, 3453], [1.03234, 3453] ]
486 486
487 487 In [43]: dview.push(dict(c='speed'))
488 488 Out[43]: [None,None,None,None]
489 489
490 490 In non-blocking mode :meth:`push` and :meth:`pull` also return
491 491 :class:`AsyncResult` objects:
492 492
493 493 .. sourcecode:: ipython
494 494
495 495 In [48]: ar = dview.pull('a', block=False)
496 496
497 497 In [49]: ar.get()
498 498 Out[49]: [1.03234, 1.03234, 1.03234, 1.03234]
499 499
500 500
501 501 Dictionary interface
502 502 --------------------
503 503
504 504 Since a Python namespace is just a :class:`dict`, :class:`DirectView` objects provide
505 505 dictionary-style access by key and methods such as :meth:`get` and
506 506 :meth:`update` for convenience. This make the remote namespaces of the engines
507 507 appear as a local dictionary. Underneath, these methods call :meth:`apply`:
508 508
509 509 .. sourcecode:: ipython
510 510
511 511 In [51]: dview['a']=['foo','bar']
512 512
513 513 In [52]: dview['a']
514 514 Out[52]: [ ['foo', 'bar'], ['foo', 'bar'], ['foo', 'bar'], ['foo', 'bar'] ]
515 515
516 516 Scatter and gather
517 517 ------------------
518 518
519 519 Sometimes it is useful to partition a sequence and push the partitions to
520 520 different engines. In MPI language, this is know as scatter/gather and we
521 521 follow that terminology. However, it is important to remember that in
522 522 IPython's :class:`Client` class, :meth:`scatter` is from the
523 523 interactive IPython session to the engines and :meth:`gather` is from the
524 524 engines back to the interactive IPython session. For scatter/gather operations
525 525 between engines, MPI should be used:
526 526
527 527 .. sourcecode:: ipython
528 528
529 529 In [58]: dview.scatter('a',range(16))
530 530 Out[58]: [None,None,None,None]
531 531
532 532 In [59]: dview['a']
533 533 Out[59]: [ [0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11], [12, 13, 14, 15] ]
534 534
535 535 In [60]: dview.gather('a')
536 536 Out[60]: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
537 537
538 538 Other things to look at
539 539 =======================
540 540
541 541 How to do parallel list comprehensions
542 542 --------------------------------------
543 543
544 544 In many cases list comprehensions are nicer than using the map function. While
545 545 we don't have fully parallel list comprehensions, it is simple to get the
546 546 basic effect using :meth:`scatter` and :meth:`gather`:
547 547
548 548 .. sourcecode:: ipython
549 549
550 550 In [66]: dview.scatter('x',range(64))
551 551
552 552 In [67]: %px y = [i**10 for i in x]
553 553 Parallel execution on engines: [0, 1, 2, 3]
554 554 Out[67]:
555 555
556 556 In [68]: y = dview.gather('y')
557 557
558 558 In [69]: print y
559 559 [0, 1, 1024, 59049, 1048576, 9765625, 60466176, 282475249, 1073741824,...]
560 560
561 561 Remote imports
562 562 --------------
563 563
564 564 Sometimes you will want to import packages both in your interactive session
565 565 and on your remote engines. This can be done with the :class:`ContextManager`
566 566 created by a DirectView's :meth:`sync_imports` method:
567 567
568 568 .. sourcecode:: ipython
569 569
570 570 In [69]: with dview.sync_imports():
571 571 ...: import numpy
572 572 importing numpy on engine(s)
573 573
574 574 Any imports made inside the block will also be performed on the view's engines.
575 575 sync_imports also takes a `local` boolean flag that defaults to True, which specifies
576 576 whether the local imports should also be performed. However, support for `local=False`
577 577 has not been implemented, so only packages that can be imported locally will work
578 578 this way.
579 579
580 580 You can also specify imports via the ``@require`` decorator. This is a decorator
581 581 designed for use in Dependencies, but can be used to handle remote imports as well.
582 582 Modules or module names passed to ``@require`` will be imported before the decorated
583 583 function is called. If they cannot be imported, the decorated function will never
584 584 execution, and will fail with an UnmetDependencyError.
585 585
586 586 .. sourcecode:: ipython
587 587
588 588 In [69]: from IPython.parallel import require
589 589
590 590 In [70]: @requre('re'):
591 591 ...: def findall(pat, x):
592 592 ...: # re is guaranteed to be available
593 593 ...: return re.findall(pat, x)
594 594
595 595 # you can also pass modules themselves, that you already have locally:
596 596 In [71]: @requre(time):
597 597 ...: def wait(t):
598 598 ...: time.sleep(t)
599 599 ...: return t
600 600
601 601
602 602 Parallel exceptions
603 603 -------------------
604 604
605 605 In the multiengine interface, parallel commands can raise Python exceptions,
606 606 just like serial commands. But, it is a little subtle, because a single
607 607 parallel command can actually raise multiple exceptions (one for each engine
608 608 the command was run on). To express this idea, we have a
609 609 :exc:`CompositeError` exception class that will be raised in most cases. The
610 610 :exc:`CompositeError` class is a special type of exception that wraps one or
611 611 more other types of exceptions. Here is how it works:
612 612
613 613 .. sourcecode:: ipython
614 614
615 615 In [76]: dview.block=True
616 616
617 617 In [77]: dview.execute('1/0')
618 618 ---------------------------------------------------------------------------
619 619 CompositeError Traceback (most recent call last)
620 620 /home/you/<ipython-input-10-15c2c22dec39> in <module>()
621 621 ----> 1 dview.execute('1/0', block=True)
622 622
623 623 /path/to/site-packages/IPython/parallel/view.py in execute(self, code, block)
624 624 460 default: self.block
625 625 461 """
626 626 --> 462 return self.apply_with_flags(util._execute, args=(code,), block=block)
627 627 463
628 628 464 def run(self, filename, block=None):
629 629
630 630 /home/you/<string> in apply_with_flags(self, f, args, kwargs, block, track)
631 631
632 632 /path/to/site-packages/IPython/parallel/view.py in sync_results(f, self, *args, **kwargs)
633 633 46 def sync_results(f, self, *args, **kwargs):
634 634 47 """sync relevant results from self.client to our results attribute."""
635 635 ---> 48 ret = f(self, *args, **kwargs)
636 636 49 delta = self.outstanding.difference(self.client.outstanding)
637 637 50 completed = self.outstanding.intersection(delta)
638 638
639 639 /home/you/<string> in apply_with_flags(self, f, args, kwargs, block, track)
640 640
641 641 /path/to/site-packages/IPython/parallel/view.py in save_ids(f, self, *args, **kwargs)
642 642 35 n_previous = len(self.client.history)
643 643 36 try:
644 644 ---> 37 ret = f(self, *args, **kwargs)
645 645 38 finally:
646 646 39 nmsgs = len(self.client.history) - n_previous
647 647
648 648 /path/to/site-packages/IPython/parallel/view.py in apply_with_flags(self, f, args, kwargs, block, track)
649 649 398 if block:
650 650 399 try:
651 651 --> 400 return ar.get()
652 652 401 except KeyboardInterrupt:
653 653 402 pass
654 654
655 655 /path/to/site-packages/IPython/parallel/asyncresult.pyc in get(self, timeout)
656 656 87 return self._result
657 657 88 else:
658 658 ---> 89 raise self._exception
659 659 90 else:
660 660 91 raise error.TimeoutError("Result not ready.")
661 661
662 662 CompositeError: one or more exceptions from call to method: _execute
663 663 [0:apply]: ZeroDivisionError: integer division or modulo by zero
664 664 [1:apply]: ZeroDivisionError: integer division or modulo by zero
665 665 [2:apply]: ZeroDivisionError: integer division or modulo by zero
666 666 [3:apply]: ZeroDivisionError: integer division or modulo by zero
667 667
668 668
669 669 Notice how the error message printed when :exc:`CompositeError` is raised has
670 670 information about the individual exceptions that were raised on each engine.
671 671 If you want, you can even raise one of these original exceptions:
672 672
673 673 .. sourcecode:: ipython
674 674
675 675 In [80]: try:
676 676 ....: dview.execute('1/0')
677 677 ....: except client.CompositeError, e:
678 678 ....: e.raise_exception()
679 679 ....:
680 680 ....:
681 681 ---------------------------------------------------------------------------
682 682 ZeroDivisionError Traceback (most recent call last)
683 683
684 684 /ipython1-client-r3021/docs/examples/<ipython console> in <module>()
685 685
686 686 /ipython1-client-r3021/ipython1/kernel/error.pyc in raise_exception(self, excid)
687 687 156 raise IndexError("an exception with index %i does not exist"%excid)
688 688 157 else:
689 689 --> 158 raise et, ev, etb
690 690 159
691 691 160 def collect_exceptions(rlist, method):
692 692
693 693 ZeroDivisionError: integer division or modulo by zero
694 694
695 695 If you are working in IPython, you can simple type ``%debug`` after one of
696 696 these :exc:`CompositeError` exceptions is raised, and inspect the exception
697 697 instance:
698 698
699 699 .. sourcecode:: ipython
700 700
701 701 In [81]: dview.execute('1/0')
702 702 ---------------------------------------------------------------------------
703 703 CompositeError Traceback (most recent call last)
704 704 /home/you/<ipython-input-10-15c2c22dec39> in <module>()
705 705 ----> 1 dview.execute('1/0', block=True)
706 706
707 707 /path/to/site-packages/IPython/parallel/view.py in execute(self, code, block)
708 708 460 default: self.block
709 709 461 """
710 710 --> 462 return self.apply_with_flags(util._execute, args=(code,), block=block)
711 711 463
712 712 464 def run(self, filename, block=None):
713 713
714 714 /home/you/<string> in apply_with_flags(self, f, args, kwargs, block, track)
715 715
716 716 /path/to/site-packages/IPython/parallel/view.py in sync_results(f, self, *args, **kwargs)
717 717 46 def sync_results(f, self, *args, **kwargs):
718 718 47 """sync relevant results from self.client to our results attribute."""
719 719 ---> 48 ret = f(self, *args, **kwargs)
720 720 49 delta = self.outstanding.difference(self.client.outstanding)
721 721 50 completed = self.outstanding.intersection(delta)
722 722
723 723 /home/you/<string> in apply_with_flags(self, f, args, kwargs, block, track)
724 724
725 725 /path/to/site-packages/IPython/parallel/view.py in save_ids(f, self, *args, **kwargs)
726 726 35 n_previous = len(self.client.history)
727 727 36 try:
728 728 ---> 37 ret = f(self, *args, **kwargs)
729 729 38 finally:
730 730 39 nmsgs = len(self.client.history) - n_previous
731 731
732 732 /path/to/site-packages/IPython/parallel/view.py in apply_with_flags(self, f, args, kwargs, block, track)
733 733 398 if block:
734 734 399 try:
735 735 --> 400 return ar.get()
736 736 401 except KeyboardInterrupt:
737 737 402 pass
738 738
739 739 /path/to/site-packages/IPython/parallel/asyncresult.pyc in get(self, timeout)
740 740 87 return self._result
741 741 88 else:
742 742 ---> 89 raise self._exception
743 743 90 else:
744 744 91 raise error.TimeoutError("Result not ready.")
745 745
746 746 CompositeError: one or more exceptions from call to method: _execute
747 747 [0:apply]: ZeroDivisionError: integer division or modulo by zero
748 748 [1:apply]: ZeroDivisionError: integer division or modulo by zero
749 749 [2:apply]: ZeroDivisionError: integer division or modulo by zero
750 750 [3:apply]: ZeroDivisionError: integer division or modulo by zero
751 751
752 752 In [82]: %debug
753 753 > /path/to/site-packages/IPython/parallel/asyncresult.py(80)get()
754 754 79 else:
755 755 ---> 80 raise self._exception
756 756 81 else:
757 757
758 758
759 759 # With the debugger running, e is the exceptions instance. We can tab complete
760 760 # on it and see the extra methods that are available.
761 761 ipdb> e.
762 762 e.__class__ e.__getitem__ e.__new__ e.__setstate__ e.args
763 763 e.__delattr__ e.__getslice__ e.__reduce__ e.__str__ e.elist
764 764 e.__dict__ e.__hash__ e.__reduce_ex__ e.__weakref__ e.message
765 765 e.__doc__ e.__init__ e.__repr__ e._get_engine_str e.print_tracebacks
766 766 e.__getattribute__ e.__module__ e.__setattr__ e._get_traceback e.raise_exception
767 767 ipdb> e.print_tracebacks()
768 768 [0:apply]:
769 769 Traceback (most recent call last):
770 770 File "/path/to/site-packages/IPython/parallel/streamkernel.py", line 332, in apply_request
771 771 exec code in working, working
772 772 File "<string>", line 1, in <module>
773 773 File "/path/to/site-packages/IPython/parallel/client.py", line 69, in _execute
774 774 exec code in globals()
775 775 File "<string>", line 1, in <module>
776 776 ZeroDivisionError: integer division or modulo by zero
777 777
778 778
779 779 [1:apply]:
780 780 Traceback (most recent call last):
781 781 File "/path/to/site-packages/IPython/parallel/streamkernel.py", line 332, in apply_request
782 782 exec code in working, working
783 783 File "<string>", line 1, in <module>
784 784 File "/path/to/site-packages/IPython/parallel/client.py", line 69, in _execute
785 785 exec code in globals()
786 786 File "<string>", line 1, in <module>
787 787 ZeroDivisionError: integer division or modulo by zero
788 788
789 789
790 790 [2:apply]:
791 791 Traceback (most recent call last):
792 792 File "/path/to/site-packages/IPython/parallel/streamkernel.py", line 332, in apply_request
793 793 exec code in working, working
794 794 File "<string>", line 1, in <module>
795 795 File "/path/to/site-packages/IPython/parallel/client.py", line 69, in _execute
796 796 exec code in globals()
797 797 File "<string>", line 1, in <module>
798 798 ZeroDivisionError: integer division or modulo by zero
799 799
800 800
801 801 [3:apply]:
802 802 Traceback (most recent call last):
803 803 File "/path/to/site-packages/IPython/parallel/streamkernel.py", line 332, in apply_request
804 804 exec code in working, working
805 805 File "<string>", line 1, in <module>
806 806 File "/path/to/site-packages/IPython/parallel/client.py", line 69, in _execute
807 807 exec code in globals()
808 808 File "<string>", line 1, in <module>
809 809 ZeroDivisionError: integer division or modulo by zero
810 810
811 811
812 812 .. note::
813 813
814 814 TODO: The above tracebacks are not up to date
815 815
816 816
817 817 All of this same error handling magic even works in non-blocking mode:
818 818
819 819 .. sourcecode:: ipython
820 820
821 821 In [83]: dview.block=False
822 822
823 823 In [84]: ar = dview.execute('1/0')
824 824
825 825 In [85]: ar.get()
826 826 ---------------------------------------------------------------------------
827 827 CompositeError Traceback (most recent call last)
828 828 /Users/minrk/<ipython-input-3-8531eb3d26fb> in <module>()
829 829 ----> 1 ar.get()
830 830
831 831 /path/to/site-packages/IPython/parallel/asyncresult.pyc in get(self, timeout)
832 832 78 return self._result
833 833 79 else:
834 834 ---> 80 raise self._exception
835 835 81 else:
836 836 82 raise error.TimeoutError("Result not ready.")
837 837
838 838 CompositeError: one or more exceptions from call to method: _execute
839 839 [0:apply]: ZeroDivisionError: integer division or modulo by zero
840 840 [1:apply]: ZeroDivisionError: integer division or modulo by zero
841 841 [2:apply]: ZeroDivisionError: integer division or modulo by zero
842 842 [3:apply]: ZeroDivisionError: integer division or modulo by zero
843 843
1 NO CONTENT: file renamed from docs/source/parallelz/parallel_pi.pdf to docs/source/parallel/parallel_pi.pdf
1 NO CONTENT: file renamed from docs/source/parallelz/parallel_pi.png to docs/source/parallel/parallel_pi.png
@@ -1,506 +1,506 b''
1 1 .. _parallel_process:
2 2
3 3 ===========================================
4 4 Starting the IPython controller and engines
5 5 ===========================================
6 6
7 7 To use IPython for parallel computing, you need to start one instance of
8 8 the controller and one or more instances of the engine. The controller
9 9 and each engine can run on different machines or on the same machine.
10 10 Because of this, there are many different possibilities.
11 11
12 12 Broadly speaking, there are two ways of going about starting a controller and engines:
13 13
14 * In an automated manner using the :command:`ipclusterz` command.
15 * In a more manual way using the :command:`ipcontrollerz` and
16 :command:`ipenginez` commands.
14 * In an automated manner using the :command:`ipcluster` command.
15 * In a more manual way using the :command:`ipcontroller` and
16 :command:`ipengine` commands.
17 17
18 18 This document describes both of these methods. We recommend that new users
19 start with the :command:`ipclusterz` command as it simplifies many common usage
19 start with the :command:`ipcluster` command as it simplifies many common usage
20 20 cases.
21 21
22 22 General considerations
23 23 ======================
24 24
25 25 Before delving into the details about how you can start a controller and
26 26 engines using the various methods, we outline some of the general issues that
27 27 come up when starting the controller and engines. These things come up no
28 28 matter which method you use to start your IPython cluster.
29 29
30 30 Let's say that you want to start the controller on ``host0`` and engines on
31 31 hosts ``host1``-``hostn``. The following steps are then required:
32 32
33 1. Start the controller on ``host0`` by running :command:`ipcontrollerz` on
33 1. Start the controller on ``host0`` by running :command:`ipcontroller` on
34 34 ``host0``.
35 35 2. Move the JSON file (:file:`ipcontroller-engine.json`) created by the
36 36 controller from ``host0`` to hosts ``host1``-``hostn``.
37 37 3. Start the engines on hosts ``host1``-``hostn`` by running
38 :command:`ipenginez`. This command has to be told where the JSON file
38 :command:`ipengine`. This command has to be told where the JSON file
39 39 (:file:`ipcontroller-engine.json`) is located.
40 40
41 41 At this point, the controller and engines will be connected. By default, the JSON files
42 created by the controller are put into the :file:`~/.ipython/clusterz_default/security`
42 created by the controller are put into the :file:`~/.ipython/cluster_default/security`
43 43 directory. If the engines share a filesystem with the controller, step 2 can be skipped as
44 44 the engines will automatically look at that location.
45 45
46 46 The final step required to actually use the running controller from a client is to move
47 47 the JSON file :file:`ipcontroller-client.json` from ``host0`` to any host where clients
48 will be run. If these file are put into the :file:`~/.ipython/clusterz_default/security`
48 will be run. If these file are put into the :file:`~/.ipython/cluster_default/security`
49 49 directory of the client's host, they will be found automatically. Otherwise, the full path
50 50 to them has to be passed to the client's constructor.
51 51
52 Using :command:`ipclusterz`
52 Using :command:`ipcluster`
53 53 ===========================
54 54
55 The :command:`ipclusterz` command provides a simple way of starting a
55 The :command:`ipcluster` command provides a simple way of starting a
56 56 controller and engines in the following situations:
57 57
58 58 1. When the controller and engines are all run on localhost. This is useful
59 59 for testing or running on a multicore computer.
60 60 2. When engines are started using the :command:`mpirun` command that comes
61 61 with most MPI [MPI]_ implementations
62 62 3. When engines are started using the PBS [PBS]_ batch system
63 63 (or other `qsub` systems, such as SGE).
64 64 4. When the controller is started on localhost and the engines are started on
65 65 remote nodes using :command:`ssh`.
66 66 5. When engines are started using the Windows HPC Server batch system.
67 67
68 68 .. note::
69 69
70 Currently :command:`ipclusterz` requires that the
70 Currently :command:`ipcluster` requires that the
71 71 :file:`~/.ipython/cluster_<profile>/security` directory live on a shared filesystem that is
72 72 seen by both the controller and engines. If you don't have a shared file
73 system you will need to use :command:`ipcontrollerz` and
74 :command:`ipenginez` directly.
73 system you will need to use :command:`ipcontroller` and
74 :command:`ipengine` directly.
75 75
76 Under the hood, :command:`ipclusterz` just uses :command:`ipcontrollerz`
77 and :command:`ipenginez` to perform the steps described above.
76 Under the hood, :command:`ipcluster` just uses :command:`ipcontroller`
77 and :command:`ipengine` to perform the steps described above.
78 78
79 The simplest way to use ipclusterz requires no configuration, and will
79 The simplest way to use ipcluster requires no configuration, and will
80 80 launch a controller and a number of engines on the local machine. For instance,
81 81 to start one controller and 4 engines on localhost, just do::
82 82
83 $ ipclusterz start -n 4
83 $ ipcluster start -n 4
84 84
85 85 To see other command line options for the local mode, do::
86 86
87 $ ipclusterz -h
87 $ ipcluster -h
88 88
89 89
90 90 Configuring an IPython cluster
91 91 ==============================
92 92
93 93 Cluster configurations are stored as `profiles`. You can create a new profile with::
94 94
95 $ ipclusterz create -p myprofile
95 $ ipcluster create -p myprofile
96 96
97 This will create the directory :file:`IPYTHONDIR/clusterz_myprofile`, and populate it
97 This will create the directory :file:`IPYTHONDIR/cluster_myprofile`, and populate it
98 98 with the default configuration files for the three IPython cluster commands. Once
99 you edit those files, you can continue to call ipclusterz/ipcontrollerz/ipenginez
99 you edit those files, you can continue to call ipcluster/ipcontroller/ipengine
100 100 with no arguments beyond ``-p myprofile``, and any configuration will be maintained.
101 101
102 102 There is no limit to the number of profiles you can have, so you can maintain a profile for each
103 103 of your common use cases. The default profile will be used whenever the
104 profile argument is not specified, so edit :file:`IPYTHONDIR/clusterz_default/*_config.py` to
104 profile argument is not specified, so edit :file:`IPYTHONDIR/cluster_default/*_config.py` to
105 105 represent your most common use case.
106 106
107 107 The configuration files are loaded with commented-out settings and explanations,
108 108 which should cover most of the available possibilities.
109 109
110 Using various batch systems with :command:`ipclusterz`
110 Using various batch systems with :command:`ipcluster`
111 111 ------------------------------------------------------
112 112
113 :command:`ipclusterz` has a notion of Launchers that can start controllers
113 :command:`ipcluster` has a notion of Launchers that can start controllers
114 114 and engines with various remote execution schemes. Currently supported
115 115 models include `mpiexec`, PBS-style (Torque, SGE), and Windows HPC Server.
116 116
117 117 .. note::
118 118
119 119 The Launchers and configuration are designed in such a way that advanced
120 120 users can subclass and configure them to fit their own system that we
121 121 have not yet supported (such as Condor)
122 122
123 Using :command:`ipclusterz` in mpiexec/mpirun mode
123 Using :command:`ipcluster` in mpiexec/mpirun mode
124 124 --------------------------------------------------
125 125
126 126
127 127 The mpiexec/mpirun mode is useful if you:
128 128
129 129 1. Have MPI installed.
130 130 2. Your systems are configured to use the :command:`mpiexec` or
131 131 :command:`mpirun` commands to start MPI processes.
132 132
133 133 If these are satisfied, you can create a new profile::
134 134
135 $ ipclusterz create -p mpi
135 $ ipcluster create -p mpi
136 136
137 and edit the file :file:`IPYTHONDIR/clusterz_mpi/ipclusterz_config.py`.
137 and edit the file :file:`IPYTHONDIR/cluster_mpi/ipcluster_config.py`.
138 138
139 There, instruct ipclusterz to use the MPIExec launchers by adding the lines:
139 There, instruct ipcluster to use the MPIExec launchers by adding the lines:
140 140
141 141 .. sourcecode:: python
142 142
143 143 c.Global.engine_launcher = 'IPython.parallel.launcher.MPIExecEngineSetLauncher'
144 144
145 145 If the default MPI configuration is correct, then you can now start your cluster, with::
146 146
147 $ ipclusterz start -n 4 -p mpi
147 $ ipcluster start -n 4 -p mpi
148 148
149 149 This does the following:
150 150
151 151 1. Starts the IPython controller on current host.
152 152 2. Uses :command:`mpiexec` to start 4 engines.
153 153
154 154 If you have a reason to also start the Controller with mpi, you can specify:
155 155
156 156 .. sourcecode:: python
157 157
158 158 c.Global.controller_launcher = 'IPython.parallel.launcher.MPIExecControllerLauncher'
159 159
160 160 .. note::
161 161
162 162 The Controller *will not* be in the same MPI universe as the engines, so there is not
163 163 much reason to do this unless sysadmins demand it.
164 164
165 165 On newer MPI implementations (such as OpenMPI), this will work even if you
166 166 don't make any calls to MPI or call :func:`MPI_Init`. However, older MPI
167 167 implementations actually require each process to call :func:`MPI_Init` upon
168 168 starting. The easiest way of having this done is to install the mpi4py
169 [mpi4py]_ package and then specify the ``c.MPI.use`` option in :file:`ipenginez_config.py`:
169 [mpi4py]_ package and then specify the ``c.MPI.use`` option in :file:`ipengine_config.py`:
170 170
171 171 .. sourcecode:: python
172 172
173 173 c.MPI.use = 'mpi4py'
174 174
175 175 Unfortunately, even this won't work for some MPI implementations. If you are
176 176 having problems with this, you will likely have to use a custom Python
177 177 executable that itself calls :func:`MPI_Init` at the appropriate time.
178 178 Fortunately, mpi4py comes with such a custom Python executable that is easy to
179 179 install and use. However, this custom Python executable approach will not work
180 with :command:`ipclusterz` currently.
180 with :command:`ipcluster` currently.
181 181
182 182 More details on using MPI with IPython can be found :ref:`here <parallelmpi>`.
183 183
184 184
185 Using :command:`ipclusterz` in PBS mode
185 Using :command:`ipcluster` in PBS mode
186 186 ---------------------------------------
187 187
188 188 The PBS mode uses the Portable Batch System [PBS]_ to start the engines.
189 189
190 190 As usual, we will start by creating a fresh profile::
191 191
192 $ ipclusterz create -p pbs
192 $ ipcluster create -p pbs
193 193
194 And in :file:`ipclusterz_config.py`, we will select the PBS launchers for the controller
194 And in :file:`ipcluster_config.py`, we will select the PBS launchers for the controller
195 195 and engines:
196 196
197 197 .. sourcecode:: python
198 198
199 199 c.Global.controller_launcher = 'IPython.parallel.launcher.PBSControllerLauncher'
200 200 c.Global.engine_launcher = 'IPython.parallel.launcher.PBSEngineSetLauncher'
201 201
202 202 IPython does provide simple default batch templates for PBS and SGE, but you may need
203 203 to specify your own. Here is a sample PBS script template:
204 204
205 205 .. sourcecode:: bash
206 206
207 207 #PBS -N ipython
208 208 #PBS -j oe
209 209 #PBS -l walltime=00:10:00
210 210 #PBS -l nodes=${n/4}:ppn=4
211 211 #PBS -q $queue
212 212
213 213 cd $$PBS_O_WORKDIR
214 214 export PATH=$$HOME/usr/local/bin
215 215 export PYTHONPATH=$$HOME/usr/local/lib/python2.7/site-packages
216 /usr/local/bin/mpiexec -n ${n} ipenginez --cluster_dir=${cluster_dir}
216 /usr/local/bin/mpiexec -n ${n} ipengine --cluster_dir=${cluster_dir}
217 217
218 218 There are a few important points about this template:
219 219
220 220 1. This template will be rendered at runtime using IPython's :mod:`Itpl`
221 221 template engine.
222 222
223 223 2. Instead of putting in the actual number of engines, use the notation
224 224 ``${n}`` to indicate the number of engines to be started. You can also uses
225 225 expressions like ``${n/4}`` in the template to indicate the number of
226 226 nodes. There will always be a ${n} and ${cluster_dir} variable passed to the template.
227 227 These allow the batch system to know how many engines, and where the configuration
228 228 files reside. The same is true for the batch queue, with the template variable ``$queue``.
229 229
230 230 3. Because ``$`` is a special character used by the template engine, you must
231 231 escape any ``$`` by using ``$$``. This is important when referring to
232 232 environment variables in the template, or in SGE, where the config lines start
233 233 with ``#$``, which will have to be ``#$$``.
234 234
235 4. Any options to :command:`ipenginez` can be given in the batch script
236 template, or in :file:`ipenginez_config.py`.
235 4. Any options to :command:`ipengine` can be given in the batch script
236 template, or in :file:`ipengine_config.py`.
237 237
238 238 5. Depending on the configuration of you system, you may have to set
239 239 environment variables in the script template.
240 240
241 241 The controller template should be similar, but simpler:
242 242
243 243 .. sourcecode:: bash
244 244
245 245 #PBS -N ipython
246 246 #PBS -j oe
247 247 #PBS -l walltime=00:10:00
248 248 #PBS -l nodes=1:ppn=4
249 249 #PBS -q $queue
250 250
251 251 cd $$PBS_O_WORKDIR
252 252 export PATH=$$HOME/usr/local/bin
253 253 export PYTHONPATH=$$HOME/usr/local/lib/python2.7/site-packages
254 ipcontrollerz --cluster_dir=${cluster_dir}
254 ipcontroller --cluster_dir=${cluster_dir}
255 255
256 256
257 257 Once you have created these scripts, save them with names like
258 :file:`pbs.engine.template`. Now you can load them into the :file:`ipclusterz_config` with:
258 :file:`pbs.engine.template`. Now you can load them into the :file:`ipcluster_config` with:
259 259
260 260 .. sourcecode:: python
261 261
262 262 c.PBSEngineSetLauncher.batch_template_file = "pbs.engine.template"
263 263
264 264 c.PBSControllerLauncher.batch_template_file = "pbs.controller.template"
265 265
266 266
267 Alternately, you can just define the templates as strings inside :file:`ipclusterz_config`.
267 Alternately, you can just define the templates as strings inside :file:`ipcluster_config`.
268 268
269 269 Whether you are using your own templates or our defaults, the extra configurables available are
270 270 the number of engines to launch (``$n``, and the batch system queue to which the jobs are to be
271 271 submitted (``$queue``)). These are configurables, and can be specified in
272 :file:`ipclusterz_config`:
272 :file:`ipcluster_config`:
273 273
274 274 .. sourcecode:: python
275 275
276 276 c.PBSLauncher.queue = 'veryshort.q'
277 277 c.PBSEngineSetLauncher.n = 64
278 278
279 279 Note that assuming you are running PBS on a multi-node cluster, the Controller's default behavior
280 280 of listening only on localhost is likely too restrictive. In this case, also assuming the
281 281 nodes are safely behind a firewall, you can simply instruct the Controller to listen for
282 connections on all its interfaces, by adding in :file:`ipcontrollerz_config`:
282 connections on all its interfaces, by adding in :file:`ipcontroller_config`:
283 283
284 284 .. sourcecode:: python
285 285
286 286 c.RegistrationFactory.ip = '*'
287 287
288 288 You can now run the cluster with::
289 289
290 $ ipclusterz start -p pbs -n 128
290 $ ipcluster start -p pbs -n 128
291 291
292 Additional configuration options can be found in the PBS section of :file:`ipclusterz_config`.
292 Additional configuration options can be found in the PBS section of :file:`ipcluster_config`.
293 293
294 294 .. note::
295 295
296 296 Due to the flexibility of configuration, the PBS launchers work with simple changes
297 297 to the template for other :command:`qsub`-using systems, such as Sun Grid Engine,
298 298 and with further configuration in similar batch systems like Condor.
299 299
300 300
301 Using :command:`ipclusterz` in SSH mode
301 Using :command:`ipcluster` in SSH mode
302 302 ---------------------------------------
303 303
304 304
305 The SSH mode uses :command:`ssh` to execute :command:`ipenginez` on remote
306 nodes and :command:`ipcontrollerz` can be run remotely as well, or on localhost.
305 The SSH mode uses :command:`ssh` to execute :command:`ipengine` on remote
306 nodes and :command:`ipcontroller` can be run remotely as well, or on localhost.
307 307
308 308 .. note::
309 309
310 310 When using this mode it highly recommended that you have set up SSH keys
311 311 and are using ssh-agent [SSH]_ for password-less logins.
312 312
313 313 As usual, we start by creating a clean profile::
314 314
315 $ ipclusterz create -p ssh
315 $ ipcluster create -p ssh
316 316
317 To use this mode, select the SSH launchers in :file:`ipclusterz_config.py`:
317 To use this mode, select the SSH launchers in :file:`ipcluster_config.py`:
318 318
319 319 .. sourcecode:: python
320 320
321 321 c.Global.engine_launcher = 'IPython.parallel.launcher.SSHEngineSetLauncher'
322 322 # and if the Controller is also to be remote:
323 323 c.Global.controller_launcher = 'IPython.parallel.launcher.SSHControllerLauncher'
324 324
325 325
326 326 The controller's remote location and configuration can be specified:
327 327
328 328 .. sourcecode:: python
329 329
330 330 # Set the user and hostname for the controller
331 331 # c.SSHControllerLauncher.hostname = 'controller.example.com'
332 332 # c.SSHControllerLauncher.user = os.environ.get('USER','username')
333 333
334 # Set the arguments to be passed to ipcontrollerz
335 # note that remotely launched ipcontrollerz will not get the contents of
336 # the local ipcontrollerz_config.py unless it resides on the *remote host*
334 # Set the arguments to be passed to ipcontroller
335 # note that remotely launched ipcontroller will not get the contents of
336 # the local ipcontroller_config.py unless it resides on the *remote host*
337 337 # in the location specified by the --cluster_dir argument.
338 338 # c.SSHControllerLauncher.program_args = ['-r', '-ip', '0.0.0.0', '--cluster_dir', '/path/to/cd']
339 339
340 340 .. note::
341 341
342 342 SSH mode does not do any file movement, so you will need to distribute configuration
343 343 files manually. To aid in this, the `reuse_files` flag defaults to True for ssh-launched
344 344 Controllers, so you will only need to do this once, unless you override this flag back
345 345 to False.
346 346
347 347 Engines are specified in a dictionary, by hostname and the number of engines to be run
348 348 on that host.
349 349
350 350 .. sourcecode:: python
351 351
352 352 c.SSHEngineSetLauncher.engines = { 'host1.example.com' : 2,
353 353 'host2.example.com' : 5,
354 354 'host3.example.com' : (1, ['--cluster_dir', '/home/different/location']),
355 355 'host4.example.com' : 8 }
356 356
357 357 * The `engines` dict, where the keys are the host we want to run engines on and
358 358 the value is the number of engines to run on that host.
359 359 * on host3, the value is a tuple, where the number of engines is first, and the arguments
360 to be passed to :command:`ipenginez` are the second element.
360 to be passed to :command:`ipengine` are the second element.
361 361
362 362 For engines without explicitly specified arguments, the default arguments are set in
363 363 a single location:
364 364
365 365 .. sourcecode:: python
366 366
367 c.SSHEngineSetLauncher.engine_args = ['--cluster_dir', '/path/to/clusterz_ssh']
367 c.SSHEngineSetLauncher.engine_args = ['--cluster_dir', '/path/to/cluster_ssh']
368 368
369 Current limitations of the SSH mode of :command:`ipclusterz` are:
369 Current limitations of the SSH mode of :command:`ipcluster` are:
370 370
371 371 * Untested on Windows. Would require a working :command:`ssh` on Windows.
372 372 Also, we are using shell scripts to setup and execute commands on remote
373 373 hosts.
374 374 * No file movement -
375 375
376 Using the :command:`ipcontrollerz` and :command:`ipenginez` commands
376 Using the :command:`ipcontroller` and :command:`ipengine` commands
377 377 ====================================================================
378 378
379 It is also possible to use the :command:`ipcontrollerz` and :command:`ipenginez`
379 It is also possible to use the :command:`ipcontroller` and :command:`ipengine`
380 380 commands to start your controller and engines. This approach gives you full
381 381 control over all aspects of the startup process.
382 382
383 383 Starting the controller and engine on your local machine
384 384 --------------------------------------------------------
385 385
386 To use :command:`ipcontrollerz` and :command:`ipenginez` to start things on your
386 To use :command:`ipcontroller` and :command:`ipengine` to start things on your
387 387 local machine, do the following.
388 388
389 389 First start the controller::
390 390
391 $ ipcontrollerz
391 $ ipcontroller
392 392
393 393 Next, start however many instances of the engine you want using (repeatedly)
394 394 the command::
395 395
396 $ ipenginez
396 $ ipengine
397 397
398 398 The engines should start and automatically connect to the controller using the
399 JSON files in :file:`~/.ipython/clusterz_default/security`. You are now ready to use the
399 JSON files in :file:`~/.ipython/cluster_default/security`. You are now ready to use the
400 400 controller and engines from IPython.
401 401
402 402 .. warning::
403 403
404 404 The order of the above operations may be important. You *must*
405 405 start the controller before the engines, unless you are reusing connection
406 406 information (via `-r`), in which case ordering is not important.
407 407
408 408 .. note::
409 409
410 410 On some platforms (OS X), to put the controller and engine into the
411 411 background you may need to give these commands in the form ``(ipcontroller
412 412 &)`` and ``(ipengine &)`` (with the parentheses) for them to work
413 413 properly.
414 414
415 415 Starting the controller and engines on different hosts
416 416 ------------------------------------------------------
417 417
418 418 When the controller and engines are running on different hosts, things are
419 419 slightly more complicated, but the underlying ideas are the same:
420 420
421 1. Start the controller on a host using :command:`ipcontrollerz`.
421 1. Start the controller on a host using :command:`ipcontroller`.
422 422 2. Copy :file:`ipcontroller-engine.json` from :file:`~/.ipython/cluster_<profile>/security` on
423 423 the controller's host to the host where the engines will run.
424 3. Use :command:`ipenginez` on the engine's hosts to start the engines.
424 3. Use :command:`ipengine` on the engine's hosts to start the engines.
425 425
426 The only thing you have to be careful of is to tell :command:`ipenginez` where
426 The only thing you have to be careful of is to tell :command:`ipengine` where
427 427 the :file:`ipcontroller-engine.json` file is located. There are two ways you
428 428 can do this:
429 429
430 430 * Put :file:`ipcontroller-engine.json` in the :file:`~/.ipython/cluster_<profile>/security`
431 431 directory on the engine's host, where it will be found automatically.
432 * Call :command:`ipenginez` with the ``--file=full_path_to_the_file``
432 * Call :command:`ipengine` with the ``--file=full_path_to_the_file``
433 433 flag.
434 434
435 435 The ``--file`` flag works like this::
436 436
437 437 $ ipengine --file=/path/to/my/ipcontroller-engine.json
438 438
439 439 .. note::
440 440
441 441 If the controller's and engine's hosts all have a shared file system
442 442 (:file:`~/.ipython/cluster_<profile>/security` is the same on all of them), then things
443 443 will just work!
444 444
445 445 Make JSON files persistent
446 446 --------------------------
447 447
448 448 At fist glance it may seem that that managing the JSON files is a bit
449 449 annoying. Going back to the house and key analogy, copying the JSON around
450 450 each time you start the controller is like having to make a new key every time
451 451 you want to unlock the door and enter your house. As with your house, you want
452 452 to be able to create the key (or JSON file) once, and then simply use it at
453 453 any point in the future.
454 454
455 455 To do this, the only thing you have to do is specify the `-r` flag, so that
456 456 the connection information in the JSON files remains accurate::
457 457
458 $ ipcontrollerz -r
458 $ ipcontroller -r
459 459
460 460 Then, just copy the JSON files over the first time and you are set. You can
461 461 start and stop the controller and engines any many times as you want in the
462 462 future, just make sure to tell the controller to reuse the file.
463 463
464 464 .. note::
465 465
466 466 You may ask the question: what ports does the controller listen on if you
467 467 don't tell is to use specific ones? The default is to use high random port
468 468 numbers. We do this for two reasons: i) to increase security through
469 469 obscurity and ii) to multiple controllers on a given host to start and
470 470 automatically use different ports.
471 471
472 472 Log files
473 473 ---------
474 474
475 475 All of the components of IPython have log files associated with them.
476 476 These log files can be extremely useful in debugging problems with
477 477 IPython and can be found in the directory :file:`~/.ipython/cluster_<profile>/log`.
478 478 Sending the log files to us will often help us to debug any problems.
479 479
480 480
481 Configuring `ipcontrollerz`
481 Configuring `ipcontroller`
482 482 ---------------------------
483 483
484 484 Ports and addresses
485 485 *******************
486 486
487 487
488 488 Database Backend
489 489 ****************
490 490
491 491
492 492 .. seealso::
493 493
494 494
495 495
496 Configuring `ipenginez`
496 Configuring `ipengine`
497 497 -----------------------
498 498
499 499 .. note::
500 500
501 501 TODO
502 502
503 503
504 504
505 505 .. [PBS] Portable Batch System. http://www.openpbs.org/
506 506 .. [SSH] SSH-Agent http://en.wikipedia.org/wiki/ssh-agent
@@ -1,324 +1,324 b''
1 1 .. _parallelsecurity:
2 2
3 3 ===========================
4 4 Security details of IPython
5 5 ===========================
6 6
7 7 .. note::
8 8
9 9 This section is not thorough, and IPython.zmq needs a thorough security
10 10 audit.
11 11
12 12 IPython's :mod:`IPython.zmq` package exposes the full power of the
13 13 Python interpreter over a TCP/IP network for the purposes of parallel
14 14 computing. This feature brings up the important question of IPython's security
15 15 model. This document gives details about this model and how it is implemented
16 16 in IPython's architecture.
17 17
18 18 Processs and network topology
19 19 =============================
20 20
21 21 To enable parallel computing, IPython has a number of different processes that
22 22 run. These processes are discussed at length in the IPython documentation and
23 23 are summarized here:
24 24
25 25 * The IPython *engine*. This process is a full blown Python
26 26 interpreter in which user code is executed. Multiple
27 27 engines are started to make parallel computing possible.
28 28 * The IPython *hub*. This process monitors a set of
29 29 engines and schedulers, and keeps track of the state of the processes. It listens
30 30 for registration connections from engines and clients, and monitor connections
31 31 from schedulers.
32 32 * The IPython *schedulers*. This is a set of processes that relay commands and results
33 33 between clients and engines. They are typically on the same machine as the controller,
34 34 and listen for connections from engines and clients, but connect to the Hub.
35 35 * The IPython *client*. This process is typically an
36 36 interactive Python process that is used to coordinate the
37 37 engines to get a parallel computation done.
38 38
39 39 Collectively, these processes are called the IPython *kernel*, and the hub and schedulers
40 40 together are referred to as the *controller*.
41 41
42 42 .. note::
43 43
44 44 Are these really still referred to as the Kernel? It doesn't seem so to me. 'cluster'
45 45 seems more accurate.
46 46
47 47 -MinRK
48 48
49 49 These processes communicate over any transport supported by ZeroMQ (tcp,pgm,infiniband,ipc)
50 50 with a well defined topology. The IPython hub and schedulers listen on sockets. Upon
51 51 starting, an engine connects to a hub and registers itself, which then informs the engine
52 52 of the connection information for the schedulers, and the engine then connects to the
53 53 schedulers. These engine/hub and engine/scheduler connections persist for the
54 54 lifetime of each engine.
55 55
56 56 The IPython client also connects to the controller processes using a number of socket
57 57 connections. As of writing, this is one socket per scheduler (4), and 3 connections to the
58 58 hub for a total of 7. These connections persist for the lifetime of the client only.
59 59
60 60 A given IPython controller and set of engines engines typically has a relatively
61 61 short lifetime. Typically this lifetime corresponds to the duration of a single parallel
62 62 simulation performed by a single user. Finally, the hub, schedulers, engines, and client
63 63 processes typically execute with the permissions of that same user. More specifically, the
64 64 controller and engines are *not* executed as root or with any other superuser permissions.
65 65
66 66 Application logic
67 67 =================
68 68
69 69 When running the IPython kernel to perform a parallel computation, a user
70 70 utilizes the IPython client to send Python commands and data through the
71 71 IPython schedulers to the IPython engines, where those commands are executed
72 72 and the data processed. The design of IPython ensures that the client is the
73 73 only access point for the capabilities of the engines. That is, the only way
74 74 of addressing the engines is through a client.
75 75
76 76 A user can utilize the client to instruct the IPython engines to execute
77 77 arbitrary Python commands. These Python commands can include calls to the
78 78 system shell, access the filesystem, etc., as required by the user's
79 79 application code. From this perspective, when a user runs an IPython engine on
80 80 a host, that engine has the same capabilities and permissions as the user
81 81 themselves (as if they were logged onto the engine's host with a terminal).
82 82
83 83 Secure network connections
84 84 ==========================
85 85
86 86 Overview
87 87 --------
88 88
89 89 ZeroMQ provides exactly no security. For this reason, users of IPython must be very
90 90 careful in managing connections, because an open TCP/IP socket presents access to
91 91 arbitrary execution as the user on the engine machines. As a result, the default behavior
92 92 of controller processes is to only listen for clients on the loopback interface, and the
93 93 client must establish SSH tunnels to connect to the controller processes.
94 94
95 95 .. warning::
96 96
97 97 If the controller's loopback interface is untrusted, then IPython should be considered
98 98 vulnerable, and this extends to the loopback of all connected clients, which have
99 99 opened a loopback port that is redirected to the controller's loopback port.
100 100
101 101
102 102 SSH
103 103 ---
104 104
105 105 Since ZeroMQ provides no security, SSH tunnels are the primary source of secure
106 106 connections. A connector file, such as `ipcontroller-client.json`, will contain
107 107 information for connecting to the controller, possibly including the address of an
108 108 ssh-server through with the client is to tunnel. The Client object then creates tunnels
109 109 using either [OpenSSH]_ or [Paramiko]_, depending on the platform. If users do not wish to
110 110 use OpenSSH or Paramiko, or the tunneling utilities are insufficient, then they may
111 111 construct the tunnels themselves, and simply connect clients and engines as if the
112 112 controller were on loopback on the connecting machine.
113 113
114 114 .. note::
115 115
116 116 There is not currently tunneling available for engines.
117 117
118 118 Authentication
119 119 --------------
120 120
121 121 To protect users of shared machines, an execution key is used to authenticate all messages.
122 122
123 123 The Session object that handles the message protocol uses a unique key to verify valid
124 124 messages. This can be any value specified by the user, but the default behavior is a
125 125 pseudo-random 128-bit number, as generated by `uuid.uuid4()`. This key is checked on every
126 126 message everywhere it is unpacked (Controller, Engine, and Client) to ensure that it came
127 127 from an authentic user, and no messages that do not contain this key are acted upon in any
128 128 way.
129 129
130 130 There is exactly one key per cluster - it must be the same everywhere. Typically, the
131 131 controller creates this key, and stores it in the private connection files
132 132 `ipython-{engine|client}.json`. These files are typically stored in the
133 `~/.ipython/clusterz_<profile>/security` directory, and are maintained as readable only by
133 `~/.ipython/cluster_<profile>/security` directory, and are maintained as readable only by
134 134 the owner, just as is common practice with a user's keys in their `.ssh` directory.
135 135
136 136 .. warning::
137 137
138 138 It is important to note that the key authentication, as emphasized by the use of
139 139 a uuid rather than generating a key with a cryptographic library, provides a
140 140 defense against *accidental* messages more than it does against malicious attacks.
141 141 If loopback is compromised, it would be trivial for an attacker to intercept messages
142 142 and deduce the key, as there is no encryption.
143 143
144 144
145 145
146 146 Specific security vulnerabilities
147 147 =================================
148 148
149 149 There are a number of potential security vulnerabilities present in IPython's
150 150 architecture. In this section we discuss those vulnerabilities and detail how
151 151 the security architecture described above prevents them from being exploited.
152 152
153 153 Unauthorized clients
154 154 --------------------
155 155
156 156 The IPython client can instruct the IPython engines to execute arbitrary
157 157 Python code with the permissions of the user who started the engines. If an
158 158 attacker were able to connect their own hostile IPython client to the IPython
159 159 controller, they could instruct the engines to execute code.
160 160
161 161
162 162 On the first level, this attack is prevented by requiring access to the controller's
163 163 ports, which are recommended to only be open on loopback if the controller is on an
164 164 untrusted local network. If the attacker does have access to the Controller's ports, then
165 165 the attack is prevented by the capabilities based client authentication of the execution
166 166 key. The relevant authentication information is encoded into the JSON file that clients
167 167 must present to gain access to the IPython controller. By limiting the distribution of
168 168 those keys, a user can grant access to only authorized persons, just as with SSH keys.
169 169
170 170 It is highly unlikely that an execution key could be guessed by an attacker
171 171 in a brute force guessing attack. A given instance of the IPython controller
172 172 only runs for a relatively short amount of time (on the order of hours). Thus
173 173 an attacker would have only a limited amount of time to test a search space of
174 174 size 2**128.
175 175
176 176 .. warning::
177 177
178 178 If the attacker has gained enough access to intercept loopback connections on
179 179 *either* the controller or client, then the key is easily deduced from network
180 180 traffic.
181 181
182 182
183 183 Unauthorized engines
184 184 --------------------
185 185
186 186 If an attacker were able to connect a hostile engine to a user's controller,
187 187 the user might unknowingly send sensitive code or data to the hostile engine.
188 188 This attacker's engine would then have full access to that code and data.
189 189
190 190 This type of attack is prevented in the same way as the unauthorized client
191 191 attack, through the usage of the capabilities based authentication scheme.
192 192
193 193 Unauthorized controllers
194 194 ------------------------
195 195
196 196 It is also possible that an attacker could try to convince a user's IPython
197 197 client or engine to connect to a hostile IPython controller. That controller
198 198 would then have full access to the code and data sent between the IPython
199 199 client and the IPython engines.
200 200
201 201 Again, this attack is prevented through the capabilities in a connection file, which
202 202 ensure that a client or engine connects to the correct controller. It is also important to
203 203 note that the connection files also encode the IP address and port that the controller is
204 204 listening on, so there is little chance of mistakenly connecting to a controller running
205 205 on a different IP address and port.
206 206
207 207 When starting an engine or client, a user must specify the key to use
208 208 for that connection. Thus, in order to introduce a hostile controller, the
209 209 attacker must convince the user to use the key associated with the
210 210 hostile controller. As long as a user is diligent in only using keys from
211 211 trusted sources, this attack is not possible.
212 212
213 213 .. note::
214 214
215 215 I may be wrong, the unauthorized controller may be easier to fake than this.
216 216
217 217 Other security measures
218 218 =======================
219 219
220 220 A number of other measures are taken to further limit the security risks
221 221 involved in running the IPython kernel.
222 222
223 223 First, by default, the IPython controller listens on random port numbers.
224 224 While this can be overridden by the user, in the default configuration, an
225 225 attacker would have to do a port scan to even find a controller to attack.
226 226 When coupled with the relatively short running time of a typical controller
227 227 (on the order of hours), an attacker would have to work extremely hard and
228 228 extremely *fast* to even find a running controller to attack.
229 229
230 230 Second, much of the time, especially when run on supercomputers or clusters,
231 231 the controller is running behind a firewall. Thus, for engines or client to
232 232 connect to the controller:
233 233
234 234 * The different processes have to all be behind the firewall.
235 235
236 236 or:
237 237
238 238 * The user has to use SSH port forwarding to tunnel the
239 239 connections through the firewall.
240 240
241 241 In either case, an attacker is presented with additional barriers that prevent
242 242 attacking or even probing the system.
243 243
244 244 Summary
245 245 =======
246 246
247 247 IPython's architecture has been carefully designed with security in mind. The
248 248 capabilities based authentication model, in conjunction with SSH tunneled
249 249 TCP/IP channels, address the core potential vulnerabilities in the system,
250 250 while still enabling user's to use the system in open networks.
251 251
252 252 Other questions
253 253 ===============
254 254
255 255 .. note::
256 256
257 257 this does not apply to ZMQ, but I am sure there will be questions.
258 258
259 259 About keys
260 260 ----------
261 261
262 262 Can you clarify the roles of the certificate and its keys versus the FURL,
263 263 which is also called a key?
264 264
265 265 The certificate created by IPython processes is a standard public key x509
266 266 certificate, that is used by the SSL handshake protocol to setup encrypted
267 267 channel between the controller and the IPython engine or client. This public
268 268 and private key associated with this certificate are used only by the SSL
269 269 handshake protocol in setting up this encrypted channel.
270 270
271 271 The FURL serves a completely different and independent purpose from the
272 272 key pair associated with the certificate. When we refer to a FURL as a
273 273 key, we are using the word "key" in the capabilities based security model
274 274 sense. This has nothing to do with "key" in the public/private key sense used
275 275 in the SSL protocol.
276 276
277 277 With that said the FURL is used as an cryptographic key, to grant
278 278 IPython engines and clients access to particular capabilities that the
279 279 controller offers.
280 280
281 281 Self signed certificates
282 282 ------------------------
283 283
284 284 Is the controller creating a self-signed certificate? Is this created for per
285 285 instance/session, one-time-setup or each-time the controller is started?
286 286
287 287 The Foolscap network protocol, which handles the SSL protocol details, creates
288 288 a self-signed x509 certificate using OpenSSL for each IPython process. The
289 289 lifetime of the certificate is handled differently for the IPython controller
290 290 and the engines/client.
291 291
292 292 For the IPython engines and client, the certificate is only held in memory for
293 293 the lifetime of its process. It is never written to disk.
294 294
295 295 For the controller, the certificate can be created anew each time the
296 296 controller starts or it can be created once and reused each time the
297 297 controller starts. If at any point, the certificate is deleted, a new one is
298 298 created the next time the controller starts.
299 299
300 300 SSL private key
301 301 ---------------
302 302
303 303 How the private key (associated with the certificate) is distributed?
304 304
305 305 In the usual implementation of the SSL protocol, the private key is never
306 306 distributed. We follow this standard always.
307 307
308 308 SSL versus Foolscap authentication
309 309 ----------------------------------
310 310
311 311 Many SSL connections only perform one sided authentication (the server to the
312 312 client). How is the client authentication in IPython's system related to SSL
313 313 authentication?
314 314
315 315 We perform a two way SSL handshake in which both parties request and verify
316 316 the certificate of their peer. This mutual authentication is handled by the
317 317 SSL handshake and is separate and independent from the additional
318 318 authentication steps that the CLIENT and SERVER perform after an encrypted
319 319 channel is established.
320 320
321 321 .. [RFC5246] <http://tools.ietf.org/html/rfc5246>
322 322
323 323 .. [OpenSSH] <http://www.openssh.com/>
324 324 .. [Paramiko] <http://www.lag.net/paramiko/>
@@ -1,418 +1,418 b''
1 1 .. _parallel_task:
2 2
3 3 ==========================
4 4 The IPython task interface
5 5 ==========================
6 6
7 7 The task interface to the cluster presents the engines as a fault tolerant,
8 8 dynamic load-balanced system of workers. Unlike the multiengine interface, in
9 9 the task interface the user have no direct access to individual engines. By
10 10 allowing the IPython scheduler to assign work, this interface is simultaneously
11 11 simpler and more powerful.
12 12
13 13 Best of all, the user can use both of these interfaces running at the same time
14 14 to take advantage of their respective strengths. When the user can break up
15 15 the user's work into segments that do not depend on previous execution, the
16 16 task interface is ideal. But it also has more power and flexibility, allowing
17 17 the user to guide the distribution of jobs, without having to assign tasks to
18 18 engines explicitly.
19 19
20 20 Starting the IPython controller and engines
21 21 ===========================================
22 22
23 23 To follow along with this tutorial, you will need to start the IPython
24 24 controller and four IPython engines. The simplest way of doing this is to use
25 the :command:`ipclusterz` command::
25 the :command:`ipcluster` command::
26 26
27 $ ipclusterz start -n 4
27 $ ipcluster start -n 4
28 28
29 29 For more detailed information about starting the controller and engines, see
30 30 our :ref:`introduction <ip1par>` to using IPython for parallel computing.
31 31
32 32 Creating a ``Client`` instance
33 33 ==============================
34 34
35 35 The first step is to import the IPython :mod:`IPython.parallel.client`
36 36 module and then create a :class:`.Client` instance, and we will also be using
37 37 a :class:`LoadBalancedView`, here called `lview`:
38 38
39 39 .. sourcecode:: ipython
40 40
41 41 In [1]: from IPython.parallel import Client
42 42
43 43 In [2]: rc = Client()
44 44
45 45
46 46 This form assumes that the controller was started on localhost with default
47 47 configuration. If not, the location of the controller must be given as an
48 48 argument to the constructor:
49 49
50 50 .. sourcecode:: ipython
51 51
52 52 # for a visible LAN controller listening on an external port:
53 53 In [2]: rc = Client('tcp://192.168.1.16:10101')
54 54 # or to connect with a specific profile you have set up:
55 55 In [3]: rc = Client(profile='mpi')
56 56
57 57 For load-balanced execution, we will make use of a :class:`LoadBalancedView` object, which can
58 58 be constructed via the client's :meth:`load_balanced_view` method:
59 59
60 60 .. sourcecode:: ipython
61 61
62 62 In [4]: lview = rc.load_balanced_view() # default load-balanced view
63 63
64 64 .. seealso::
65 65
66 66 For more information, see the in-depth explanation of :ref:`Views <parallel_details>`.
67 67
68 68
69 69 Quick and easy parallelism
70 70 ==========================
71 71
72 72 In many cases, you simply want to apply a Python function to a sequence of
73 73 objects, but *in parallel*. Like the multiengine interface, these can be
74 74 implemented via the task interface. The exact same tools can perform these
75 75 actions in load-balanced ways as well as multiplexed ways: a parallel version
76 76 of :func:`map` and :func:`@parallel` function decorator. If one specifies the
77 77 argument `balanced=True`, then they are dynamically load balanced. Thus, if the
78 78 execution time per item varies significantly, you should use the versions in
79 79 the task interface.
80 80
81 81 Parallel map
82 82 ------------
83 83
84 84 To load-balance :meth:`map`,simply use a LoadBalancedView:
85 85
86 86 .. sourcecode:: ipython
87 87
88 88 In [62]: lview.block = True
89 89
90 90 In [63]: serial_result = map(lambda x:x**10, range(32))
91 91
92 92 In [64]: parallel_result = lview.map(lambda x:x**10, range(32))
93 93
94 94 In [65]: serial_result==parallel_result
95 95 Out[65]: True
96 96
97 97 Parallel function decorator
98 98 ---------------------------
99 99
100 100 Parallel functions are just like normal function, but they can be called on
101 101 sequences and *in parallel*. The multiengine interface provides a decorator
102 102 that turns any Python function into a parallel function:
103 103
104 104 .. sourcecode:: ipython
105 105
106 106 In [10]: @lview.parallel()
107 107 ....: def f(x):
108 108 ....: return 10.0*x**4
109 109 ....:
110 110
111 111 In [11]: f.map(range(32)) # this is done in parallel
112 112 Out[11]: [0.0,10.0,160.0,...]
113 113
114 114 .. _parallel_dependencies:
115 115
116 116 Dependencies
117 117 ============
118 118
119 119 Often, pure atomic load-balancing is too primitive for your work. In these cases, you
120 120 may want to associate some kind of `Dependency` that describes when, where, or whether
121 121 a task can be run. In IPython, we provide two types of dependencies:
122 122 `Functional Dependencies`_ and `Graph Dependencies`_
123 123
124 124 .. note::
125 125
126 126 It is important to note that the pure ZeroMQ scheduler does not support dependencies,
127 127 and you will see errors or warnings if you try to use dependencies with the pure
128 128 scheduler.
129 129
130 130 Functional Dependencies
131 131 -----------------------
132 132
133 133 Functional dependencies are used to determine whether a given engine is capable of running
134 134 a particular task. This is implemented via a special :class:`Exception` class,
135 135 :class:`UnmetDependency`, found in `IPython.parallel.error`. Its use is very simple:
136 136 if a task fails with an UnmetDependency exception, then the scheduler, instead of relaying
137 137 the error up to the client like any other error, catches the error, and submits the task
138 138 to a different engine. This will repeat indefinitely, and a task will never be submitted
139 139 to a given engine a second time.
140 140
141 141 You can manually raise the :class:`UnmetDependency` yourself, but IPython has provided
142 142 some decorators for facilitating this behavior.
143 143
144 144 There are two decorators and a class used for functional dependencies:
145 145
146 146 .. sourcecode:: ipython
147 147
148 148 In [9]: from IPython.parallel.dependency import depend, require, dependent
149 149
150 150 @require
151 151 ********
152 152
153 153 The simplest sort of dependency is requiring that a Python module is available. The
154 154 ``@require`` decorator lets you define a function that will only run on engines where names
155 155 you specify are importable:
156 156
157 157 .. sourcecode:: ipython
158 158
159 159 In [10]: @require('numpy', 'zmq')
160 160 ...: def myfunc():
161 161 ...: return dostuff()
162 162
163 163 Now, any time you apply :func:`myfunc`, the task will only run on a machine that has
164 164 numpy and pyzmq available, and when :func:`myfunc` is called, numpy and zmq will be imported.
165 165
166 166 @depend
167 167 *******
168 168
169 169 The ``@depend`` decorator lets you decorate any function with any *other* function to
170 170 evaluate the dependency. The dependency function will be called at the start of the task,
171 171 and if it returns ``False``, then the dependency will be considered unmet, and the task
172 172 will be assigned to another engine. If the dependency returns *anything other than
173 173 ``False``*, the rest of the task will continue.
174 174
175 175 .. sourcecode:: ipython
176 176
177 177 In [10]: def platform_specific(plat):
178 178 ...: import sys
179 179 ...: return sys.platform == plat
180 180
181 181 In [11]: @depend(platform_specific, 'darwin')
182 182 ...: def mactask():
183 183 ...: do_mac_stuff()
184 184
185 185 In [12]: @depend(platform_specific, 'nt')
186 186 ...: def wintask():
187 187 ...: do_windows_stuff()
188 188
189 189 In this case, any time you apply ``mytask``, it will only run on an OSX machine.
190 190 ``@depend`` is just like ``apply``, in that it has a ``@depend(f,*args,**kwargs)``
191 191 signature.
192 192
193 193 dependents
194 194 **********
195 195
196 196 You don't have to use the decorators on your tasks, if for instance you may want
197 197 to run tasks with a single function but varying dependencies, you can directly construct
198 198 the :class:`dependent` object that the decorators use:
199 199
200 200 .. sourcecode::ipython
201 201
202 202 In [13]: def mytask(*args):
203 203 ...: dostuff()
204 204
205 205 In [14]: mactask = dependent(mytask, platform_specific, 'darwin')
206 206 # this is the same as decorating the declaration of mytask with @depend
207 207 # but you can do it again:
208 208
209 209 In [15]: wintask = dependent(mytask, platform_specific, 'nt')
210 210
211 211 # in general:
212 212 In [16]: t = dependent(f, g, *dargs, **dkwargs)
213 213
214 214 # is equivalent to:
215 215 In [17]: @depend(g, *dargs, **dkwargs)
216 216 ...: def t(a,b,c):
217 217 ...: # contents of f
218 218
219 219 Graph Dependencies
220 220 ------------------
221 221
222 222 Sometimes you want to restrict the time and/or location to run a given task as a function
223 223 of the time and/or location of other tasks. This is implemented via a subclass of
224 224 :class:`set`, called a :class:`Dependency`. A Dependency is just a set of `msg_ids`
225 225 corresponding to tasks, and a few attributes to guide how to decide when the Dependency
226 226 has been met.
227 227
228 228 The switches we provide for interpreting whether a given dependency set has been met:
229 229
230 230 any|all
231 231 Whether the dependency is considered met if *any* of the dependencies are done, or
232 232 only after *all* of them have finished. This is set by a Dependency's :attr:`all`
233 233 boolean attribute, which defaults to ``True``.
234 234
235 235 success [default: True]
236 236 Whether to consider tasks that succeeded as fulfilling dependencies.
237 237
238 238 failure [default : False]
239 239 Whether to consider tasks that failed as fulfilling dependencies.
240 240 using `failure=True,success=False` is useful for setting up cleanup tasks, to be run
241 241 only when tasks have failed.
242 242
243 243 Sometimes you want to run a task after another, but only if that task succeeded. In this case,
244 244 ``success`` should be ``True`` and ``failure`` should be ``False``. However sometimes you may
245 245 not care whether the task succeeds, and always want the second task to run, in which case you
246 246 should use `success=failure=True`. The default behavior is to only use successes.
247 247
248 248 There are other switches for interpretation that are made at the *task* level. These are
249 249 specified via keyword arguments to the client's :meth:`apply` method.
250 250
251 251 after,follow
252 252 You may want to run a task *after* a given set of dependencies have been run and/or
253 253 run it *where* another set of dependencies are met. To support this, every task has an
254 254 `after` dependency to restrict time, and a `follow` dependency to restrict
255 255 destination.
256 256
257 257 timeout
258 258 You may also want to set a time-limit for how long the scheduler should wait before a
259 259 task's dependencies are met. This is done via a `timeout`, which defaults to 0, which
260 260 indicates that the task should never timeout. If the timeout is reached, and the
261 261 scheduler still hasn't been able to assign the task to an engine, the task will fail
262 262 with a :class:`DependencyTimeout`.
263 263
264 264 .. note::
265 265
266 266 Dependencies only work within the task scheduler. You cannot instruct a load-balanced
267 267 task to run after a job submitted via the MUX interface.
268 268
269 269 The simplest form of Dependencies is with `all=True,success=True,failure=False`. In these cases,
270 270 you can skip using Dependency objects, and just pass msg_ids or AsyncResult objects as the
271 271 `follow` and `after` keywords to :meth:`client.apply`:
272 272
273 273 .. sourcecode:: ipython
274 274
275 275 In [14]: client.block=False
276 276
277 277 In [15]: ar = lview.apply(f, args, kwargs)
278 278
279 279 In [16]: ar2 = lview.apply(f2)
280 280
281 281 In [17]: ar3 = lview.apply_with_flags(f3, after=[ar,ar2])
282 282
283 283 In [17]: ar4 = lview.apply_with_flags(f3, follow=[ar], timeout=2.5)
284 284
285 285
286 286 .. seealso::
287 287
288 288 Some parallel workloads can be described as a `Directed Acyclic Graph
289 289 <http://en.wikipedia.org/wiki/Directed_acyclic_graph>`_, or DAG. See :ref:`DAG
290 290 Dependencies <dag_dependencies>` for an example demonstrating how to use map a NetworkX DAG
291 291 onto task dependencies.
292 292
293 293
294 294
295 295 Impossible Dependencies
296 296 ***********************
297 297
298 298 The schedulers do perform some analysis on graph dependencies to determine whether they
299 299 are not possible to be met. If the scheduler does discover that a dependency cannot be
300 300 met, then the task will fail with an :class:`ImpossibleDependency` error. This way, if the
301 301 scheduler realized that a task can never be run, it won't sit indefinitely in the
302 302 scheduler clogging the pipeline.
303 303
304 304 The basic cases that are checked:
305 305
306 306 * depending on nonexistent messages
307 307 * `follow` dependencies were run on more than one machine and `all=True`
308 308 * any dependencies failed and `all=True,success=True,failures=False`
309 309 * all dependencies failed and `all=False,success=True,failure=False`
310 310
311 311 .. warning::
312 312
313 313 This analysis has not been proven to be rigorous, so it is likely possible for tasks
314 314 to become impossible to run in obscure situations, so a timeout may be a good choice.
315 315
316 316 .. _parallel_schedulers:
317 317
318 318 Schedulers
319 319 ==========
320 320
321 321 There are a variety of valid ways to determine where jobs should be assigned in a
322 322 load-balancing situation. In IPython, we support several standard schemes, and
323 323 even make it easy to define your own. The scheme can be selected via the ``--scheme``
324 argument to :command:`ipcontrollerz`, or in the :attr:`HubFactory.scheme` attribute
324 argument to :command:`ipcontroller`, or in the :attr:`HubFactory.scheme` attribute
325 325 of a controller config object.
326 326
327 327 The built-in routing schemes:
328 328
329 329 To select one of these schemes, simply do::
330 330
331 $ ipcontrollerz --scheme <schemename>
331 $ ipcontroller --scheme <schemename>
332 332 for instance:
333 $ ipcontrollerz --scheme lru
333 $ ipcontroller --scheme lru
334 334
335 335 lru: Least Recently Used
336 336
337 337 Always assign work to the least-recently-used engine. A close relative of
338 338 round-robin, it will be fair with respect to the number of tasks, agnostic
339 339 with respect to runtime of each task.
340 340
341 341 plainrandom: Plain Random
342 342
343 343 Randomly picks an engine on which to run.
344 344
345 345 twobin: Two-Bin Random
346 346
347 347 **Requires numpy**
348 348
349 349 Pick two engines at random, and use the LRU of the two. This is known to be better
350 350 than plain random in many cases, but requires a small amount of computation.
351 351
352 352 leastload: Least Load
353 353
354 354 **This is the default scheme**
355 355
356 356 Always assign tasks to the engine with the fewest outstanding tasks (LRU breaks tie).
357 357
358 358 weighted: Weighted Two-Bin Random
359 359
360 360 **Requires numpy**
361 361
362 362 Pick two engines at random using the number of outstanding tasks as inverse weights,
363 363 and use the one with the lower load.
364 364
365 365
366 366 Pure ZMQ Scheduler
367 367 ------------------
368 368
369 369 For maximum throughput, the 'pure' scheme is not Python at all, but a C-level
370 370 :class:`MonitoredQueue` from PyZMQ, which uses a ZeroMQ ``XREQ`` socket to perform all
371 371 load-balancing. This scheduler does not support any of the advanced features of the Python
372 372 :class:`.Scheduler`.
373 373
374 374 Disabled features when using the ZMQ Scheduler:
375 375
376 376 * Engine unregistration
377 377 Task farming will be disabled if an engine unregisters.
378 378 Further, if an engine is unregistered during computation, the scheduler may not recover.
379 379 * Dependencies
380 380 Since there is no Python logic inside the Scheduler, routing decisions cannot be made
381 381 based on message content.
382 382 * Early destination notification
383 383 The Python schedulers know which engine gets which task, and notify the Hub. This
384 384 allows graceful handling of Engines coming and going. There is no way to know
385 385 where ZeroMQ messages have gone, so there is no way to know what tasks are on which
386 386 engine until they *finish*. This makes recovery from engine shutdown very difficult.
387 387
388 388
389 389 .. note::
390 390
391 391 TODO: performance comparisons
392 392
393 393
394 394 More details
395 395 ============
396 396
397 397 The :class:`LoadBalancedView` has many more powerful features that allow quite a bit
398 398 of flexibility in how tasks are defined and run. The next places to look are
399 399 in the following classes:
400 400
401 401 * :class:`IPython.parallel.view.LoadBalancedView`
402 402 * :class:`IPython.parallel.asyncresult.AsyncResult`
403 403 * :meth:`IPython.parallel.view.LoadBalancedView.apply`
404 404 * :mod:`IPython.parallel.dependency`
405 405
406 406 The following is an overview of how to use these classes together:
407 407
408 408 1. Create a :class:`Client` and :class:`LoadBalancedView`
409 409 2. Define some functions to be run as tasks
410 410 3. Submit your tasks to using the :meth:`apply` method of your
411 411 :class:`LoadBalancedView` instance.
412 412 4. Use :meth:`Client.get_result` to get the results of the
413 413 tasks, or use the :meth:`AsyncResult.get` method of the results to wait
414 414 for and then receive the results.
415 415
416 416 .. seealso::
417 417
418 418 A demo of :ref:`DAG Dependencies <dag_dependencies>` with NetworkX and IPython.
1 NO CONTENT: file renamed from docs/source/parallelz/parallel_transition.txt to docs/source/parallel/parallel_transition.txt
@@ -1,334 +1,334 b''
1 1 ============================================
2 2 Getting started with Windows HPC Server 2008
3 3 ============================================
4 4
5 5 .. note::
6 6
7 7 Not adapted to zmq yet
8 8
9 9 Introduction
10 10 ============
11 11
12 12 The Python programming language is an increasingly popular language for
13 13 numerical computing. This is due to a unique combination of factors. First,
14 14 Python is a high-level and *interactive* language that is well matched to
15 15 interactive numerical work. Second, it is easy (often times trivial) to
16 16 integrate legacy C/C++/Fortran code into Python. Third, a large number of
17 17 high-quality open source projects provide all the needed building blocks for
18 18 numerical computing: numerical arrays (NumPy), algorithms (SciPy), 2D/3D
19 19 Visualization (Matplotlib, Mayavi, Chaco), Symbolic Mathematics (Sage, Sympy)
20 20 and others.
21 21
22 22 The IPython project is a core part of this open-source toolchain and is
23 23 focused on creating a comprehensive environment for interactive and
24 24 exploratory computing in the Python programming language. It enables all of
25 25 the above tools to be used interactively and consists of two main components:
26 26
27 27 * An enhanced interactive Python shell with support for interactive plotting
28 28 and visualization.
29 29 * An architecture for interactive parallel computing.
30 30
31 31 With these components, it is possible to perform all aspects of a parallel
32 32 computation interactively. This type of workflow is particularly relevant in
33 33 scientific and numerical computing where algorithms, code and data are
34 34 continually evolving as the user/developer explores a problem. The broad
35 35 treads in computing (commodity clusters, multicore, cloud computing, etc.)
36 36 make these capabilities of IPython particularly relevant.
37 37
38 38 While IPython is a cross platform tool, it has particularly strong support for
39 39 Windows based compute clusters running Windows HPC Server 2008. This document
40 40 describes how to get started with IPython on Windows HPC Server 2008. The
41 41 content and emphasis here is practical: installing IPython, configuring
42 42 IPython to use the Windows job scheduler and running example parallel programs
43 43 interactively. A more complete description of IPython's parallel computing
44 44 capabilities can be found in IPython's online documentation
45 45 (http://ipython.scipy.org/moin/Documentation).
46 46
47 47 Setting up your Windows cluster
48 48 ===============================
49 49
50 50 This document assumes that you already have a cluster running Windows
51 51 HPC Server 2008. Here is a broad overview of what is involved with setting up
52 52 such a cluster:
53 53
54 54 1. Install Windows Server 2008 on the head and compute nodes in the cluster.
55 55 2. Setup the network configuration on each host. Each host should have a
56 56 static IP address.
57 57 3. On the head node, activate the "Active Directory Domain Services" role
58 58 and make the head node the domain controller.
59 59 4. Join the compute nodes to the newly created Active Directory (AD) domain.
60 60 5. Setup user accounts in the domain with shared home directories.
61 61 6. Install the HPC Pack 2008 on the head node to create a cluster.
62 62 7. Install the HPC Pack 2008 on the compute nodes.
63 63
64 64 More details about installing and configuring Windows HPC Server 2008 can be
65 65 found on the Windows HPC Home Page (http://www.microsoft.com/hpc). Regardless
66 66 of what steps you follow to set up your cluster, the remainder of this
67 67 document will assume that:
68 68
69 69 * There are domain users that can log on to the AD domain and submit jobs
70 70 to the cluster scheduler.
71 71 * These domain users have shared home directories. While shared home
72 72 directories are not required to use IPython, they make it much easier to
73 73 use IPython.
74 74
75 75 Installation of IPython and its dependencies
76 76 ============================================
77 77
78 78 IPython and all of its dependencies are freely available and open source.
79 79 These packages provide a powerful and cost-effective approach to numerical and
80 80 scientific computing on Windows. The following dependencies are needed to run
81 81 IPython on Windows:
82 82
83 83 * Python 2.6 or 2.7 (http://www.python.org)
84 84 * pywin32 (http://sourceforge.net/projects/pywin32/)
85 85 * PyReadline (https://launchpad.net/pyreadline)
86 86 * pyzmq (http://github.com/zeromq/pyzmq/downloads)
87 87 * IPython (http://ipython.scipy.org)
88 88
89 89 In addition, the following dependencies are needed to run the demos described
90 90 in this document.
91 91
92 92 * NumPy and SciPy (http://www.scipy.org)
93 93 * Matplotlib (http://matplotlib.sourceforge.net/)
94 94
95 95 The easiest way of obtaining these dependencies is through the Enthought
96 96 Python Distribution (EPD) (http://www.enthought.com/products/epd.php). EPD is
97 97 produced by Enthought, Inc. and contains all of these packages and others in a
98 98 single installer and is available free for academic users. While it is also
99 99 possible to download and install each package individually, this is a tedious
100 100 process. Thus, we highly recommend using EPD to install these packages on
101 101 Windows.
102 102
103 103 Regardless of how you install the dependencies, here are the steps you will
104 104 need to follow:
105 105
106 106 1. Install all of the packages listed above, either individually or using EPD
107 107 on the head node, compute nodes and user workstations.
108 108
109 109 2. Make sure that :file:`C:\\Python27` and :file:`C:\\Python27\\Scripts` are
110 110 in the system :envvar:`%PATH%` variable on each node.
111 111
112 112 3. Install the latest development version of IPython. This can be done by
113 113 downloading the the development version from the IPython website
114 114 (http://ipython.scipy.org) and following the installation instructions.
115 115
116 116 Further details about installing IPython or its dependencies can be found in
117 117 the online IPython documentation (http://ipython.scipy.org/moin/Documentation)
118 118 Once you are finished with the installation, you can try IPython out by
119 119 opening a Windows Command Prompt and typing ``ipython``. This will
120 120 start IPython's interactive shell and you should see something like the
121 121 following screenshot:
122 122
123 123 .. image:: ipython_shell.*
124 124
125 125 Starting an IPython cluster
126 126 ===========================
127 127
128 128 To use IPython's parallel computing capabilities, you will need to start an
129 129 IPython cluster. An IPython cluster consists of one controller and multiple
130 130 engines:
131 131
132 132 IPython controller
133 133 The IPython controller manages the engines and acts as a gateway between
134 134 the engines and the client, which runs in the user's interactive IPython
135 135 session. The controller is started using the :command:`ipcontroller`
136 136 command.
137 137
138 138 IPython engine
139 139 IPython engines run a user's Python code in parallel on the compute nodes.
140 140 Engines are starting using the :command:`ipengine` command.
141 141
142 142 Once these processes are started, a user can run Python code interactively and
143 143 in parallel on the engines from within the IPython shell using an appropriate
144 144 client. This includes the ability to interact with, plot and visualize data
145 145 from the engines.
146 146
147 IPython has a command line program called :command:`ipclusterz` that automates
147 IPython has a command line program called :command:`ipcluster` that automates
148 148 all aspects of starting the controller and engines on the compute nodes.
149 :command:`ipclusterz` has full support for the Windows HPC job scheduler,
150 meaning that :command:`ipclusterz` can use this job scheduler to start the
149 :command:`ipcluster` has full support for the Windows HPC job scheduler,
150 meaning that :command:`ipcluster` can use this job scheduler to start the
151 151 controller and engines. In our experience, the Windows HPC job scheduler is
152 152 particularly well suited for interactive applications, such as IPython. Once
153 :command:`ipclusterz` is configured properly, a user can start an IPython
153 :command:`ipcluster` is configured properly, a user can start an IPython
154 154 cluster from their local workstation almost instantly, without having to log
155 155 on to the head node (as is typically required by Unix based job schedulers).
156 156 This enables a user to move seamlessly between serial and parallel
157 157 computations.
158 158
159 In this section we show how to use :command:`ipclusterz` to start an IPython
159 In this section we show how to use :command:`ipcluster` to start an IPython
160 160 cluster using the Windows HPC Server 2008 job scheduler. To make sure that
161 :command:`ipclusterz` is installed and working properly, you should first try
161 :command:`ipcluster` is installed and working properly, you should first try
162 162 to start an IPython cluster on your local host. To do this, open a Windows
163 163 Command Prompt and type the following command::
164 164
165 ipclusterz start -n 2
165 ipcluster start -n 2
166 166
167 167 You should see a number of messages printed to the screen, ending with
168 168 "IPython cluster: started". The result should look something like the following
169 169 screenshot:
170 170
171 171 .. image:: ipcluster_start.*
172 172
173 173 At this point, the controller and two engines are running on your local host.
174 174 This configuration is useful for testing and for situations where you want to
175 175 take advantage of multiple cores on your local computer.
176 176
177 Now that we have confirmed that :command:`ipclusterz` is working properly, we
177 Now that we have confirmed that :command:`ipcluster` is working properly, we
178 178 describe how to configure and run an IPython cluster on an actual compute
179 179 cluster running Windows HPC Server 2008. Here is an outline of the needed
180 180 steps:
181 181
182 1. Create a cluster profile using: ``ipclusterz create -p mycluster``
182 1. Create a cluster profile using: ``ipcluster create -p mycluster``
183 183
184 184 2. Edit configuration files in the directory :file:`.ipython\\cluster_mycluster`
185 185
186 186 3. Start the cluster using: ``ipcluser start -p mycluster -n 32``
187 187
188 188 Creating a cluster profile
189 189 --------------------------
190 190
191 191 In most cases, you will have to create a cluster profile to use IPython on a
192 192 cluster. A cluster profile is a name (like "mycluster") that is associated
193 193 with a particular cluster configuration. The profile name is used by
194 :command:`ipclusterz` when working with the cluster.
194 :command:`ipcluster` when working with the cluster.
195 195
196 196 Associated with each cluster profile is a cluster directory. This cluster
197 197 directory is a specially named directory (typically located in the
198 198 :file:`.ipython` subdirectory of your home directory) that contains the
199 199 configuration files for a particular cluster profile, as well as log files and
200 200 security keys. The naming convention for cluster directories is:
201 201 :file:`cluster_<profile name>`. Thus, the cluster directory for a profile named
202 202 "foo" would be :file:`.ipython\\cluster_foo`.
203 203
204 204 To create a new cluster profile (named "mycluster") and the associated cluster
205 205 directory, type the following command at the Windows Command Prompt::
206 206
207 ipclusterz create -p mycluster
207 ipcluster create -p mycluster
208 208
209 209 The output of this command is shown in the screenshot below. Notice how
210 :command:`ipclusterz` prints out the location of the newly created cluster
210 :command:`ipcluster` prints out the location of the newly created cluster
211 211 directory.
212 212
213 213 .. image:: ipcluster_create.*
214 214
215 215 Configuring a cluster profile
216 216 -----------------------------
217 217
218 218 Next, you will need to configure the newly created cluster profile by editing
219 219 the following configuration files in the cluster directory:
220 220
221 * :file:`ipclusterz_config.py`
221 * :file:`ipcluster_config.py`
222 222 * :file:`ipcontroller_config.py`
223 223 * :file:`ipengine_config.py`
224 224
225 When :command:`ipclusterz` is run, these configuration files are used to
225 When :command:`ipcluster` is run, these configuration files are used to
226 226 determine how the engines and controller will be started. In most cases,
227 227 you will only have to set a few of the attributes in these files.
228 228
229 To configure :command:`ipclusterz` to use the Windows HPC job scheduler, you
229 To configure :command:`ipcluster` to use the Windows HPC job scheduler, you
230 230 will need to edit the following attributes in the file
231 :file:`ipclusterz_config.py`::
231 :file:`ipcluster_config.py`::
232 232
233 # Set these at the top of the file to tell ipclusterz to use the
233 # Set these at the top of the file to tell ipcluster to use the
234 234 # Windows HPC job scheduler.
235 235 c.Global.controller_launcher = \
236 236 'IPython.parallel.launcher.WindowsHPCControllerLauncher'
237 237 c.Global.engine_launcher = \
238 238 'IPython.parallel.launcher.WindowsHPCEngineSetLauncher'
239 239
240 240 # Set these to the host name of the scheduler (head node) of your cluster.
241 241 c.WindowsHPCControllerLauncher.scheduler = 'HEADNODE'
242 242 c.WindowsHPCEngineSetLauncher.scheduler = 'HEADNODE'
243 243
244 244 There are a number of other configuration attributes that can be set, but
245 245 in most cases these will be sufficient to get you started.
246 246
247 247 .. warning::
248 248 If any of your configuration attributes involve specifying the location
249 249 of shared directories or files, you must make sure that you use UNC paths
250 250 like :file:`\\\\host\\share`. It is also important that you specify
251 251 these paths using raw Python strings: ``r'\\host\share'`` to make sure
252 252 that the backslashes are properly escaped.
253 253
254 254 Starting the cluster profile
255 255 ----------------------------
256 256
257 257 Once a cluster profile has been configured, starting an IPython cluster using
258 258 the profile is simple::
259 259
260 ipclusterz start -p mycluster -n 32
260 ipcluster start -p mycluster -n 32
261 261
262 The ``-n`` option tells :command:`ipclusterz` how many engines to start (in
262 The ``-n`` option tells :command:`ipcluster` how many engines to start (in
263 263 this case 32). Stopping the cluster is as simple as typing Control-C.
264 264
265 265 Using the HPC Job Manager
266 266 -------------------------
267 267
268 When ``ipclusterz start`` is run the first time, :command:`ipclusterz` creates
268 When ``ipcluster start`` is run the first time, :command:`ipcluster` creates
269 269 two XML job description files in the cluster directory:
270 270
271 271 * :file:`ipcontroller_job.xml`
272 272 * :file:`ipengineset_job.xml`
273 273
274 274 Once these files have been created, they can be imported into the HPC Job
275 275 Manager application. Then, the controller and engines for that profile can be
276 started using the HPC Job Manager directly, without using :command:`ipclusterz`.
277 However, anytime the cluster profile is re-configured, ``ipclusterz start``
276 started using the HPC Job Manager directly, without using :command:`ipcluster`.
277 However, anytime the cluster profile is re-configured, ``ipcluster start``
278 278 must be run again to regenerate the XML job description files. The
279 279 following screenshot shows what the HPC Job Manager interface looks like
280 280 with a running IPython cluster.
281 281
282 282 .. image:: hpc_job_manager.*
283 283
284 284 Performing a simple interactive parallel computation
285 285 ====================================================
286 286
287 287 Once you have started your IPython cluster, you can start to use it. To do
288 288 this, open up a new Windows Command Prompt and start up IPython's interactive
289 289 shell by typing::
290 290
291 291 ipython
292 292
293 293 Then you can create a :class:`MultiEngineClient` instance for your profile and
294 294 use the resulting instance to do a simple interactive parallel computation. In
295 295 the code and screenshot that follows, we take a simple Python function and
296 296 apply it to each element of an array of integers in parallel using the
297 297 :meth:`MultiEngineClient.map` method:
298 298
299 299 .. sourcecode:: ipython
300 300
301 301 In [1]: from IPython.parallel import *
302 302
303 303 In [2]: c = MultiEngineClient(profile='mycluster')
304 304
305 305 In [3]: mec.get_ids()
306 306 Out[3]: [0, 1, 2, 3, 4, 5, 67, 8, 9, 10, 11, 12, 13, 14]
307 307
308 308 In [4]: def f(x):
309 309 ...: return x**10
310 310
311 311 In [5]: mec.map(f, range(15)) # f is applied in parallel
312 312 Out[5]:
313 313 [0,
314 314 1,
315 315 1024,
316 316 59049,
317 317 1048576,
318 318 9765625,
319 319 60466176,
320 320 282475249,
321 321 1073741824,
322 322 3486784401L,
323 323 10000000000L,
324 324 25937424601L,
325 325 61917364224L,
326 326 137858491849L,
327 327 289254654976L]
328 328
329 329 The :meth:`map` method has the same signature as Python's builtin :func:`map`
330 330 function, but runs the calculation in parallel. More involved examples of using
331 331 :class:`MultiEngineClient` are provided in the examples that follow.
332 332
333 333 .. image:: mec_simple.*
334 334
1 NO CONTENT: file renamed from docs/source/parallelz/simpledag.pdf to docs/source/parallel/simpledag.pdf
1 NO CONTENT: file renamed from docs/source/parallelz/simpledag.png to docs/source/parallel/simpledag.png
1 NO CONTENT: file renamed from docs/source/parallelz/single_digits.pdf to docs/source/parallel/single_digits.pdf
1 NO CONTENT: file renamed from docs/source/parallelz/single_digits.png to docs/source/parallel/single_digits.png
1 NO CONTENT: file renamed from docs/source/parallelz/two_digit_counts.pdf to docs/source/parallel/two_digit_counts.pdf
1 NO CONTENT: file renamed from docs/source/parallelz/two_digit_counts.png to docs/source/parallel/two_digit_counts.png
1 NO CONTENT: file renamed from docs/source/parallelz/winhpc_index.txt to docs/source/parallel/winhpc_index.txt
@@ -1,252 +1,252 b''
1 1 #!/usr/bin/env python
2 2 # -*- coding: utf-8 -*-
3 3 """Setup script for IPython.
4 4
5 5 Under Posix environments it works like a typical setup.py script.
6 6 Under Windows, the command sdist is not supported, since IPython
7 7 requires utilities which are not available under Windows."""
8 8
9 9 #-----------------------------------------------------------------------------
10 10 # Copyright (c) 2008-2010, IPython Development Team.
11 11 # Copyright (c) 2001-2007, Fernando Perez <fernando.perez@colorado.edu>
12 12 # Copyright (c) 2001, Janko Hauser <jhauser@zscout.de>
13 13 # Copyright (c) 2001, Nathaniel Gray <n8gray@caltech.edu>
14 14 #
15 15 # Distributed under the terms of the Modified BSD License.
16 16 #
17 17 # The full license is in the file COPYING.txt, distributed with this software.
18 18 #-----------------------------------------------------------------------------
19 19
20 20 #-----------------------------------------------------------------------------
21 21 # Minimal Python version sanity check
22 22 #-----------------------------------------------------------------------------
23 23
24 24 import sys
25 25
26 26 # This check is also made in IPython/__init__, don't forget to update both when
27 27 # changing Python version requirements.
28 28 if sys.version[0:3] < '2.6':
29 29 error = """\
30 30 ERROR: 'IPython requires Python Version 2.6 or above.'
31 31 Exiting."""
32 32 print >> sys.stderr, error
33 33 sys.exit(1)
34 34
35 35 # At least we're on the python version we need, move on.
36 36
37 37 #-------------------------------------------------------------------------------
38 38 # Imports
39 39 #-------------------------------------------------------------------------------
40 40
41 41 # Stdlib imports
42 42 import os
43 43 import shutil
44 44
45 45 from glob import glob
46 46
47 47 # BEFORE importing distutils, remove MANIFEST. distutils doesn't properly
48 48 # update it when the contents of directories change.
49 49 if os.path.exists('MANIFEST'): os.remove('MANIFEST')
50 50
51 51 from distutils.core import setup
52 52
53 53 # Our own imports
54 54 from IPython.utils.path import target_update
55 55
56 56 from setupbase import (
57 57 setup_args,
58 58 find_packages,
59 59 find_package_data,
60 60 find_scripts,
61 61 find_data_files,
62 62 check_for_dependencies,
63 63 record_commit_info,
64 64 )
65 65
66 66 isfile = os.path.isfile
67 67 pjoin = os.path.join
68 68
69 69 #-----------------------------------------------------------------------------
70 70 # Function definitions
71 71 #-----------------------------------------------------------------------------
72 72
73 73 def cleanup():
74 74 """Clean up the junk left around by the build process"""
75 75 if "develop" not in sys.argv:
76 76 try:
77 77 shutil.rmtree('ipython.egg-info')
78 78 except:
79 79 try:
80 80 os.unlink('ipython.egg-info')
81 81 except:
82 82 pass
83 83
84 84 #-------------------------------------------------------------------------------
85 85 # Handle OS specific things
86 86 #-------------------------------------------------------------------------------
87 87
88 88 if os.name == 'posix':
89 89 os_name = 'posix'
90 90 elif os.name in ['nt','dos']:
91 91 os_name = 'windows'
92 92 else:
93 93 print 'Unsupported operating system:',os.name
94 94 sys.exit(1)
95 95
96 96 # Under Windows, 'sdist' has not been supported. Now that the docs build with
97 97 # Sphinx it might work, but let's not turn it on until someone confirms that it
98 98 # actually works.
99 99 if os_name == 'windows' and 'sdist' in sys.argv:
100 100 print 'The sdist command is not available under Windows. Exiting.'
101 101 sys.exit(1)
102 102
103 103 #-------------------------------------------------------------------------------
104 104 # Things related to the IPython documentation
105 105 #-------------------------------------------------------------------------------
106 106
107 107 # update the manuals when building a source dist
108 108 if len(sys.argv) >= 2 and sys.argv[1] in ('sdist','bdist_rpm'):
109 109 import textwrap
110 110
111 111 # List of things to be updated. Each entry is a triplet of args for
112 112 # target_update()
113 113 to_update = [
114 114 # FIXME - Disabled for now: we need to redo an automatic way
115 115 # of generating the magic info inside the rst.
116 116 #('docs/magic.tex',
117 117 #['IPython/Magic.py'],
118 118 #"cd doc && ./update_magic.sh" ),
119 119
120 120 ('docs/man/ipcluster.1.gz',
121 121 ['docs/man/ipcluster.1'],
122 122 'cd docs/man && gzip -9c ipcluster.1 > ipcluster.1.gz'),
123 123
124 124 ('docs/man/ipcontroller.1.gz',
125 125 ['docs/man/ipcontroller.1'],
126 126 'cd docs/man && gzip -9c ipcontroller.1 > ipcontroller.1.gz'),
127 127
128 128 ('docs/man/ipengine.1.gz',
129 129 ['docs/man/ipengine.1'],
130 130 'cd docs/man && gzip -9c ipengine.1 > ipengine.1.gz'),
131 131
132 132 ('docs/man/ipython.1.gz',
133 133 ['docs/man/ipython.1'],
134 134 'cd docs/man && gzip -9c ipython.1 > ipython.1.gz'),
135 135
136 136 ('docs/man/ipython-wx.1.gz',
137 137 ['docs/man/ipython-wx.1'],
138 138 'cd docs/man && gzip -9c ipython-wx.1 > ipython-wx.1.gz'),
139 139
140 140 ('docs/man/ipythonx.1.gz',
141 141 ['docs/man/ipythonx.1'],
142 142 'cd docs/man && gzip -9c ipythonx.1 > ipythonx.1.gz'),
143 143
144 144 ('docs/man/irunner.1.gz',
145 145 ['docs/man/irunner.1'],
146 146 'cd docs/man && gzip -9c irunner.1 > irunner.1.gz'),
147 147
148 148 ('docs/man/pycolor.1.gz',
149 149 ['docs/man/pycolor.1'],
150 150 'cd docs/man && gzip -9c pycolor.1 > pycolor.1.gz'),
151 151 ]
152 152
153 153 # Only build the docs if sphinx is present
154 154 try:
155 155 import sphinx
156 156 except ImportError:
157 157 pass
158 158 else:
159 159 # The Makefile calls the do_sphinx scripts to build html and pdf, so
160 160 # just one target is enough to cover all manual generation
161 161
162 162 # First, compute all the dependencies that can force us to rebuild the
163 163 # docs. Start with the main release file that contains metadata
164 164 docdeps = ['IPython/core/release.py']
165 165 # Inculde all the reST sources
166 166 pjoin = os.path.join
167 167 for dirpath,dirnames,filenames in os.walk('docs/source'):
168 168 if dirpath in ['_static','_templates']:
169 169 continue
170 170 docdeps += [ pjoin(dirpath,f) for f in filenames
171 171 if f.endswith('.txt') ]
172 172 # and the examples
173 173 for dirpath,dirnames,filenames in os.walk('docs/example'):
174 174 docdeps += [ pjoin(dirpath,f) for f in filenames
175 175 if not f.endswith('~') ]
176 176 # then, make them all dependencies for the main PDF (the html will get
177 177 # auto-generated as well).
178 178 to_update.append(
179 179 ('docs/dist/ipython.pdf',
180 180 docdeps,
181 181 "cd docs && make dist")
182 182 )
183 183
184 184 [ target_update(*t) for t in to_update ]
185 185
186 186 #---------------------------------------------------------------------------
187 187 # Find all the packages, package data, scripts and data_files
188 188 #---------------------------------------------------------------------------
189 189
190 190 packages = find_packages()
191 191 package_data = find_package_data()
192 192 scripts = find_scripts()
193 193 data_files = find_data_files()
194 194
195 195 #---------------------------------------------------------------------------
196 196 # Handle dependencies and setuptools specific things
197 197 #---------------------------------------------------------------------------
198 198
199 199 # For some commands, use setuptools. Note that we do NOT list install here!
200 200 # If you want a setuptools-enhanced install, just run 'setupegg.py install'
201 201 if len(set(('develop', 'sdist', 'release', 'bdist_egg', 'bdist_rpm',
202 202 'bdist', 'bdist_dumb', 'bdist_wininst', 'install_egg_info',
203 203 'build_sphinx', 'egg_info', 'easy_install', 'upload',
204 204 )).intersection(sys.argv)) > 0:
205 205 import setuptools
206 206
207 207 # This dict is used for passing extra arguments that are setuptools
208 208 # specific to setup
209 209 setuptools_extra_args = {}
210 210
211 211 if 'setuptools' in sys.modules:
212 212 setuptools_extra_args['zip_safe'] = False
213 213 setuptools_extra_args['entry_points'] = {
214 214 'console_scripts': [
215 215 'ipython = IPython.frontend.terminal.ipapp:launch_new_instance',
216 216 'ipython-qtconsole = IPython.frontend.qt.console.ipythonqt:main',
217 217 'pycolor = IPython.utils.PyColorize:main',
218 'ipcontrollerz = IPython.parallel.ipcontrollerapp:launch_new_instance',
219 'ipenginez = IPython.parallel.ipengineapp:launch_new_instance',
220 'iploggerz = IPython.parallel.iploggerapp:launch_new_instance',
221 'ipclusterz = IPython.parallel.ipclusterapp:launch_new_instance',
218 'ipcontroller = IPython.parallel.ipcontrollerapp:launch_new_instance',
219 'ipengine = IPython.parallel.ipengineapp:launch_new_instance',
220 'iplogger = IPython.parallel.iploggerapp:launch_new_instance',
221 'ipcluster = IPython.parallel.ipclusterapp:launch_new_instance',
222 222 'iptest = IPython.testing.iptest:main',
223 223 'irunner = IPython.lib.irunner:main'
224 224 ]
225 225 }
226 226 setup_args['extras_require'] = dict(
227 zmq = 'pyzmq>=2.0.10',
227 zmq = 'pyzmq>=2.0.10.1',
228 228 doc='Sphinx>=0.3',
229 229 test='nose>=0.10.1',
230 230 security='pyOpenSSL>=0.6'
231 231 )
232 232 else:
233 233 # If we are running without setuptools, call this function which will
234 234 # check for dependencies an inform the user what is needed. This is
235 235 # just to make life easy for users.
236 236 check_for_dependencies()
237 237
238 238 #---------------------------------------------------------------------------
239 239 # Do the actual setup now
240 240 #---------------------------------------------------------------------------
241 241
242 242 setup_args['cmdclass'] = {'build_py': record_commit_info('IPython')}
243 243 setup_args['packages'] = packages
244 244 setup_args['package_data'] = package_data
245 245 setup_args['scripts'] = scripts
246 246 setup_args['data_files'] = data_files
247 247 setup_args.update(setuptools_extra_args)
248 248
249 249
250 250 if __name__ == '__main__':
251 251 setup(**setup_args)
252 252 cleanup()
@@ -1,380 +1,380 b''
1 1 # encoding: utf-8
2 2 """
3 3 This module defines the things that are used in setup.py for building IPython
4 4
5 5 This includes:
6 6
7 7 * The basic arguments to setup
8 8 * Functions for finding things like packages, package data, etc.
9 9 * A function for checking dependencies.
10 10 """
11 11 from __future__ import print_function
12 12
13 13 #-------------------------------------------------------------------------------
14 14 # Copyright (C) 2008 The IPython Development Team
15 15 #
16 16 # Distributed under the terms of the BSD License. The full license is in
17 17 # the file COPYING, distributed as part of this software.
18 18 #-------------------------------------------------------------------------------
19 19
20 20 #-------------------------------------------------------------------------------
21 21 # Imports
22 22 #-------------------------------------------------------------------------------
23 23 import os
24 24 import sys
25 25
26 26 from ConfigParser import ConfigParser
27 27 from distutils.command.build_py import build_py
28 28 from glob import glob
29 29
30 30 from setupext import install_data_ext
31 31
32 32 #-------------------------------------------------------------------------------
33 33 # Useful globals and utility functions
34 34 #-------------------------------------------------------------------------------
35 35
36 36 # A few handy globals
37 37 isfile = os.path.isfile
38 38 pjoin = os.path.join
39 39
40 40 def oscmd(s):
41 41 print(">", s)
42 42 os.system(s)
43 43
44 44 # A little utility we'll need below, since glob() does NOT allow you to do
45 45 # exclusion on multiple endings!
46 46 def file_doesnt_endwith(test,endings):
47 47 """Return true if test is a file and its name does NOT end with any
48 48 of the strings listed in endings."""
49 49 if not isfile(test):
50 50 return False
51 51 for e in endings:
52 52 if test.endswith(e):
53 53 return False
54 54 return True
55 55
56 56 #---------------------------------------------------------------------------
57 57 # Basic project information
58 58 #---------------------------------------------------------------------------
59 59
60 60 # release.py contains version, authors, license, url, keywords, etc.
61 61 execfile(pjoin('IPython','core','release.py'))
62 62
63 63 # Create a dict with the basic information
64 64 # This dict is eventually passed to setup after additional keys are added.
65 65 setup_args = dict(
66 66 name = name,
67 67 version = version,
68 68 description = description,
69 69 long_description = long_description,
70 70 author = author,
71 71 author_email = author_email,
72 72 url = url,
73 73 download_url = download_url,
74 74 license = license,
75 75 platforms = platforms,
76 76 keywords = keywords,
77 77 cmdclass = {'install_data': install_data_ext},
78 78 )
79 79
80 80
81 81 #---------------------------------------------------------------------------
82 82 # Find packages
83 83 #---------------------------------------------------------------------------
84 84
85 85 def add_package(packages,pname,config=False,tests=False,scripts=False,
86 86 others=None):
87 87 """
88 88 Add a package to the list of packages, including certain subpackages.
89 89 """
90 90 packages.append('.'.join(['IPython',pname]))
91 91 if config:
92 92 packages.append('.'.join(['IPython',pname,'config']))
93 93 if tests:
94 94 packages.append('.'.join(['IPython',pname,'tests']))
95 95 if scripts:
96 96 packages.append('.'.join(['IPython',pname,'scripts']))
97 97 if others is not None:
98 98 for o in others:
99 99 packages.append('.'.join(['IPython',pname,o]))
100 100
101 101 def find_packages():
102 102 """
103 103 Find all of IPython's packages.
104 104 """
105 105 packages = ['IPython']
106 106 add_package(packages, 'config', tests=True, others=['default','profile'])
107 107 add_package(packages, 'core', tests=True)
108 108 add_package(packages, 'deathrow', tests=True)
109 109 add_package(packages, 'extensions')
110 110 add_package(packages, 'external')
111 111 add_package(packages, 'external.argparse')
112 112 add_package(packages, 'external.configobj')
113 113 add_package(packages, 'external.decorator')
114 114 add_package(packages, 'external.decorators')
115 115 add_package(packages, 'external.guid')
116 116 add_package(packages, 'external.Itpl')
117 117 add_package(packages, 'external.mglob')
118 118 add_package(packages, 'external.path')
119 119 add_package(packages, 'external.pexpect')
120 120 add_package(packages, 'external.pyparsing')
121 121 add_package(packages, 'external.simplegeneric')
122 122 add_package(packages, 'external.ssh')
123 123 add_package(packages, 'external.validate')
124 124 add_package(packages, 'kernel')
125 125 add_package(packages, 'frontend')
126 126 add_package(packages, 'frontend.qt')
127 127 add_package(packages, 'frontend.qt.console', tests=True)
128 128 add_package(packages, 'frontend.terminal', tests=True)
129 129 add_package(packages, 'lib', tests=True)
130 add_package(packages, 'parallel', tests=True)
130 131 add_package(packages, 'quarantine', tests=True)
131 132 add_package(packages, 'scripts')
132 133 add_package(packages, 'testing', tests=True)
133 134 add_package(packages, 'testing.plugin', tests=False)
134 135 add_package(packages, 'utils', tests=True)
135 136 add_package(packages, 'zmq')
136 137 add_package(packages, 'zmq.pylab')
137 add_package(packages, 'parallel')
138 138 return packages
139 139
140 140 #---------------------------------------------------------------------------
141 141 # Find package data
142 142 #---------------------------------------------------------------------------
143 143
144 144 def find_package_data():
145 145 """
146 146 Find IPython's package_data.
147 147 """
148 148 # This is not enough for these things to appear in an sdist.
149 149 # We need to muck with the MANIFEST to get this to work
150 150 package_data = {
151 151 'IPython.config.userconfig' : ['*'],
152 152 'IPython.testing' : ['*.txt']
153 153 }
154 154 return package_data
155 155
156 156
157 157 #---------------------------------------------------------------------------
158 158 # Find data files
159 159 #---------------------------------------------------------------------------
160 160
161 161 def make_dir_struct(tag,base,out_base):
162 162 """Make the directory structure of all files below a starting dir.
163 163
164 164 This is just a convenience routine to help build a nested directory
165 165 hierarchy because distutils is too stupid to do this by itself.
166 166
167 167 XXX - this needs a proper docstring!
168 168 """
169 169
170 170 # we'll use these a lot below
171 171 lbase = len(base)
172 172 pathsep = os.path.sep
173 173 lpathsep = len(pathsep)
174 174
175 175 out = []
176 176 for (dirpath,dirnames,filenames) in os.walk(base):
177 177 # we need to strip out the dirpath from the base to map it to the
178 178 # output (installation) path. This requires possibly stripping the
179 179 # path separator, because otherwise pjoin will not work correctly
180 180 # (pjoin('foo/','/bar') returns '/bar').
181 181
182 182 dp_eff = dirpath[lbase:]
183 183 if dp_eff.startswith(pathsep):
184 184 dp_eff = dp_eff[lpathsep:]
185 185 # The output path must be anchored at the out_base marker
186 186 out_path = pjoin(out_base,dp_eff)
187 187 # Now we can generate the final filenames. Since os.walk only produces
188 188 # filenames, we must join back with the dirpath to get full valid file
189 189 # paths:
190 190 pfiles = [pjoin(dirpath,f) for f in filenames]
191 191 # Finally, generate the entry we need, which is a pari of (output
192 192 # path, files) for use as a data_files parameter in install_data.
193 193 out.append((out_path, pfiles))
194 194
195 195 return out
196 196
197 197
198 198 def find_data_files():
199 199 """
200 200 Find IPython's data_files.
201 201
202 202 Most of these are docs.
203 203 """
204 204
205 205 docdirbase = pjoin('share', 'doc', 'ipython')
206 206 manpagebase = pjoin('share', 'man', 'man1')
207 207
208 208 # Simple file lists can be made by hand
209 209 manpages = filter(isfile, glob(pjoin('docs','man','*.1.gz')))
210 210 igridhelpfiles = filter(isfile,
211 211 glob(pjoin('IPython','extensions','igrid_help.*')))
212 212
213 213 # For nested structures, use the utility above
214 214 example_files = make_dir_struct(
215 215 'data',
216 216 pjoin('docs','examples'),
217 217 pjoin(docdirbase,'examples')
218 218 )
219 219 manual_files = make_dir_struct(
220 220 'data',
221 221 pjoin('docs','dist'),
222 222 pjoin(docdirbase,'manual')
223 223 )
224 224
225 225 # And assemble the entire output list
226 226 data_files = [ (manpagebase, manpages),
227 227 (pjoin(docdirbase, 'extensions'), igridhelpfiles),
228 228 ] + manual_files + example_files
229 229
230 230 return data_files
231 231
232 232
233 233 def make_man_update_target(manpage):
234 234 """Return a target_update-compliant tuple for the given manpage.
235 235
236 236 Parameters
237 237 ----------
238 238 manpage : string
239 239 Name of the manpage, must include the section number (trailing number).
240 240
241 241 Example
242 242 -------
243 243
244 244 >>> make_man_update_target('ipython.1') #doctest: +NORMALIZE_WHITESPACE
245 245 ('docs/man/ipython.1.gz',
246 246 ['docs/man/ipython.1'],
247 247 'cd docs/man && gzip -9c ipython.1 > ipython.1.gz')
248 248 """
249 249 man_dir = pjoin('docs', 'man')
250 250 manpage_gz = manpage + '.gz'
251 251 manpath = pjoin(man_dir, manpage)
252 252 manpath_gz = pjoin(man_dir, manpage_gz)
253 253 gz_cmd = ( "cd %(man_dir)s && gzip -9c %(manpage)s > %(manpage_gz)s" %
254 254 locals() )
255 255 return (manpath_gz, [manpath], gz_cmd)
256 256
257 257 #---------------------------------------------------------------------------
258 258 # Find scripts
259 259 #---------------------------------------------------------------------------
260 260
261 261 def find_scripts():
262 262 """
263 263 Find IPython's scripts.
264 264 """
265 265 parallel_scripts = pjoin('IPython','parallel','scripts')
266 266 main_scripts = pjoin('IPython','scripts')
267 267 scripts = [
268 pjoin(parallel_scripts, 'ipenginez'),
269 pjoin(parallel_scripts, 'ipcontrollerz'),
270 pjoin(parallel_scripts, 'ipclusterz'),
271 pjoin(parallel_scripts, 'iploggerz'),
268 pjoin(parallel_scripts, 'ipengine'),
269 pjoin(parallel_scripts, 'ipcontroller'),
270 pjoin(parallel_scripts, 'ipcluster'),
271 pjoin(parallel_scripts, 'iplogger'),
272 272 pjoin(main_scripts, 'ipython'),
273 273 pjoin(main_scripts, 'ipython-qtconsole'),
274 274 pjoin(main_scripts, 'pycolor'),
275 275 pjoin(main_scripts, 'irunner'),
276 276 pjoin(main_scripts, 'iptest')
277 277 ]
278 278
279 279 # Script to be run by the windows binary installer after the default setup
280 280 # routine, to add shortcuts and similar windows-only things. Windows
281 281 # post-install scripts MUST reside in the scripts/ dir, otherwise distutils
282 282 # doesn't find them.
283 283 if 'bdist_wininst' in sys.argv:
284 284 if len(sys.argv) > 2 and \
285 285 ('sdist' in sys.argv or 'bdist_rpm' in sys.argv):
286 286 print("ERROR: bdist_wininst must be run alone. Exiting.",
287 287 file=sys.stderr)
288 288 sys.exit(1)
289 289 scripts.append(pjoin('scripts','ipython_win_post_install.py'))
290 290
291 291 return scripts
292 292
293 293 #---------------------------------------------------------------------------
294 294 # Verify all dependencies
295 295 #---------------------------------------------------------------------------
296 296
297 297 def check_for_dependencies():
298 298 """Check for IPython's dependencies.
299 299
300 300 This function should NOT be called if running under setuptools!
301 301 """
302 302 from setupext.setupext import (
303 303 print_line, print_raw, print_status,
304 304 check_for_sphinx, check_for_pygments,
305 305 check_for_nose, check_for_pexpect,
306 306 check_for_pyzmq
307 307 )
308 308 print_line()
309 309 print_raw("BUILDING IPYTHON")
310 310 print_status('python', sys.version)
311 311 print_status('platform', sys.platform)
312 312 if sys.platform == 'win32':
313 313 print_status('Windows version', sys.getwindowsversion())
314 314
315 315 print_raw("")
316 316 print_raw("OPTIONAL DEPENDENCIES")
317 317
318 318 check_for_sphinx()
319 319 check_for_pygments()
320 320 check_for_nose()
321 321 check_for_pexpect()
322 322 check_for_pyzmq()
323 323
324 324
325 325 def record_commit_info(pkg_dir, build_cmd=build_py):
326 326 """ Return extended build command class for recording commit
327 327
328 328 The extended command tries to run git to find the current commit, getting
329 329 the empty string if it fails. It then writes the commit hash into a file
330 330 in the `pkg_dir` path, named ``.git_commit_info.ini``.
331 331
332 332 In due course this information can be used by the package after it is
333 333 installed, to tell you what commit it was installed from if known.
334 334
335 335 To make use of this system, you need a package with a .git_commit_info.ini
336 336 file - e.g. ``myproject/.git_commit_info.ini`` - that might well look like
337 337 this::
338 338
339 339 # This is an ini file that may contain information about the code state
340 340 [commit hash]
341 341 # The line below may contain a valid hash if it has been substituted
342 342 # during 'git archive'
343 343 archive_subst_hash=$Format:%h$
344 344 # This line may be modified by the install process
345 345 install_hash=
346 346
347 347 The .git_commit_info file above is also designed to be used with git
348 348 substitution - so you probably also want a ``.gitattributes`` file in the
349 349 root directory of your working tree that contains something like this::
350 350
351 351 myproject/.git_commit_info.ini export-subst
352 352
353 353 That will cause the ``.git_commit_info.ini`` file to get filled in by ``git
354 354 archive`` - useful in case someone makes such an archive - for example with
355 355 via the github 'download source' button.
356 356
357 357 Although all the above will work as is, you might consider having something
358 358 like a ``get_info()`` function in your package to display the commit
359 359 information at the terminal. See the ``pkg_info.py`` module in the nipy
360 360 package for an example.
361 361 """
362 362 class MyBuildPy(build_cmd):
363 363 ''' Subclass to write commit data into installation tree '''
364 364 def run(self):
365 365 build_py.run(self)
366 366 import subprocess
367 367 proc = subprocess.Popen('git rev-parse --short HEAD',
368 368 stdout=subprocess.PIPE,
369 369 stderr=subprocess.PIPE,
370 370 shell=True)
371 371 repo_commit, _ = proc.communicate()
372 372 # We write the installation commit even if it's empty
373 373 cfg_parser = ConfigParser()
374 374 cfg_parser.read(pjoin(pkg_dir, '.git_commit_info.ini'))
375 375 cfg_parser.set('commit hash', 'install_hash', repo_commit)
376 376 out_pth = pjoin(self.build_lib, pkg_dir, '.git_commit_info.ini')
377 377 out_file = open(out_pth, 'wt')
378 378 cfg_parser.write(out_file)
379 379 out_file.close()
380 380 return MyBuildPy
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now