Merge lp:~unifield-team/unifield-toolbox/invalid-name-instance into lp:unifield-toolbox

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
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.

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'
876Binary 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'
878Binary 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'

Subscribers

People subscribed via source and target branches