| 1 | """nodoctests |
|---|
| 2 | Distributed SAGE |
|---|
| 3 | |
|---|
| 4 | AUTHORS: |
|---|
| 5 | Yi Qiang (yqiang@gmail.com) |
|---|
| 6 | """ |
|---|
| 7 | |
|---|
| 8 | import os |
|---|
| 9 | import subprocess |
|---|
| 10 | from getpass import getuser |
|---|
| 11 | |
|---|
| 12 | import sage.interfaces.cleaner |
|---|
| 13 | from sage.misc.all import SAGE_ROOT |
|---|
| 14 | from sage.dsage.misc.constants import DSAGE_DIR |
|---|
| 15 | |
|---|
| 16 | def spawn(cmd, verbose=True): |
|---|
| 17 | """ |
|---|
| 18 | Spawns a process and registers it with the SAGE cleaner. |
|---|
| 19 | """ |
|---|
| 20 | |
|---|
| 21 | null = open('/dev/null', 'a') |
|---|
| 22 | proc = '%s/%s' % (SAGE_ROOT + '/local/bin', cmd) |
|---|
| 23 | process = subprocess.Popen(proc, shell=True, stdout=null, stderr=null) |
|---|
| 24 | sage.interfaces.cleaner.cleaner(process.pid, cmd) |
|---|
| 25 | if verbose: |
|---|
| 26 | print 'Spawned %s (pid = %s)\n' % (cmd, process.pid) |
|---|
| 27 | |
|---|
| 28 | class DistributedSage(object): |
|---|
| 29 | r""" |
|---|
| 30 | Distributed SAGE allows you to do distributed computing in SAGE. |
|---|
| 31 | |
|---|
| 32 | To get up and running quickly, run dsage.setup() to run the |
|---|
| 33 | configuration utility. |
|---|
| 34 | |
|---|
| 35 | Note that configuration files will be stored in the |
|---|
| 36 | directory \code{\$DOT\_SAGE/dsage}. |
|---|
| 37 | |
|---|
| 38 | There are three distinct parts of Distributed SAGE: |
|---|
| 39 | Server |
|---|
| 40 | Launch the server with dsage.server() |
|---|
| 41 | |
|---|
| 42 | Worker |
|---|
| 43 | Launch the worker with dsage.worker() |
|---|
| 44 | |
|---|
| 45 | Client |
|---|
| 46 | Create the DSage object like this: |
|---|
| 47 | d = DSage() |
|---|
| 48 | |
|---|
| 49 | EXAMPLES: |
|---|
| 50 | This starts a server instance on localhost: |
|---|
| 51 | |
|---|
| 52 | sage: dsage.server() |
|---|
| 53 | |
|---|
| 54 | The dsage server is currently blocking by default. |
|---|
| 55 | |
|---|
| 56 | Open another sage instance and type: |
|---|
| 57 | |
|---|
| 58 | sage: dsage.worker() |
|---|
| 59 | |
|---|
| 60 | This starts a worker connecting the localhost. By default the worker will |
|---|
| 61 | connect to localhost and the port the last server started is listening on. |
|---|
| 62 | All of these settings are configurable via changing |
|---|
| 63 | \code{\$DOT\_SAGE/dsage/worker.conf} |
|---|
| 64 | |
|---|
| 65 | Open yet another terminal and type: |
|---|
| 66 | sage: D = DSage() |
|---|
| 67 | |
|---|
| 68 | This creates a connection to the remote server. To do a simple |
|---|
| 69 | evaluation, type: |
|---|
| 70 | sage: job1 = D('2+2') |
|---|
| 71 | |
|---|
| 72 | This sends the job '2+2' to a worker and you can view the |
|---|
| 73 | result by typing: |
|---|
| 74 | |
|---|
| 75 | sage: print job1 |
|---|
| 76 | |
|---|
| 77 | This is the most basic way of interacting with dsage. To do more |
|---|
| 78 | complicated tasks, you should look at the DistributedFunction |
|---|
| 79 | class. For example, to do distributed integer factorization with |
|---|
| 80 | ECM, type this: |
|---|
| 81 | |
|---|
| 82 | sage: f = DistributedFactor(P, number, name='my_factor') |
|---|
| 83 | sage: f.start() |
|---|
| 84 | |
|---|
| 85 | To check the result, do |
|---|
| 86 | |
|---|
| 87 | sage: print f.result |
|---|
| 88 | |
|---|
| 89 | To check if it is done, do |
|---|
| 90 | |
|---|
| 91 | sage: print f.done |
|---|
| 92 | |
|---|
| 93 | Customization: |
|---|
| 94 | |
|---|
| 95 | See the DOT\_SAGE/dsage directory. |
|---|
| 96 | |
|---|
| 97 | """ |
|---|
| 98 | |
|---|
| 99 | def __init__(self): |
|---|
| 100 | pass |
|---|
| 101 | |
|---|
| 102 | def start_all(self, port=8081, workers=2, log_level=0, poll=1.0, |
|---|
| 103 | anonymous_workers=False, verbose=True): |
|---|
| 104 | """ |
|---|
| 105 | Start the server and worker and returns a connection to the server. |
|---|
| 106 | |
|---|
| 107 | """ |
|---|
| 108 | |
|---|
| 109 | from sage.dsage.interface.dsage_interface import BlockingDSage |
|---|
| 110 | |
|---|
| 111 | self.server(port=port, log_level=log_level, blocking=False, |
|---|
| 112 | verbose=verbose) |
|---|
| 113 | self.worker(port=port, workers=workers, log_level=log_level, |
|---|
| 114 | blocking=False, poll=poll, anonymous=anonymous_workers, |
|---|
| 115 | verbose=verbose) |
|---|
| 116 | |
|---|
| 117 | import time |
|---|
| 118 | time.sleep(1) # Allow the server to start completely before trying |
|---|
| 119 | # to connect |
|---|
| 120 | d = BlockingDSage(server='localhost', port=port) |
|---|
| 121 | |
|---|
| 122 | return d |
|---|
| 123 | |
|---|
| 124 | def server(self, blocking=True, port=8081, log_level=0, ssl=True, |
|---|
| 125 | db_file=os.path.join(DSAGE_DIR, 'db', 'dsage.db'), |
|---|
| 126 | log_file=os.path.join(DSAGE_DIR, 'server.log'), |
|---|
| 127 | privkey=os.path.join(DSAGE_DIR, 'cacert.pem'), |
|---|
| 128 | cert=os.path.join(DSAGE_DIR, 'pubcert.pem'), |
|---|
| 129 | stats_file=os.path.join(DSAGE_DIR, 'dsage.xml'), |
|---|
| 130 | anonymous_logins=False, |
|---|
| 131 | verbose=True): |
|---|
| 132 | r""" |
|---|
| 133 | Run the Distributed SAGE server. |
|---|
| 134 | |
|---|
| 135 | Doing \code{dsage.server()} will spawn a server process which |
|---|
| 136 | listens by default on port 8081. |
|---|
| 137 | |
|---|
| 138 | INPUT: |
|---|
| 139 | blocking -- boolean (default: True) -- if False the dsage |
|---|
| 140 | server will run and you'll still be able to |
|---|
| 141 | enter commands at the command prompt (though |
|---|
| 142 | logging will make this hard). |
|---|
| 143 | logfile -- only used if blocking=True; the default is |
|---|
| 144 | to log to $DOT_SAGE/dsage/server.log |
|---|
| 145 | |
|---|
| 146 | """ |
|---|
| 147 | |
|---|
| 148 | cmd = 'dsage_server.py -d %s -p %s -l %s -f %s ' + \ |
|---|
| 149 | '-c %s -k %s --statsfile=%s' |
|---|
| 150 | cmd = cmd % (db_file, port, log_level, log_file, cert, privkey, |
|---|
| 151 | stats_file) |
|---|
| 152 | if ssl: |
|---|
| 153 | cmd += ' --ssl' |
|---|
| 154 | if not blocking: |
|---|
| 155 | cmd += ' --noblock' |
|---|
| 156 | spawn(cmd, verbose=verbose) |
|---|
| 157 | else: |
|---|
| 158 | os.system(cmd) |
|---|
| 159 | |
|---|
| 160 | def worker(self, server='localhost', port=8081, workers=2, poll=1.0, |
|---|
| 161 | username=getuser(), blocking=True, ssl=True, log_level=0, |
|---|
| 162 | anonymous=False, priority=20, |
|---|
| 163 | privkey=os.path.join(DSAGE_DIR, 'dsage_key'), |
|---|
| 164 | pubkey=os.path.join(DSAGE_DIR, 'dsage_key.pub'), |
|---|
| 165 | log_file=os.path.join(DSAGE_DIR, 'worker.log'), |
|---|
| 166 | verbose=True): |
|---|
| 167 | r""" |
|---|
| 168 | Run the Distributed SAGE worker. |
|---|
| 169 | |
|---|
| 170 | Typing \code{sage.worker()} will launch a worker which by |
|---|
| 171 | default connects to localhost on port 8081 to fetch jobs. |
|---|
| 172 | |
|---|
| 173 | INPUT: |
|---|
| 174 | server -- (string, default: None) the server you want to |
|---|
| 175 | connect to if None, connects to the server |
|---|
| 176 | specified in .sage/dsage/worker.conf |
|---|
| 177 | port -- (integer, default: None) the port that the server |
|---|
| 178 | listens on for workers. |
|---|
| 179 | blocking -- (bool, default: True) whether or not to make a |
|---|
| 180 | blocking connection. |
|---|
| 181 | logfile -- only used if blocking=True; the default is |
|---|
| 182 | to log to $DOT_SAGE/dsage/worker.log |
|---|
| 183 | poll -- rate at which the worker pings the server to check for new |
|---|
| 184 | jobs, this value will increase if the server has no jobs |
|---|
| 185 | """ |
|---|
| 186 | |
|---|
| 187 | cmd = 'dsage_worker.py -s %s -p %s -u %s -w %s --poll %s -l %s -f %s ' + \ |
|---|
| 188 | '--privkey=%s --pubkey=%s --priority=%s ' |
|---|
| 189 | cmd = cmd % (server, port, username, workers, poll, log_level, |
|---|
| 190 | log_file, privkey, pubkey, priority) |
|---|
| 191 | |
|---|
| 192 | if ssl: |
|---|
| 193 | cmd += ' --ssl' |
|---|
| 194 | if anonymous: |
|---|
| 195 | cmd += ' -a' |
|---|
| 196 | if not blocking: |
|---|
| 197 | cmd += ' --noblock' |
|---|
| 198 | spawn(cmd, verbose=verbose) |
|---|
| 199 | else: |
|---|
| 200 | os.system(cmd) |
|---|
| 201 | |
|---|
| 202 | def setup(self, template=None): |
|---|
| 203 | r""" |
|---|
| 204 | This is the setup utility which helps you configure dsage. |
|---|
| 205 | |
|---|
| 206 | Type \code{dsage.setup()} to run the configuration for the server, |
|---|
| 207 | worker and client. Alternatively, if you want to run the |
|---|
| 208 | configuration for just one parts, you can launch |
|---|
| 209 | \code{dsage.setup_server()}, \code{dsage.setup\_worker()} |
|---|
| 210 | or \code{dsage.setup()}. |
|---|
| 211 | |
|---|
| 212 | """ |
|---|
| 213 | |
|---|
| 214 | from sage.dsage.scripts.dsage_setup import setup |
|---|
| 215 | setup(template=template) |
|---|
| 216 | |
|---|
| 217 | def setup_server(self, *args): |
|---|
| 218 | """ |
|---|
| 219 | This method runs the configuration utility for the server. |
|---|
| 220 | |
|---|
| 221 | """ |
|---|
| 222 | |
|---|
| 223 | from sage.dsage.scripts.dsage_setup import setup_server |
|---|
| 224 | setup_server(*args) |
|---|
| 225 | |
|---|
| 226 | def setup_worker(self): |
|---|
| 227 | """ |
|---|
| 228 | This method runs the configuration utility for the worker. |
|---|
| 229 | |
|---|
| 230 | """ |
|---|
| 231 | |
|---|
| 232 | from sage.dsage.scripts.dsage_setup import setup_worker |
|---|
| 233 | setup_worker() |
|---|
| 234 | |
|---|
| 235 | def setup_client(self): |
|---|
| 236 | """ |
|---|
| 237 | This method runs the configuration utility for the client. |
|---|
| 238 | |
|---|
| 239 | """ |
|---|
| 240 | |
|---|
| 241 | from sage.dsage.scripts.dsage_setup import setup_client |
|---|
| 242 | setup_client() |
|---|
| 243 | |
|---|
| 244 | dsage = DistributedSage() |
|---|