Merge lp:~unifield-team/unifield-toolbox/invalid-name-instance into lp:unifield-toolbox
- invalid-name-instance
- Merge into trunk
Proposed by
Quentin THEURET @Amaris
Status: | Superseded |
---|---|
Proposed branch: | lp:~unifield-team/unifield-toolbox/invalid-name-instance |
Merge into: | lp:unifield-toolbox |
Diff against target: |
1660 lines (+1590/-0) 12 files modified
.bzrignore (+18/-0) common/etc/default_config.ini (+27/-0) common/etc/openerprc (+57/-0) kill.sh (+8/-0) lib/__init__.py (+1/-0) lib/initdb.py (+89/-0) lib/utils/__init__.py (+1/-0) lib/utils/errors.py (+66/-0) lib/utils/rpc.py (+460/-0) lib/utils/tiny_socket.py (+92/-0) nginx/style.css (+4/-0) runbot.py (+767/-0) |
To merge this branch: | bzr merge lp:~unifield-team/unifield-toolbox/invalid-name-instance |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
jftempo | Pending | ||
Review via email: mp+80433@code.launchpad.net |
This proposal has been superseded by a proposal from 2011-10-27.
Commit message
Description of the change
To post a comment you must log in.
Unmerged revisions
- 17. By Quentin THEURET @Amaris
-
Test if the name of the instance contains an invalid character and stop the program if yes
- 16. By Duy Vo <dvo@uf0002>
-
Add runbot
- 15. By Duy Vo <dvo@uf0002>
-
Resolve conflict
- 14. By jf <jf@tempo4>
-
[IMP] option skell --unit to run unit test
- 13. By jf <jf@tempo4>
-
[IMP] Create and start a new inst. with 1 line
- 12. By jf <jf@tempo4>
-
[IMP] disabled in list
- 11. By jf <jf@tempo4>
-
[IMP] comment on index page
- 10. By jf <jf@tempo4>
-
[FIX] nginx path
- 9. By jf <jf@tempo4>
-
[IMP] change default runbot port
- 8. By jf <jf@tempo4>
-
[FIX] ignore . directory
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === added file '.bzrignore' |
2 | --- .bzrignore 1970-01-01 00:00:00 +0000 |
3 | +++ .bzrignore 2011-10-26 10:12:24 +0000 |
4 | @@ -0,0 +1,18 @@ |
5 | +running/* |
6 | +common/unifield-wm |
7 | +common/unifield-web |
8 | +common/unifield-addons |
9 | +common/unifield-server |
10 | +common/unifield-data |
11 | +nginx/nginx.pid |
12 | +out.log |
13 | +nginx/access.log |
14 | +nginx/error.log |
15 | +nginx/index.html |
16 | +!nginx/ok.png |
17 | +!nginx/nok.png |
18 | +nginx/*.png |
19 | +nginx/nginx.conf |
20 | +nginx/nginx.conf |
21 | +common/.bzr |
22 | +running/.bzr |
23 | |
24 | === added directory 'common' |
25 | === added directory 'common/etc' |
26 | === added file 'common/etc/default_config.ini' |
27 | --- common/etc/default_config.ini 1970-01-01 00:00:00 +0000 |
28 | +++ common/etc/default_config.ini 2011-10-26 10:12:24 +0000 |
29 | @@ -0,0 +1,27 @@ |
30 | +[global] |
31 | +; set port to 0 to automatically get a free port |
32 | +; change it at your own risk |
33 | +port = 0 |
34 | + |
35 | +; comma separated list of modules to load |
36 | +modules = msf_profile |
37 | + |
38 | +; if true: load the csv data from unifield-data (only available if msf_profile is loaded) |
39 | +load_data = 1 |
40 | +; if load_data, you can set an email address |
41 | +email = |
42 | + |
43 | +; load openerp demo xml (disabled if load_data is true) |
44 | +load_demo = 0 |
45 | + |
46 | +; comment added on web page |
47 | +comment = |
48 | + |
49 | +; launchpad branches i.e: lp:unifield-wm/main |
50 | +; if set to 'link', a soft link will be created from common/ directory |
51 | +; if the directory already exists in "running/" nothing will be done |
52 | +unifield-wm = link |
53 | +unifield-addons = link |
54 | +unifield-web = link |
55 | +unifield-server = link |
56 | +unifield-data = link |
57 | |
58 | === added file 'common/etc/openerprc' |
59 | --- common/etc/openerprc 1970-01-01 00:00:00 +0000 |
60 | +++ common/etc/openerprc 2011-10-26 10:12:24 +0000 |
61 | @@ -0,0 +1,57 @@ |
62 | +[options] |
63 | +without_demo = all |
64 | +smtp_port = 25 |
65 | +xmlrpcs_interface = |
66 | +syslog = False |
67 | +logrotate = True |
68 | +xmlrpcs_port = |
69 | +test_report_directory = False |
70 | +list_db = True |
71 | +timezone = False |
72 | +xmlrpc_interface = |
73 | +test_file = False |
74 | +cache_timeout = 100000 |
75 | +smtp_password = False |
76 | +secure_pkey_file = server.pkey |
77 | +xmlrpc_port = |
78 | +log_level = info |
79 | +xmlrpc = True |
80 | +test_disable = False |
81 | +admin_passwd = 4unifield |
82 | +assert_exit_level = error |
83 | +smtp_server = localhost |
84 | +static_http_url_prefix = None |
85 | +test_commit = False |
86 | +xmlrpcs = False |
87 | +demo = {} |
88 | +login_message = False |
89 | +import_partial = |
90 | +pidfile = PIDFILE |
91 | +db_maxconn = 64 |
92 | +stop_after_init = False |
93 | +osv_memory_count_limit = False |
94 | +reportgz = False |
95 | +osv_memory_age_limit = 1.0 |
96 | +netrpc_port = |
97 | +db_port = False |
98 | +db_name = UF_INSTANCE |
99 | +debug_mode = False |
100 | +netrpc = True |
101 | +secure_cert_file = server.cert |
102 | +logfile = |
103 | +csv_internal_sep = , |
104 | +pg_path = None |
105 | +static_http_enable = False |
106 | +translate_modules = ['all'] |
107 | +smtp_ssl = False |
108 | +root_path = |
109 | +netrpc_interface = |
110 | +smtp_user = False |
111 | + |
112 | +db_user= |
113 | +db_password= |
114 | +db_host= |
115 | + |
116 | +email_from = False |
117 | +addons_path = UF_ADDONS_PATH/unifield-addons,UF_ADDONS_PATH/unifield-wm |
118 | +static_http_document_root = None |
119 | |
120 | === added file 'kill.sh' |
121 | --- kill.sh 1970-01-01 00:00:00 +0000 |
122 | +++ kill.sh 2011-10-26 10:12:24 +0000 |
123 | @@ -0,0 +1,8 @@ |
124 | +#! /bin/bash |
125 | + |
126 | +RUNNING_PATH=$1 |
127 | + |
128 | +if [ -z "$RUNNING_PATH" ]; then |
129 | + RUNNING_PATH=/running |
130 | +fi |
131 | +kill `ps aux | grep $RUNNING_PATH | awk '{print $2}'` |
132 | |
133 | === added directory 'lib' |
134 | === added file 'lib/__init__.py' |
135 | --- lib/__init__.py 1970-01-01 00:00:00 +0000 |
136 | +++ lib/__init__.py 2011-10-26 10:12:24 +0000 |
137 | @@ -0,0 +1,1 @@ |
138 | +import initdb |
139 | |
140 | === added file 'lib/initdb.py' |
141 | --- lib/initdb.py 1970-01-01 00:00:00 +0000 |
142 | +++ lib/initdb.py 2011-10-26 10:12:24 +0000 |
143 | @@ -0,0 +1,89 @@ |
144 | +#!/usr/bin/python |
145 | +# -*- encoding: utf-8 -*- |
146 | + |
147 | + |
148 | +import optparse |
149 | +import os |
150 | +import getpass |
151 | +import sys |
152 | +import glob |
153 | +import re |
154 | +import base64 |
155 | +import time |
156 | +import utils |
157 | +import socket |
158 | + |
159 | +def nb_request(): |
160 | + return len(utils.rpc.RPCProxy('res.request').search([('act_to', 'in', [utils.rpc.session.uid])])) |
161 | + |
162 | +def import_csv(lpath): |
163 | + for f in sorted(glob.glob(os.path.join(lpath, '*.csv'))): |
164 | + nbr = nb_request() |
165 | + m = re.match('.*/\d+_(.*)\.csv', f) |
166 | + if not m: |
167 | + continue |
168 | + obj = m.group(1) |
169 | + |
170 | + fo = open(f) |
171 | + wid = utils.rpc.RPCProxy('import_data').create({'ignore': 1, 'object': obj, 'file': base64.encodestring(fo.read()), 'debug': True}) |
172 | + fo.close() |
173 | + utils.rpc.RPCProxy('import_data').import_csv([wid]) |
174 | + while nb_request() == nbr: |
175 | + time.sleep(5) |
176 | + |
177 | +def wizard_init(): |
178 | + wiz = utils.rpc.RPCProxy('res.config').start([]) |
179 | + while wiz and wiz.get('res_model') not in ('base.setup.config','ir.ui.menu'): |
180 | + id = utils.rpc.RPCProxy(wiz['res_model']).create({}) |
181 | + wiz = utils.rpc.RPCProxy(wiz['res_model']).action_next([id]) |
182 | + if wiz.get('res_model') == 'base.setup.config': |
183 | + utils.rpc.RPCProxy(wiz['res_model']).config([]) |
184 | + return True |
185 | + return False |
186 | + |
187 | +def try_socket(sock, host, port, max_wait, start_time=False): |
188 | + if not start_time: |
189 | + start_time = time.time() |
190 | + try: |
191 | + sock.connect((host, int(port))) |
192 | + except socket.error, e: |
193 | + if e.errno == 111: |
194 | + if time.time()-start_time > max_wait: |
195 | + return False |
196 | + time.sleep(5) |
197 | + return try_socket(sock, host, port, max_wait, start_time) |
198 | + sock.close() |
199 | + return True |
200 | + |
201 | +def connect_db(user, pwd, dbname, host, port, path): |
202 | + msg = [] |
203 | + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) |
204 | + if try_socket(sock, host, port, 290): |
205 | + utils.rpc.initialize(host, port, 'socket', storage=dict()) |
206 | + utils.rpc.session.login(dbname, user, pwd) |
207 | + wiz = wizard_init() |
208 | + if wiz: |
209 | + import_csv(path) |
210 | + return 'Data successfully loaded' |
211 | + return 'Data not loaded: no init wizard to execute' |
212 | + |
213 | + raise Exception('Data not loaded: server is not ready') |
214 | + |
215 | + |
216 | +if __name__=="__main__": |
217 | + usage = "usage: %prog [options] import_dir" |
218 | + parser = optparse.OptionParser(usage=usage) |
219 | + parser.add_option("-d", "--database", dest="dbname", help="Database [default: %default]",default="test") |
220 | + parser.add_option("-H", "--host", dest="host", help="Host [default: %default]",default="127.0.0.1") |
221 | + parser.add_option("-p", "--port", dest="port", help=u"Port [default: %default]",default="8070") |
222 | + parser.add_option("-u", "--user", dest="user", help="User [default: %default]", default="admin") |
223 | + parser.add_option("-w", "--password", dest="pwd", help="Password") |
224 | + (opt, args) = parser.parse_args() |
225 | + |
226 | + if len(args) < 1: |
227 | + parser.error("Missing arg import_dir") |
228 | + |
229 | + if not opt.pwd: |
230 | + opt.pwd = getpass.getpass('Password : ') |
231 | + |
232 | + connect_db(opt.user, opt.pwd, opt.dbname, opt.host, opt.port, args[0]) |
233 | |
234 | === added directory 'lib/utils' |
235 | === added file 'lib/utils/__init__.py' |
236 | --- lib/utils/__init__.py 1970-01-01 00:00:00 +0000 |
237 | +++ lib/utils/__init__.py 2011-10-26 10:12:24 +0000 |
238 | @@ -0,0 +1,1 @@ |
239 | +import rpc |
240 | |
241 | === added file 'lib/utils/errors.py' |
242 | --- lib/utils/errors.py 1970-01-01 00:00:00 +0000 |
243 | +++ lib/utils/errors.py 2011-10-26 10:12:24 +0000 |
244 | @@ -0,0 +1,66 @@ |
245 | +# -*- coding: utf-8 -*- |
246 | + |
247 | +__all__ = ['AuthenticationError', 'TinyException', 'TinyError', |
248 | + 'TinyWarning', 'TinyMessage', 'Concurrency', 'AccessDenied'] |
249 | + |
250 | +class AuthenticationError(Exception): pass |
251 | + |
252 | + |
253 | +class TinyException(Exception): |
254 | + |
255 | + def __init__(self, message, title=None): |
256 | + |
257 | + self.title = title |
258 | + self.message = message |
259 | + |
260 | + def __unicode__(self): |
261 | + return ustr(self.message) |
262 | + |
263 | + def __str__(self): |
264 | + return self.message |
265 | + |
266 | +class TinyError(TinyException): |
267 | + |
268 | + def __init__(self, message, title=None): |
269 | + if title is None: title = "Error" |
270 | + TinyException.__init__(self, message=message, title=title) |
271 | + |
272 | +class TinyWarning(TinyException): |
273 | + |
274 | + def __init__(self, message, title=None): |
275 | + if title is None: title = "Warning" |
276 | + TinyException.__init__(self, message=message, title=title) |
277 | + |
278 | +class TinyMessage(TinyException): |
279 | + |
280 | + def __init__(self, message, title=None): |
281 | + if title is None: title = "Information" |
282 | + TinyException.__init__(self, message=message, title=title) |
283 | + |
284 | +class Concurrency(Exception): |
285 | + |
286 | + def __init__(self, message, title=None, datas=None): |
287 | + self.title = title |
288 | + self.datas = datas |
289 | + self.message = message |
290 | + |
291 | + def __unicode__(self): |
292 | + return ustr(self.title) |
293 | + |
294 | + def __str__(self): |
295 | + return self.title |
296 | + |
297 | +class AccessDenied(TinyError): pass |
298 | + |
299 | +def error(title, msg): |
300 | + raise TinyError(message=msg, title=title or "Error") |
301 | + |
302 | +def warning(msg, title=None): |
303 | + raise TinyWarning(message=msg, title=title or "Warning") |
304 | + |
305 | +def message(msg): |
306 | + raise TinyMessage(message=msg) |
307 | + |
308 | +def concurrency(msg, title=None, datas=None): |
309 | + raise Concurrency(message=msg, title=title, datas=datas) |
310 | + |
311 | |
312 | === added file 'lib/utils/rpc.py' |
313 | --- lib/utils/rpc.py 1970-01-01 00:00:00 +0000 |
314 | +++ lib/utils/rpc.py 2011-10-26 10:12:24 +0000 |
315 | @@ -0,0 +1,460 @@ |
316 | +############################################################################### |
317 | +# |
318 | +# Copyright (C) 2007-TODAY OpenERP SA. All Rights Reserved. |
319 | +# |
320 | +# $Id$ |
321 | +# |
322 | +# Developed by OpenERP (http://openerp.com) and Axelor (http://axelor.com). |
323 | +# |
324 | +# The OpenERP web client is distributed under the "OpenERP Public License". |
325 | +# It's based on Mozilla Public License Version (MPL) 1.1 with following |
326 | +# restrictions: |
327 | +# |
328 | +# - All names, links and logos of OpenERP must be kept as in original |
329 | +# distribution without any changes in all software screens, especially |
330 | +# in start-up page and the software header, even if the application |
331 | +# source code has been changed or updated or code has been added. |
332 | +# |
333 | +# You can see the MPL licence at: http://www.mozilla.org/MPL/MPL-1.1.html |
334 | +# |
335 | +############################################################################### |
336 | + |
337 | +import socket |
338 | +import xmlrpclib |
339 | + |
340 | +import errors |
341 | + |
342 | +from tiny_socket import TinySocket |
343 | +from tiny_socket import TinySocketError |
344 | + |
345 | +class NotLoggedIn(errors.TinyError, errors.AuthenticationError): pass |
346 | + |
347 | +class RPCException(Exception): |
348 | + """A common exeption class for RPC errors. |
349 | + """ |
350 | + |
351 | + def __init__(self, code, backtrace): |
352 | + |
353 | + self.code = code |
354 | + self.args = backtrace |
355 | + |
356 | + if hasattr(code, 'split'): |
357 | + lines = code.split('\n') |
358 | + |
359 | + self.type = lines[0].split(' -- ')[0] |
360 | + self.message = '' |
361 | + if len(lines[0].split(' -- ')) > 1: |
362 | + self.message = lines[0].split(' -- ')[1] |
363 | + |
364 | + self.data = '\n'.join(lines[2:]) |
365 | + |
366 | + else: |
367 | + self.type = 'error' |
368 | + self.message = backtrace |
369 | + self.data = backtrace |
370 | + |
371 | + self.backtrace = backtrace |
372 | + |
373 | + def __str__(self): |
374 | + return self.message |
375 | + |
376 | + |
377 | +class RPCGateway(object): |
378 | + """Gateway abstraction, that implement common stuffs for rpc gateways. |
379 | + All RPC gateway should extends this class. |
380 | + """ |
381 | + |
382 | + def __init__(self, session): |
383 | + if not isinstance(session, RPCSession): |
384 | + raise TypeError("RPCSession argument expected, got %s" % type(session)) |
385 | + self.session = session |
386 | + |
387 | + def __rpc__(self, obj, method, args=(), auth=True): |
388 | + """Derived classes should owverride this method. |
389 | + |
390 | + @param obj: the remote object |
391 | + @param method: the method of the remote object |
392 | + @param args: arguments to be passed |
393 | + @param oauth: authentication is required or not |
394 | + |
395 | + @return: the result of the method |
396 | + """ |
397 | + pass |
398 | + |
399 | + @property |
400 | + def connection_string(self): |
401 | + """Get the connection string... |
402 | + """ |
403 | + return "%s://%s:%s/"%(self.session.protocol, self.session.host, self.session.port) |
404 | + |
405 | + def __convert(self, result): |
406 | + |
407 | + if isinstance(result, str): |
408 | + # try to convert into unicode string |
409 | + try: |
410 | + return ustr(result) |
411 | + except Exception, e: |
412 | + return result |
413 | + |
414 | + elif isinstance(result, list): |
415 | + return [self.__convert(val) for val in result] |
416 | + |
417 | + elif isinstance(result, tuple): |
418 | + return tuple([self.__convert(val) for val in result]) |
419 | + |
420 | + elif isinstance(result, dict): |
421 | + newres = {} |
422 | + for key, val in result.items(): |
423 | + newres[key] = self.__convert(val) |
424 | + |
425 | + return newres |
426 | + |
427 | + else: |
428 | + return result |
429 | + |
430 | + def __execute(self, obj, method, args=(), auth=True): |
431 | + try: |
432 | + result = self.__rpc__(obj, method, args, auth=auth) |
433 | + return self.__convert(result) |
434 | + except socket.error, e: |
435 | + raise errors.TinyException(e.message or e.strerror, title='Application Error') |
436 | + |
437 | + except RPCException, err: |
438 | + if err.type in ('warning', 'UserError'): |
439 | + if err.message in ('ConcurrencyException') and len(args) > 4: |
440 | + errors.concurrency(err.message, err.data, args) |
441 | + else: |
442 | + errors.warning(err.data) |
443 | + else: |
444 | + errors.error('Application Error', err.backtrace) |
445 | + |
446 | + except Exception, e: |
447 | + errors.error('Application Error', str(e)) |
448 | + |
449 | + def execute(self, obj, method, *args): |
450 | + """Excecute the method of the obj with the given arguments. |
451 | + |
452 | + @param obj: the remote object |
453 | + @param method: the method of the remote object |
454 | + @param args: arguments to be passed |
455 | + |
456 | + @return: the result of the method |
457 | + """ |
458 | + return self.__execute(obj, method, args) |
459 | + |
460 | + def execute_noauth(self, obj, method, *args): |
461 | + """Excecute the method of the obj with the given arguments without authentication. |
462 | + |
463 | + @param obj: the object |
464 | + @param method: the method to execute |
465 | + @param args: the arguments |
466 | + |
467 | + @return: the result of the method |
468 | + """ |
469 | + return self.__execute(obj, method, args, auth=False) |
470 | + |
471 | + |
472 | +class XMLRPCGateway(RPCGateway): |
473 | + """XMLRPC implementation. |
474 | + """ |
475 | + |
476 | + def __init__(self, session): |
477 | + """Create new instance of XMLRPCGateway. |
478 | + |
479 | + @param session: a session |
480 | + """ |
481 | + super(XMLRPCGateway, self).__init__(session) |
482 | + self._url = self.connection_string + 'xmlrpc/' |
483 | + |
484 | + def __rpc__(self, obj, method, args=(), auth=True): |
485 | + sock = xmlrpclib.ServerProxy(self._url + str(obj)) |
486 | + try: |
487 | + if auth: |
488 | + args = (self.session.db, self.session.uid, self.session.password) + args |
489 | + return getattr(sock, method)(*args) |
490 | + except xmlrpclib.Fault, err: |
491 | + raise RPCException(err.faultCode, err.faultString) |
492 | + |
493 | + |
494 | +class NETRPCGateway(RPCGateway): |
495 | + """NETRPC Implementation. |
496 | + """ |
497 | + |
498 | + def __rpc__(self, obj, method, args=(), auth=True): |
499 | + sock = TinySocket() |
500 | + try: |
501 | + sock.connect(self.session.host, self.session.port) |
502 | + if auth: |
503 | + args = (self.session.db, self.session.uid, self.session.password) + args |
504 | + sock.send((obj, method) + args) |
505 | + res = sock.receive() |
506 | + sock.disconnect() |
507 | + return res |
508 | + |
509 | + except xmlrpclib.Fault, err: |
510 | + raise RPCException(err.faultCode, err.faultString) |
511 | + |
512 | + except TinySocketError, err: |
513 | + raise RPCException(err.faultCode, err.faultString) |
514 | + |
515 | + |
516 | +# XXX: Fix openobject server to return PyTZ compatible timezone name |
517 | +_TZ_ALIASES = { |
518 | + 'IST' : 'Asia/Calcutta' |
519 | +} |
520 | + |
521 | + |
522 | +class RPCSession(object): |
523 | + """Maintains client session and provides way to authenticate |
524 | + client & invoce RPC requested by clients. |
525 | + """ |
526 | + |
527 | + __slots__ = ['host', 'port', 'protocol', 'storage', 'gateway'] |
528 | + |
529 | + def __init__(self, host, port, protocol='socket', storage={}): |
530 | + """Create new instance of RPCSession. |
531 | + |
532 | + @param host: the openobject-server host |
533 | + @params port: the openobject-server port |
534 | + @params protocol: the openobject-server protocol |
535 | + @param storage: a dict like storage that will be used to store session data |
536 | + """ |
537 | + self.host = host |
538 | + self.port = port |
539 | + self.protocol = protocol |
540 | + self.storage = storage |
541 | + |
542 | + if protocol in ('http', 'https'): |
543 | + self.gateway = XMLRPCGateway(self) |
544 | + |
545 | + elif protocol == 'socket': |
546 | + self.gateway = NETRPCGateway(self) |
547 | + |
548 | + else: |
549 | + raise errors.message("Unsupported protocol.") |
550 | + |
551 | + def __getattr__(self, name): |
552 | + try: |
553 | + return super(RPCSession, self).__getattribute__(name) |
554 | + except: |
555 | + pass |
556 | + |
557 | + return self.storage.get(name) |
558 | + |
559 | + def __setattr__(self, name, value): |
560 | + if name in self.__slots__: |
561 | + super(RPCSession, self).__setattr__(name, value) |
562 | + else: |
563 | + self.storage[name] = value |
564 | + |
565 | + def __getitem__(self, name): |
566 | + return self.storage[name] |
567 | + |
568 | + def __setitem__(self, name, value): |
569 | + self.storage[name] = value |
570 | + |
571 | + def __delitem__(self, name): |
572 | + try: |
573 | + del self.storage[name] |
574 | + except: |
575 | + pass |
576 | + |
577 | + def get(self, name, default=None): |
578 | + return self.storage.get(name, default) |
579 | + |
580 | + @property |
581 | + def context(self): |
582 | + return (self._context or {}).copy() |
583 | + |
584 | + @property |
585 | + def connection_string(self): |
586 | + return self.gateway.connection_string |
587 | + |
588 | + def listdb(self): |
589 | + try: |
590 | + return self.execute_noauth('db', 'list') |
591 | + except errors.TinyError, e: |
592 | + if e.message == 'AccessDenied': |
593 | + return None |
594 | + raise |
595 | + |
596 | + def login(self, db, user, password): |
597 | + |
598 | + if not (db and user and password): |
599 | + return -1 |
600 | + |
601 | + try: |
602 | + uid = self.execute_noauth('common', 'login', db, user, password) |
603 | + except Exception, e: |
604 | + return -1 |
605 | + |
606 | + if uid <= 0: |
607 | + return -1 |
608 | + |
609 | + self._logged_as(db, uid, password) |
610 | + return uid |
611 | + |
612 | + def _logged_as(self, db, uid, password): |
613 | + self.storage['uid'] = uid |
614 | + self.storage['db'] = db |
615 | + self.storage['password'] = password |
616 | + self.storage['open'] = True |
617 | + |
618 | + # read the full name of the user |
619 | + res_users = self.execute('object', 'execute', 'res.users', 'read', [uid], ['name', 'company_id', 'login'])[0] |
620 | + self.storage['user_name'] = res_users['name'] |
621 | + self.storage['company_id'], self.storage['company_name'] = res_users['company_id'] |
622 | + self.storage['loginname'] = res_users['login'] |
623 | + # set the context |
624 | + self.context_reload() |
625 | + |
626 | + def logout(self): |
627 | + try: |
628 | + self.storage.clear() |
629 | + except Exception, e: |
630 | + pass |
631 | + |
632 | + def is_logged(self): |
633 | + return self.uid and self.open |
634 | + |
635 | + def context_reload(self): |
636 | + """Reload the context for the current user |
637 | + """ |
638 | + |
639 | + self.storage['_context'] = {'client': 'web'} |
640 | + |
641 | + # self.uid |
642 | + context = self.execute('object', 'execute', 'res.users', 'context_get') |
643 | + self._context.update(context or {}) |
644 | + |
645 | + self.storage['remote_timezone'] = 'utc' |
646 | + self.storage['client_timezone'] = self.context.get("tz", False) |
647 | + |
648 | + if self.storage.get('client_timezone'): |
649 | + self.storage['remote_timezone'] = self.execute('common', 'timezone_get') |
650 | + try: |
651 | + import pytz |
652 | + except: |
653 | + raise errors.warning('You select a timezone but OpenERP could not find pytz library!\nThe timezone functionality will be disable.') |
654 | + |
655 | + # set locale in session |
656 | + self.storage['locale'] = self.context.get('lang', 'en_US') |
657 | + lang_ids = self.execute( |
658 | + 'object', 'execute', 'res.lang', |
659 | + 'search', [('code', '=', self.storage['locale'])]) |
660 | + if lang_ids: |
661 | + self.storage['lang'] = self.execute( |
662 | + 'object', 'execute', 'res.lang', 'read', lang_ids[0], []) |
663 | + |
664 | + def execute(self, obj, method, *args): |
665 | + if not self.is_logged(): |
666 | + raise NotLoggedIn('Not logged...', 'Authorization Error') |
667 | + |
668 | + return self.gateway.execute(obj, method, *args) |
669 | + |
670 | + def execute_noauth(self, obj, method, *args): |
671 | + return self.gateway.execute_noauth(obj, method, *args) |
672 | + |
673 | + def execute_db(self, method, *args): |
674 | + return self.execute_noauth('db', method, *args) |
675 | + |
676 | + |
677 | +# global session variable, will be initialized with connect |
678 | +session = None |
679 | + |
680 | + |
681 | +def initialize(host, port, protocol='socket', storage=None): |
682 | + """ Initialize the default rpc session. |
683 | + """ |
684 | + global session |
685 | + session = RPCSession(host, port, protocol, storage=storage) |
686 | + |
687 | + |
688 | +class RPCProxy(object): |
689 | + """A wrapper arround xmlrpclib, provides pythonic way to access tiny resources. |
690 | + |
691 | + For example, |
692 | + |
693 | + >>> users = RPCProxy("ir.users") |
694 | + >>> res = users.read([1], ['name', 'active_id'], session.context) |
695 | + """ |
696 | + |
697 | + def __init__(self, resource): |
698 | + """Create new instance of RPCProxy for the give tiny resource |
699 | + |
700 | + @param resource: the tinyresource |
701 | + """ |
702 | + self._resource = resource |
703 | + self._session = session |
704 | + self._attrs = {} |
705 | + |
706 | + def _func_getter(self, name): |
707 | + return lambda *args: self(name, *args) |
708 | + |
709 | + def __getattr__(self, name): |
710 | + if name not in self._attrs: |
711 | + return self._attrs.setdefault(name, self._func_getter(name)) |
712 | + return self._attrs[name] |
713 | + |
714 | + def __call__(self, *args): |
715 | + return self._session.execute('object', 'execute', |
716 | + self._resource, *args) |
717 | + |
718 | + def fields_get(self, fields, context=None): |
719 | + if context is None: |
720 | + context = self._session.context |
721 | + return self('fields_get', fields, context) |
722 | + def fields_view_get(self, view_id, view_type, context=None, |
723 | + hastoolbar=False, hassubmenu=False): |
724 | + if context is None: |
725 | + context = self._session.context |
726 | + return self('fields_view_get', view_id or False, view_type, context, |
727 | + hastoolbar, hassubmenu) |
728 | + |
729 | + def search(self, criteria, offset=0, limit=False, order=False, context=None): |
730 | + if context is None: |
731 | + context = self._session.context |
732 | + return self('search', criteria, offset, limit, order, context) |
733 | + |
734 | +def name_get(model, id, context=None): |
735 | + |
736 | + id = (id or False) and int(id) |
737 | + name = (id or str('')) and str(id) |
738 | + |
739 | + if model and id: |
740 | + |
741 | + ctx = session.context.copy() |
742 | + ctx.update(context or {}) |
743 | + |
744 | + proxy = RPCProxy(model) |
745 | + |
746 | + try: |
747 | + name = proxy.name_get([id], ctx) |
748 | + name = name and name[0][1] or '' |
749 | + except errors.TinyWarning: |
750 | + name = _("== Access Denied ==") |
751 | + |
752 | + return name |
753 | + |
754 | + |
755 | +if __name__=="__main__": |
756 | + |
757 | + host = 'localhost' |
758 | + port = 8070 |
759 | + protocol = 'socket' |
760 | + |
761 | + initialize(host, port, protocol, storage=dict()) |
762 | + |
763 | + res = session.listdb() |
764 | + print res |
765 | + |
766 | + res = session.login('t1', 'admin', 'admin') |
767 | + print res |
768 | + |
769 | + res = RPCProxy('res.users').read([session.uid], ['name']) |
770 | + print res |
771 | + |
772 | + print session.context |
773 | + |
774 | + |
775 | +# vim: ts=4 sts=4 sw=4 si et |
776 | |
777 | === added file 'lib/utils/tiny_socket.py' |
778 | --- lib/utils/tiny_socket.py 1970-01-01 00:00:00 +0000 |
779 | +++ lib/utils/tiny_socket.py 2011-10-26 10:12:24 +0000 |
780 | @@ -0,0 +1,92 @@ |
781 | +############################################################################### |
782 | +# |
783 | +# Copyright (C) 2007-TODAY OpenERP SA. All Rights Reserved. |
784 | +# |
785 | +# $Id$ |
786 | +# |
787 | +# Developed by OpenERP (http://openerp.com) and Axelor (http://axelor.com). |
788 | +# |
789 | +# The OpenERP web client is distributed under the "OpenERP Public License". |
790 | +# It's based on Mozilla Public License Version (MPL) 1.1 with following |
791 | +# restrictions: |
792 | +# |
793 | +# - All names, links and logos of OpenERP must be kept as in original |
794 | +# distribution without any changes in all software screens, especially |
795 | +# in start-up page and the software header, even if the application |
796 | +# source code has been changed or updated or code has been added. |
797 | +# |
798 | +# You can see the MPL licence at: http://www.mozilla.org/MPL/MPL-1.1.html |
799 | +# |
800 | +############################################################################### |
801 | + |
802 | +import socket |
803 | +import cPickle |
804 | +import sys |
805 | + |
806 | +DNS_CACHE = {} |
807 | + |
808 | +class TinySocketError(Exception): |
809 | + |
810 | + def __init__(self, faultCode, faultString): |
811 | + self.faultCode = faultCode |
812 | + self.faultString = faultString |
813 | + self.args = (faultCode, faultString) |
814 | + |
815 | +SOCKET_TIMEOUT = 450 |
816 | +socket.setdefaulttimeout(SOCKET_TIMEOUT) |
817 | +class TinySocket(object): |
818 | + |
819 | + def __init__(self, sock=None): |
820 | + if sock is None: |
821 | + self.sock = socket.socket( |
822 | + socket.AF_INET, socket.SOCK_STREAM) |
823 | + else: |
824 | + self.sock = sock |
825 | + self.sock.settimeout(SOCKET_TIMEOUT) |
826 | + # disables Nagle algorithm (avoids 200ms default delay on Windows) |
827 | + self.sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) |
828 | + |
829 | + def connect(self, host, port=False): |
830 | + if not port: |
831 | + protocol, buf = host.split('//') |
832 | + host, port = buf.split(':') |
833 | + if host in DNS_CACHE: |
834 | + host = DNS_CACHE[host] |
835 | + self.sock.connect((host, int(port))) |
836 | + DNS_CACHE[host], port = self.sock.getpeername() |
837 | + |
838 | + def disconnect(self): |
839 | + # on Mac, the connection is automatically shutdown when the server disconnect. |
840 | + # see http://bugs.python.org/issue4397 |
841 | + if sys.platform != 'darwin': |
842 | + self.sock.shutdown(socket.SHUT_RDWR) |
843 | + self.sock.close() |
844 | + |
845 | + def send(self, msg, exception=False, traceback=None): |
846 | + msg = cPickle.dumps([msg,traceback]) |
847 | + self.sock.sendall('%8d%s%s' % (len(msg), exception and "1" or "0", msg)) |
848 | + |
849 | + def receive(self): |
850 | + |
851 | + def read(socket, size): |
852 | + buf='' |
853 | + while len(buf) < size: |
854 | + chunk = self.sock.recv(size - len(buf)) |
855 | + if chunk == '': |
856 | + raise RuntimeError, "socket connection broken" |
857 | + buf += chunk |
858 | + return buf |
859 | + |
860 | + size = int(read(self.sock, 8)) |
861 | + buf = read(self.sock, 1) |
862 | + exception = buf != '0' and buf or False |
863 | + res = cPickle.loads(read(self.sock, size)) |
864 | + |
865 | + if isinstance(res[0],Exception): |
866 | + if exception: |
867 | + raise TinySocketError(res[0], res[1]) |
868 | + raise res[0] |
869 | + else: |
870 | + return res[0] |
871 | + |
872 | +# vim: ts=4 sts=4 sw=4 si et |
873 | |
874 | === added directory 'nginx' |
875 | === added file 'nginx/nok.png' |
876 | Binary files nginx/nok.png 1970-01-01 00:00:00 +0000 and nginx/nok.png 2011-10-26 10:12:24 +0000 differ |
877 | === added file 'nginx/ok.png' |
878 | Binary files nginx/ok.png 1970-01-01 00:00:00 +0000 and nginx/ok.png 2011-10-26 10:12:24 +0000 differ |
879 | === added file 'nginx/style.css' |
880 | --- nginx/style.css 1970-01-01 00:00:00 +0000 |
881 | +++ nginx/style.css 2011-10-26 10:12:24 +0000 |
882 | @@ -0,0 +1,4 @@ |
883 | +.comment { |
884 | + font-size: 10px; |
885 | + padding-left: 20px; |
886 | +} |
887 | |
888 | === added file 'runbot.py' |
889 | --- runbot.py 1970-01-01 00:00:00 +0000 |
890 | +++ runbot.py 2011-10-26 10:12:24 +0000 |
891 | @@ -0,0 +1,767 @@ |
892 | +#!/usr/bin/python |
893 | + |
894 | +import cgitb,os,re,subprocess,sys,time |
895 | +import argparse |
896 | +import fileinput |
897 | +import mako.template |
898 | +from lib import initdb |
899 | +import ConfigParser |
900 | +import psycopg2 |
901 | +import psycopg2.extensions |
902 | +from bzrlib.branch import Branch |
903 | +from bzrlib.bzrdir import BzrDir |
904 | +from bzrlib.workingtree import WorkingTree |
905 | +from bzrlib.plugins.launchpad.lp_directory import LaunchpadDirectory |
906 | +import shutil |
907 | +import smtplib |
908 | +from email.mime.text import MIMEText |
909 | + |
910 | +#---------------------------------------------------------- |
911 | +# OpenERP rdtools utils |
912 | +#---------------------------------------------------------- |
913 | + |
914 | +def write_pid(pidfile, pid): |
915 | + pidf = open(pidfile, "w") |
916 | + pidf.write("%d"%pid) |
917 | + pidf.close() |
918 | + |
919 | +def log(*l,**kw): |
920 | + out=[time.strftime("%Y-%m-%d %H:%M:%S")] |
921 | + for i in l: |
922 | + if not isinstance(i,basestring): |
923 | + i=repr(i) |
924 | + out.append(i) |
925 | + out+=["%s=%r"%(k,v) for k,v in kw.items()] |
926 | + sys.stdout.write(" ".join(out)) |
927 | + sys.stdout.write("\n") |
928 | + |
929 | +def run(l): |
930 | + log("run",*l) |
931 | + if isinstance(l,list): |
932 | + rc=os.spawnvp(os.P_WAIT, l[0], l) |
933 | + elif isinstance(l,str): |
934 | + tmp=['sh','-c',l] |
935 | + rc=os.spawnvp(os.P_WAIT, tmp[0], tmp) |
936 | + return rc |
937 | + |
938 | +def kill(pid,sig=9): |
939 | + try: |
940 | + os.kill(pid,sig) |
941 | + except OSError: |
942 | + pass |
943 | + |
944 | +def _is_running(pid): |
945 | + if not pid: |
946 | + return False |
947 | + try: |
948 | + os.kill(pid, 0) |
949 | + return pid |
950 | + except OSError: |
951 | + return False |
952 | + |
953 | +def underscorize(n): |
954 | + return n.replace("~","").replace(":","_").replace("/","_") |
955 | + |
956 | +#---------------------------------------------------------- |
957 | +# OpenERP RunBot |
958 | +#---------------------------------------------------------- |
959 | + |
960 | +class RunBotBranch(object): |
961 | + def __init__(self,runbot, subfolder): |
962 | + self.runbot=runbot |
963 | + self.running=False |
964 | + self.running_port=None |
965 | + self.running_server_pid=None |
966 | + self.running_web_pid=None |
967 | + self.running_t0=None |
968 | + self.date_last_modified=0 |
969 | + self.revision_count=0 |
970 | + self.merge_count=0 |
971 | + |
972 | + self.name = subfolder |
973 | + self.unique_name = subfolder |
974 | + self.project_name = subfolder |
975 | + self.uname=underscorize(self.unique_name) |
976 | + |
977 | + self.subdomain=subfolder |
978 | + self.instance_path=os.path.join(self.runbot.wd, "running", self.subdomain) |
979 | + |
980 | + self.server_path=os.path.join(self.instance_path,"unifield-server") |
981 | + self.server_bin_path=os.path.join(self.server_path,"openerp-server.py") |
982 | + if not os.path.exists(self.server_bin_path): # for 6.0 branches |
983 | + self.server_bin_path=os.path.join(self.server_path,"bin","openerp-server.py") |
984 | + |
985 | + self.data_path = os.path.join(self.instance_path,"unifield-data") |
986 | + |
987 | + self.web_path=os.path.join(self.instance_path,"unifield-web") |
988 | + self.etc_path = os.path.join(self.instance_path,"etc") |
989 | + self.web_bin_path=os.path.join(self.web_path,"openerp-web.py") |
990 | + |
991 | + self.log_path=os.path.join(self.instance_path, 'logs') |
992 | + self.log_server_path=os.path.join(self.log_path,'server.txt') |
993 | + self.log_web_path=os.path.join(self.log_path,'web.txt') |
994 | + self.file_pidweb = os.path.join(self.instance_path,'etc','web.pid') |
995 | + self.file_pidserver = os.path.join(self.instance_path,'etc','server.pid') |
996 | + self.configfile = os.path.join(self.instance_path,'config.ini') |
997 | + |
998 | + self.ini = ConfigParser.ConfigParser() |
999 | + self.ini.readfp(open(self.runbot.common_configfile, 'r')) |
1000 | + self.ini.read(self.configfile) |
1001 | + if not self.ini.has_section('global'): |
1002 | + self.ini.add_section('global') |
1003 | + |
1004 | + def write_ini(self): |
1005 | + f = open(self.configfile, "w") |
1006 | + self.ini.write(f) |
1007 | + f.close() |
1008 | + |
1009 | + def remove_option(self, key): |
1010 | + try: |
1011 | + return self.ini.remove_option('global', key) |
1012 | + except ConfigParser.NoOptionError: |
1013 | + return False |
1014 | + |
1015 | + def get_ini(self, key): |
1016 | + try: |
1017 | + return self.ini.get('global', key) |
1018 | + except ConfigParser.NoOptionError: |
1019 | + return False |
1020 | + |
1021 | + def get_int_ini(self, key): |
1022 | + try: |
1023 | + return self.ini.getint('global', key) |
1024 | + except ConfigParser.NoOptionError: |
1025 | + return False |
1026 | + |
1027 | + def get_bool_ini(self, key, default=False): |
1028 | + try: |
1029 | + return self.ini.getboolean('global', key) |
1030 | + except ConfigParser.NoOptionError: |
1031 | + return default |
1032 | + |
1033 | + def set_ini(self, key, value): |
1034 | + return self.ini.set('global', key, '%s'%value) |
1035 | + |
1036 | + def is_web_running(self): |
1037 | + pid = self.pidweb() |
1038 | + return _is_running(pid) |
1039 | + |
1040 | + def is_server_running(self): |
1041 | + pid = self.pidserver() |
1042 | + return _is_running(pid) |
1043 | + |
1044 | + def _get_pid(self, pidfile): |
1045 | + if not os.path.isfile(pidfile): |
1046 | + return False |
1047 | + try: |
1048 | + pf = file(pidfile,'r') |
1049 | + pid = int(pf.read().strip()) |
1050 | + pf.close() |
1051 | + return pid |
1052 | + except IOError: |
1053 | + return False |
1054 | + return False |
1055 | + |
1056 | + def pidweb(self): |
1057 | + return self._get_pid(self.file_pidweb) |
1058 | + |
1059 | + def pidserver(self): |
1060 | + return self._get_pid(self.file_pidserver) |
1061 | + |
1062 | + def start_createdb(self): |
1063 | + dbname = self.subdomain.lower() |
1064 | + try: |
1065 | + conn = psycopg2.connect(database=dbname) |
1066 | + log("Database %s exists"%(dbname, )) |
1067 | + except psycopg2.OperationalError: |
1068 | + log("Creating database %s"%(dbname, )) |
1069 | + conn = psycopg2.connect(database='template1') |
1070 | + conn.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT) |
1071 | + c = conn.cursor() |
1072 | + c.execute('CREATE DATABASE %s'%(dbname, )) |
1073 | + conn.close() |
1074 | + |
1075 | + def start_run_server(self,port): |
1076 | + if self.is_server_running(): |
1077 | + log("run","Server %s already running ..."%(self.subdomain, )) |
1078 | + return True |
1079 | + |
1080 | + log("branch-start-run-server", self.project_name, port=port) |
1081 | + out=open(self.log_server_path,"a") |
1082 | + |
1083 | + config_file = os.path.join(self.instance_path,'etc', 'openerprc') |
1084 | + |
1085 | + |
1086 | + dbname = self.subdomain.lower() |
1087 | + cmd=[self.server_bin_path,"-c",config_file, "-d",dbname,"--no-xmlrpc","--no-xmlrpcs","--netrpc-port=%d"%(port)] |
1088 | + |
1089 | + modules = self.get_ini('modules') |
1090 | + if not modules: |
1091 | + modules = 'base' |
1092 | + |
1093 | + if 'msf_profile' not in modules: |
1094 | + self.set_ini('load_data',0) |
1095 | + |
1096 | + cmd += ['-i', modules] |
1097 | + |
1098 | + if self.get_bool_ini('load_data') or not self.get_bool_ini('load_demo'): |
1099 | + cmd.append("--without-demo=all") |
1100 | + |
1101 | + log("run",*cmd,log=self.log_server_path) |
1102 | + p=subprocess.Popen(cmd, stdout=out, stderr=out, close_fds=True) |
1103 | + self.running_server_pid=p.pid |
1104 | + write_pid(self.file_pidserver, p.pid) |
1105 | + |
1106 | + if self.get_bool_ini('load_data') and not self.get_bool_ini('data_already_loaded'): |
1107 | + pid = os.fork() |
1108 | + if not pid: |
1109 | + self._symlink_nginx_icon('nok') |
1110 | + try: |
1111 | + msg = initdb.connect_db('admin', 'admin', dbname, '127.0.0.1', port, self.data_path) |
1112 | + except Exception, e: |
1113 | + self._email(str(e), True) |
1114 | + log('init', self.name, str(e)) |
1115 | + sys.exit(1) |
1116 | + self._symlink_nginx_icon('ok') |
1117 | + if self.get_ini('comment'): |
1118 | + msg += "\n\n%s"%(self.get_ini('comment'), ) |
1119 | + self._email(msg) |
1120 | + sys.exit(1) |
1121 | + self.set_ini('data_already_loaded', '1') |
1122 | + else: |
1123 | + self._symlink_nginx_icon('ok') |
1124 | + |
1125 | + def _email(self, msg, err=False): |
1126 | + dest = self.get_ini('email') |
1127 | + if self.runbot.smtp_host and dest: |
1128 | + if err: |
1129 | + data ="Unable to initialize %s.\n\n%s\n\n"%(self.name, msg) |
1130 | + else: |
1131 | + data ="Your instance is ready: http://%s.%s\n%s\n\n"%(self.subdomain, self.runbot.domain, msg) |
1132 | + |
1133 | + |
1134 | + data += "http://%s"%(self.runbot.domain, ) |
1135 | + msg = MIMEText(data) |
1136 | + |
1137 | + msg['Subject'] = 'Runbot %s'%(self.name, ) |
1138 | + msg['To'] = dest |
1139 | + msg['From'] = 'noreply@%s'%(self.runbot.domain, ) |
1140 | + |
1141 | + s = smtplib.SMTP(self.runbot.smtp_host) |
1142 | + s.sendmail(msg['From'], dest.split(','), msg.as_string()) |
1143 | + |
1144 | + |
1145 | + |
1146 | + def _symlink_nginx_icon(self, itype): |
1147 | + # assert itype in ['ok', 'nok'] |
1148 | + dest = os.path.join(self.runbot.nginx_path, '%s.png'%(self.name,)) |
1149 | + os.path.exists(dest) and os.remove(dest) |
1150 | + os.symlink(os.path.join(self.runbot.nginx_path,'%s.png'%(itype, )), dest) |
1151 | + |
1152 | + def start_run_web(self,port): |
1153 | + if self.is_web_running(): |
1154 | + log("run","Web %s already running ..."%(self.subdomain, )) |
1155 | + return True |
1156 | + |
1157 | + config="""[global] |
1158 | +server.environment = "development" |
1159 | +server.socket_host = "0.0.0.0" |
1160 | +server.socket_port = %d |
1161 | +server.thread_pool = 10 |
1162 | +tools.sessions.on = True |
1163 | +log.access_level = "INFO" |
1164 | +log.error_level = "INFO" |
1165 | +tools.csrf.on = False |
1166 | +tools.log_tracebacks.on = False |
1167 | +tools.cgitb.on = True |
1168 | +openerp.server.host = 'localhost' |
1169 | +openerp.server.port = %d |
1170 | +openerp.server.protocol = 'socket' |
1171 | +openerp.server.timeout = 1500 |
1172 | +tools.proxy.on = True |
1173 | +[openerp-web] |
1174 | +dblist.filter = 'BOTH' |
1175 | +dbbutton.visible = True |
1176 | +company.url = '' |
1177 | +"""%(port+1,port) |
1178 | + |
1179 | + config_file = os.path.join(self.etc_path,"openerp-web.cfg") |
1180 | + open(config_file,"w").write(config) |
1181 | + |
1182 | + out=open(self.log_web_path,"a") |
1183 | + cmd=[self.web_bin_path, '-c', config_file] |
1184 | + |
1185 | + log("run",*cmd,log=self.log_web_path) |
1186 | + p=subprocess.Popen(cmd, stdout=out, stderr=out, close_fds=True) |
1187 | + self.running_web_pid=p.pid |
1188 | + write_pid(self.file_pidweb, p.pid) |
1189 | + |
1190 | + def start(self): |
1191 | + port = self.get_int_ini('port') |
1192 | + log("branch-start",branch=self.unique_name,port=port) |
1193 | + |
1194 | + ''' |
1195 | + Here check if the instance existed already, then do not drop and recreate the DB, just launch the server and web! |
1196 | + ''' |
1197 | + |
1198 | + self.start_createdb() |
1199 | + self.start_run_server(port) |
1200 | + self.start_run_web(port) |
1201 | + |
1202 | + self.runbot.running.insert(0,self) |
1203 | + self.runbot.running.sort(key=lambda x:x.date_last_modified,reverse=1) |
1204 | + self.running_t0=time.time() |
1205 | + self.running=True |
1206 | + self.running_port=port |
1207 | + self.write_ini() |
1208 | + |
1209 | + def stop(self): |
1210 | + log("branch-stop",branch=self.unique_name,port=self.running_port) |
1211 | + pidserver = self.pidserver() |
1212 | + if pidserver: |
1213 | + log('Kill server %s'%(pidserver,)) |
1214 | + kill(pidserver) |
1215 | + pidweb = self.pidweb() |
1216 | + if pidweb: |
1217 | + log('Kill server %s'%(pidweb,)) |
1218 | + kill(pidweb) |
1219 | + if self in self.runbot.running: |
1220 | + self.runbot.running.remove(self) |
1221 | + self.running=False |
1222 | + self.running_port=None |
1223 | + |
1224 | + def delete(self, onlydb): |
1225 | + self.stop() |
1226 | + log("Delete db %s"%self.subdomain.lower()) |
1227 | + run(['dropdb', self.subdomain.lower()]) |
1228 | + if not onlydb: |
1229 | + log("Delete %s"%self.instance_path) |
1230 | + shutil.rmtree(self.instance_path) |
1231 | + try: |
1232 | + os.remove(os.path.join(self.runbot.nginx_path, '%s.png'%(self.name, ))) |
1233 | + except OSError, e: |
1234 | + pass |
1235 | + else: |
1236 | + self.set_ini('data_already_loaded',0) |
1237 | + self.write_ini() |
1238 | + |
1239 | + del(self.runbot.uf_instances[self.name]) |
1240 | + |
1241 | +class RunBot(object): |
1242 | + def __init__(self, wd, server_port, nginx_port, domain, init, smtp_host): |
1243 | + self.wd=wd |
1244 | + self.common_path = os.path.join(self.wd,"common") |
1245 | + self.common_etc = os.path.join(self.common_path,"etc") |
1246 | + self.common_configfile = os.path.join(self.common_etc,"default_config.ini") |
1247 | + self.server_port=int(server_port) |
1248 | + self.nginx_port=int(nginx_port) |
1249 | + self.domain=domain |
1250 | + self.uf_instances={} |
1251 | + self.now = time.strftime("%Y-%m-%d %H:%M:%S") |
1252 | + self.running=[] |
1253 | + self.nginx_path = os.path.join(self.wd,'nginx') |
1254 | + self.nginx_pid_path = os.path.join(self.nginx_path,'nginx.pid') |
1255 | + self.smtp_host = smtp_host |
1256 | + |
1257 | + self.running_path=os.path.join(self.wd, "running") |
1258 | + allsubdirs = self.subdirs(self.running_path) # in consumption that the sub-folder NAMES are valid |
1259 | + |
1260 | + for folder in allsubdirs: |
1261 | + rbb=self.uf_instances.setdefault(folder, RunBotBranch(self,folder)) |
1262 | + if init and rbb.get_bool_ini('start',True): |
1263 | + self.init_folder(rbb) |
1264 | + |
1265 | + def is_nginx_running(self): |
1266 | + return _is_running(self.nginx_pid()) |
1267 | + |
1268 | + def nginx_pid(self): |
1269 | + if os.path.isfile(self.nginx_pid_path): |
1270 | + return int(open(self.nginx_pid_path).read()) |
1271 | + else: |
1272 | + return False |
1273 | + |
1274 | + def nginx_reload(self): |
1275 | + pid = self.nginx_pid() |
1276 | + if pid: |
1277 | + os.kill(pid,1) |
1278 | + else: |
1279 | + run(["/usr/sbin/nginx","-p", self.wd + "/", "-c", os.path.join(self.nginx_path,"nginx.conf")]) |
1280 | + |
1281 | + def nginx_config(self): |
1282 | + template=""" |
1283 | + pid nginx/nginx.pid; |
1284 | + error_log nginx/error.log; |
1285 | + worker_processes 1; |
1286 | + events { worker_connections 1024; } |
1287 | + http { |
1288 | + include /etc/nginx/mime.types; |
1289 | + server_names_hash_bucket_size 128; |
1290 | + autoindex on; |
1291 | + client_body_temp_path nginx; proxy_temp_path nginx; fastcgi_temp_path nginx; access_log nginx/access.log; index index.html; |
1292 | + server { listen ${r.nginx_port} default; server_name _; root ./nginx/; } |
1293 | + % for i in r.running: |
1294 | + server { |
1295 | + listen ${r.nginx_port}; |
1296 | + server_name ${i.subdomain}.${r.domain}; |
1297 | + location /${i.subdomain}/logs/ { alias ${i.log_path}/; } |
1298 | + location / { proxy_pass http://127.0.0.1:${i.running_port+1}; proxy_set_header X-Forwarded-Host $host; } |
1299 | + } |
1300 | + % endfor |
1301 | + } |
1302 | + """ |
1303 | + return mako.template.Template(template).render(r=self) |
1304 | + |
1305 | + def nginx_index_time(self,t): |
1306 | + for m,u in [(86400,'d'),(3600,'h'),(60,'m')]: |
1307 | + if t>=m: |
1308 | + return str(int(t/m))+u |
1309 | + return str(int(t))+"s" |
1310 | + |
1311 | + def nginx_index(self): |
1312 | + template = """<!DOCTYPE html> |
1313 | + <html> |
1314 | + <head> |
1315 | + <title>UniField Runbot</title> |
1316 | + <link rel="shortcut icon" href="/favicon.ico" /> |
1317 | + <link rel="stylesheet" href="style.css" type="text/css"> |
1318 | + </head> |
1319 | + <body id="indexfile"> |
1320 | + <div id="header"> |
1321 | + <div class="content"><h1>UniField Manual Runbot</h1> </div> |
1322 | + </div> |
1323 | + <div id="index"> |
1324 | + <table class="index"> |
1325 | + <thead> |
1326 | + <tr> |
1327 | + <td colspan='3'><hr/></td> |
1328 | + </tr> |
1329 | + <tr class="tablehead"> |
1330 | + <th class="name left" align="left">UniField test instance</th> |
1331 | + <th>Date</th> |
1332 | + <th>Logs</th> |
1333 | + </tr> |
1334 | + <tr> |
1335 | + <td colspan='3'><hr/></td> |
1336 | + </tr> |
1337 | + <tr> |
1338 | + <td colspan='3'></td> |
1339 | + </tr> |
1340 | + </thead> |
1341 | + <tfoot> |
1342 | + <tr> |
1343 | + <td colspan='3'></td> |
1344 | + </tr> |
1345 | + <tr> |
1346 | + <td colspan='3'></td> |
1347 | + </tr> |
1348 | + <tr class="total"> |
1349 | + <td class="name left"><b>${len(r.running)} UniField test instances</b></td> |
1350 | + <td></td> |
1351 | + <td></td> |
1352 | + <td class="right"></td> |
1353 | + </tr> |
1354 | + </tfoot> |
1355 | + <tbody> |
1356 | + % for i in r.running: |
1357 | + <tr class="file"> |
1358 | + <td class="name left"> |
1359 | + <a href="http://${i.subdomain}.${r.domain}/" target="_blank">${i.subdomain}</a> <small>(netrpc: ${i.running_port+1})</small> <img src="${i.subdomain}.png" alt=""/> |
1360 | + </td> |
1361 | + <td class="date"> |
1362 | + % if t-i.running_t0 < 120: |
1363 | + <span style="color:red;">${r.now}</span> |
1364 | + % else: |
1365 | + <span style="color:green;">${r.now}</span> |
1366 | + % endif |
1367 | + </td> |
1368 | + <td> |
1369 | + <a href="http://${i.subdomain}.${r.domain}/${i.subdomain}/logs/server.txt">server</a> |
1370 | + <a href="http://${i.subdomain}.${r.domain}/${i.subdomain}/logs/web.txt">web</a> |
1371 | + </td> |
1372 | + </tr> |
1373 | + % if i.get_ini('comment'): |
1374 | + <tr> |
1375 | + <td colspan="3" class="comment">${i.get_ini('comment')}</td> |
1376 | + % endif |
1377 | + % endfor |
1378 | + <tr> |
1379 | + <td colspan='3'><hr/></td> |
1380 | + </tr> |
1381 | + <tr> |
1382 | + <td colspan='3'></td> |
1383 | + </tr> |
1384 | + </tbody> |
1385 | + </table> |
1386 | + </div> |
1387 | + <div id="footer"> |
1388 | + <div class="content"> |
1389 | + <p><b>Last modification: ${r.now}.</b></p> |
1390 | + </div> |
1391 | + </div> |
1392 | + </body> |
1393 | + """ |
1394 | + self.now = time.strftime("%Y-%m-%d %H:%M:%S") |
1395 | + return mako.template.Template(template).render(r=self,t=time.time(),re=re) |
1396 | + |
1397 | + def nginx_udpate(self): |
1398 | + """ Update the link, port and entry of the new UniField instance into 2 files: nginx.conf and index.html |
1399 | + """ |
1400 | + log("runbot-nginx-update") |
1401 | + f=open(os.path.join(self.wd,'nginx','index.html'),"w") |
1402 | + f.write(self.nginx_index()) |
1403 | + f.close() |
1404 | + f=open(os.path.join(self.wd,'nginx','nginx.conf'),"w") |
1405 | + f.write(self.nginx_config()) |
1406 | + f.close() |
1407 | + self.nginx_reload() |
1408 | + |
1409 | + def _get_port(self): |
1410 | + i = self.server_port |
1411 | + while i in self.ports: |
1412 | + i += 2 |
1413 | + return i |
1414 | + |
1415 | + def subdirs(self, dir): |
1416 | + ''' |
1417 | + Retrieve the direct sub folders of the given folder |
1418 | + ''' |
1419 | + return [name for name in os.listdir(dir) |
1420 | + if os.path.isdir(os.path.join(dir, name)) and not name.startswith('.')] |
1421 | + |
1422 | + def process_instances(self): |
1423 | + log("runbot-folder") |
1424 | + |
1425 | + ''' |
1426 | + Get the sub folders and build a list of instances to be run |
1427 | + ''' |
1428 | + self.ports = [] |
1429 | + for rbb in self.uf_instances.values(): |
1430 | + num_port = rbb.get_int_ini('port') |
1431 | + if num_port: |
1432 | + self.ports.append(num_port) |
1433 | + self.ports.append(num_port+1) |
1434 | + |
1435 | + for rbb in self.uf_instances.values(): |
1436 | + if not rbb.get_int_ini('port'): |
1437 | + new_port = self._get_port() |
1438 | + self.ports.append(new_port) |
1439 | + self.ports.append(new_port+1) |
1440 | + rbb.set_ini('port', new_port) |
1441 | + if rbb.get_bool_ini('start',True): |
1442 | + rbb.start() |
1443 | + |
1444 | + self.nginx_udpate() |
1445 | + |
1446 | + |
1447 | + def init_folder(self, rbb): |
1448 | + |
1449 | + ''' To do the following things if not exist |
1450 | + - create the logs folder for storing log files |
1451 | + - create soft-link to: unifield-server, unifield-addons, unifield-web if not exist (in case no change has been made) |
1452 | + ''' |
1453 | + |
1454 | + logs_dir = os.path.join(rbb.instance_path, "logs") |
1455 | + if not os.path.exists(logs_dir): |
1456 | + os.mkdir(logs_dir) |
1457 | + |
1458 | + # copy the unifield-server project from 'common' folder if not existed |
1459 | + self.create_module(rbb, "unifield-server") |
1460 | + self.create_module(rbb, "unifield-web") |
1461 | + # copy the folder 'etc' from 'common' folder, then fill the instance path |
1462 | + self.process_folder_etc(rbb) |
1463 | + |
1464 | + # create softlinks for these 3 projects if not existed |
1465 | + self.create_module(rbb, "unifield-addons") |
1466 | + self.create_module(rbb, "unifield-wm") |
1467 | + self.create_module(rbb, "unifield-data") |
1468 | + |
1469 | + |
1470 | + def process_folder_etc(self, rbb): |
1471 | + # delete and recopy the folder "etc" |
1472 | + project_path = rbb.etc_path |
1473 | + #run(["rm","-r", project_path]) # delete first |
1474 | + |
1475 | + if not os.path.exists(os.path.join(project_path)): |
1476 | + run(["cp","-r", self.common_etc, rbb.instance_path]) |
1477 | + |
1478 | + # replace the UF_ADDONS_PATH with the modules path of the current instance |
1479 | + config_file = os.path.join(project_path, 'openerprc') |
1480 | + for line in fileinput.FileInput(config_file, inplace=1): |
1481 | + line = line.replace("UF_ADDONS_PATH", rbb.instance_path) |
1482 | + line = line.replace("UF_INSTANCE", rbb.name) |
1483 | + line = line.replace("PIDFILE", rbb.file_pidserver) |
1484 | + sys.stdout.write(line) |
1485 | + |
1486 | + def create_module(self, rbb, module): |
1487 | + project_path = os.path.join(rbb.instance_path, module) |
1488 | + common_project_path = os.path.join(self.common_path, module) |
1489 | + if not os.path.exists(project_path): |
1490 | + source_module = rbb.get_ini(module) |
1491 | + if not source_module or source_module == 'link': |
1492 | + log('Link module %s'%(module, )) |
1493 | + run(["ln","-s", common_project_path, project_path]) |
1494 | + else: |
1495 | + directory = LaunchpadDirectory() |
1496 | + d = directory._resolve(source_module) |
1497 | + log('bzr checkout %s'%(d, )) |
1498 | + br = Branch.open(d) |
1499 | + |
1500 | + # for symlink |
1501 | + common_project_path = os.path.realpath(common_project_path) |
1502 | + orig = WorkingTree.open(common_project_path) |
1503 | + br.create_checkout(project_path, lightweight=True, accelerator_tree=orig) |
1504 | + br.repository._client._medium.disconnect() |
1505 | + |
1506 | + |
1507 | +def skel(o, r): |
1508 | + if o.instance in r.uf_instances: |
1509 | + sys.stderr.write("Error: %s exists\n"%(o.instance, )) |
1510 | + else: |
1511 | + new_folder = os.path.join(r.running_path, o.instance) |
1512 | + os.mkdir(new_folder) |
1513 | + new_ini = os.path.join(new_folder, 'config.ini') |
1514 | + shutil.copy(r.common_configfile, new_ini) |
1515 | + inf = open(r.common_configfile, 'r') |
1516 | + outf = open(new_ini, "w") |
1517 | + for line in inf: |
1518 | + if line.startswith('comment'): |
1519 | + outf.write("comment = %s\n"%(o.comment or "")) |
1520 | + elif line.startswith('email'): |
1521 | + outf.write("email = %s\n"%(o.email or "")) |
1522 | + elif line.startswith('unifield-wm'): |
1523 | + outf.write("unifield-wm = %s\n"%(o.unifield_wm or "link")) |
1524 | + elif o.unit and line.startswith('load_demo'): |
1525 | + outf.write("load_demo = 1\n") |
1526 | + elif o.unit and line.startswith('load_data'): |
1527 | + outf.write("load_data = 0\n") |
1528 | + else: |
1529 | + outf.write(line) |
1530 | + inf.close() |
1531 | + if o.start: |
1532 | + outf.close() |
1533 | + rbb = r.uf_instances.setdefault(o.instance, RunBotBranch(r,o.instance)) |
1534 | + r.init_folder(rbb) |
1535 | + r.process_instances() |
1536 | + else: |
1537 | + outf.write("start = 0") |
1538 | + sys.stderr.write("Please edit %s , and change 'start',\nyou can use vi or a friendlier editor like nano\n"%(new_ini, )) |
1539 | + outf.close() |
1540 | + |
1541 | + |
1542 | +def killall(o, r): |
1543 | + for rbb in r.uf_instances.values(): |
1544 | + rbb.stop() |
1545 | + |
1546 | +def kill_inst(o, r): |
1547 | + if o.instance not in r.uf_instances: |
1548 | + sys.stderr.write("%s not in instance\n"%o.instance) |
1549 | + else: |
1550 | + r.uf_instances[o.instance].stop() |
1551 | + |
1552 | +def list_inst(o, r): |
1553 | + sys.stderr.write("Nginx ") |
1554 | + pid = r.is_nginx_running() |
1555 | + if pid: |
1556 | + sys.stderr.write("running on port: %s, pid: %s\n"%(r.nginx_port, pid)) |
1557 | + else: |
1558 | + sys.stderr.write("isn't running\n") |
1559 | + |
1560 | + for rbb in r.uf_instances.values(): |
1561 | + sys.stderr.write("Instance %s:\n"%(rbb.name, )) |
1562 | + if not rbb.get_bool_ini('start',True): |
1563 | + sys.stderr.write(" Disabled in config.ini\n") |
1564 | + sys.stderr.write(" web: %s\n"%(rbb.is_web_running() and 'running on port %s, pid %s'%(rbb.get_int_ini('port')+1, rbb.pidweb()) or 'not running', )) |
1565 | + sys.stderr.write(" server: %s\n"%(rbb.is_server_running() and 'running on port %s, pid %s'%(rbb.get_int_ini('port'), rbb.pidserver()) or 'not running')) |
1566 | + |
1567 | +def restartall(o, r): |
1568 | + for rbb in r.uf_instances.values(): |
1569 | + rbb.stop() |
1570 | + time.sleep(1) |
1571 | + run_inst(o, r) |
1572 | + |
1573 | +def run_inst(o, r): |
1574 | + r.process_instances() |
1575 | + |
1576 | +def restart(o, r): |
1577 | + if o.instance not in r.uf_instances: |
1578 | + sys.stderr.write("%s not in instance\n"%o.instance) |
1579 | + else: |
1580 | + r.uf_instances[o.instance].stop() |
1581 | + run_inst(o, r) |
1582 | + |
1583 | +def del_inst(o, r): |
1584 | + if o.instance not in r.uf_instances: |
1585 | + sys.stderr.write("%s not in instance\n"%o.instance) |
1586 | + else: |
1587 | + r.uf_instances[o.instance].delete(o.only_db) |
1588 | + run_inst(o, r) |
1589 | + |
1590 | +def main(): |
1591 | + |
1592 | + os.chdir(os.path.normpath(os.path.dirname(__file__))) |
1593 | + parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter) |
1594 | + |
1595 | + parser.add_argument("--runbot-dir", metavar="DIR", default=".", help="runbot working dir (default: %(default)s)") |
1596 | + parser.add_argument("--runbot-port", metavar="PORT", default=9200, help="starting port for servers (default: %(default)s)") |
1597 | + parser.add_argument("--runbot-nginx-port", metavar="PORT", default=9100, help="starting port for nginx server (default: %(default)s)") |
1598 | + parser.add_argument("--runbot-nginx-domain", metavar="DOMAIN", default="runbot.unifield.org", help="virtual host domain (default: %(default)s)") |
1599 | + parser.add_argument("--debug", action="store_true", default=False, help="print debug on stdout (default: %(default)s)") |
1600 | + parser.add_argument("--smtp-host", metavar="HOST", default='localhost', help="smtp server (default: %(default)s)") |
1601 | + subparsers = parser.add_subparsers(dest='command') |
1602 | + run_parser = subparsers.add_parser('run', help='start/init new instances') |
1603 | + run_parser.set_defaults(func=run_inst) |
1604 | + |
1605 | + killall_parser = subparsers.add_parser('killall', help='kill all instances') |
1606 | + killall_parser.set_defaults(func=killall) |
1607 | + |
1608 | + kill_parser = subparsers.add_parser('kill', help='kill an instance') |
1609 | + kill_parser.add_argument('instance', action='store', help='instance to kill') |
1610 | + kill_parser.set_defaults(func=kill_inst) |
1611 | + |
1612 | + restartall_parser = subparsers.add_parser('restartall', help='restart all instances') |
1613 | + restartall_parser.set_defaults(func=restartall) |
1614 | + |
1615 | + restart_parser = subparsers.add_parser('restart', help='restart an instance') |
1616 | + restart_parser.add_argument('instance', action='store', help='instance to kill') |
1617 | + restart_parser.set_defaults(func=restart) |
1618 | + |
1619 | + list_parser = subparsers.add_parser('list', help='list all instances') |
1620 | + list_parser.set_defaults(func=list_inst) |
1621 | + |
1622 | + skel_parser = subparsers.add_parser('skel', help='create a directory for a new instance') |
1623 | + skel_parser.add_argument('instance', action='store', help='instance') |
1624 | + skel_parser.add_argument('--start', action='store_true', default=False, help='Start this instance') |
1625 | + skel_parser.add_argument('--unit', action='store_true', default=False, help='Run instance with unit test (load demo)') |
1626 | + skel_parser.add_argument('--unifield-wm', metavar='URL', default='link', help='Launchpad url or keyword "link" (default: %(default)s)') |
1627 | + skel_parser.add_argument('--comment') |
1628 | + skel_parser.add_argument('--email') |
1629 | + skel_parser.set_defaults(func=skel) |
1630 | + |
1631 | + delete_parser = subparsers.add_parser('delete', help='delete an instance') |
1632 | + delete_parser.add_argument('instance', action='store', help='instance') |
1633 | + delete_parser.add_argument("--only-db", action="store_true", default=False, help="delete the database and not the directory (default: delete db+directory)") |
1634 | + delete_parser.set_defaults(func=del_inst) |
1635 | + |
1636 | + o = parser.parse_args() |
1637 | + if (o.runbot_dir == '.'): |
1638 | + o.runbot_dir = os.getcwd() #get the full path for the current working directory |
1639 | + |
1640 | + invalid_character = ['-'] |
1641 | + |
1642 | + for char in invalid_character: |
1643 | + if char in o.instance: |
1644 | + raise Exception('\'%s\' is an invalid character in the name of the instance' % char) |
1645 | + |
1646 | + fsock = False |
1647 | + if not o.debug: |
1648 | + fsock = open('out.log', 'a') |
1649 | + sys.stdout = fsock |
1650 | + init = o.command == 'run' |
1651 | + r = RunBot(o.runbot_dir, o.runbot_port, o.runbot_nginx_port, o.runbot_nginx_domain, init, o.smtp_host) |
1652 | + o.func(o, r) |
1653 | + if fsock: |
1654 | + fsock.close() |
1655 | + |
1656 | + |
1657 | +if __name__ == '__main__': |
1658 | + main() |
1659 | |
1660 | === added directory 'running' |