diff --git a/openerp-long-polling b/openerp-gevent
similarity index 69%
rename from openerp-long-polling
rename to openerp-gevent
index 304cd01c1ee..55044976492 100755
--- a/openerp-long-polling
+++ b/openerp-gevent
@@ -2,8 +2,8 @@
import gevent.monkey
gevent.monkey.patch_all()
-import gevent_psycopg2
-gevent_psycopg2.monkey_patch()
+import psycogreen.gevent
+psycogreen.gevent.patch_psycopg()
import openerp
diff --git a/openerp/__init__.py b/openerp/__init__.py
index 22c8c98256a..a06c85d0c21 100644
--- a/openerp/__init__.py
+++ b/openerp/__init__.py
@@ -30,7 +30,6 @@ evented = False
if sys.modules.get("gevent") is not None:
evented = True
-
# Make sure the OpenERP server runs in UTC. This is especially necessary
# under Windows as under Linux it seems the real import of time is
# sufficiently deferred so that setting the TZ environment variable
diff --git a/openerp/addons/base/__init__.py b/openerp/addons/base/__init__.py
index d666a6abbf7..a5bc4c59484 100644
--- a/openerp/addons/base/__init__.py
+++ b/openerp/addons/base/__init__.py
@@ -20,6 +20,7 @@
##############################################################################
import ir
+import workflow
import module
import res
import report
diff --git a/openerp/addons/base/__openerp__.py b/openerp/addons/base/__openerp__.py
index f2df88506f5..808dd18d3e5 100644
--- a/openerp/addons/base/__openerp__.py
+++ b/openerp/addons/base/__openerp__.py
@@ -35,21 +35,20 @@ The kernel of OpenERP, needed for all installation.
'depends': [],
'data': [
'base_data.xml',
- 'currency_data.xml',
+ 'res/res_currency_data.xml',
'res/res_country_data.xml',
'security/base_security.xml',
'base_menu.xml',
'res/res_security.xml',
'res/res_config.xml',
- 'data/res.country.state.csv',
- 'ir/wizard/wizard_menu_view.xml',
+ 'res/res.country.state.csv',
'ir/ir_actions.xml',
- 'ir/ir_attachment_view.xml',
'ir/ir_config_parameter_view.xml',
'ir/ir_cron_view.xml',
'ir/ir_filters.xml',
'ir/ir_mail_server_view.xml',
'ir/ir_model_view.xml',
+ 'ir/ir_attachment_view.xml',
'ir/ir_rule_view.xml',
'ir/ir_sequence_view.xml',
'ir/ir_translation_view.xml',
@@ -57,8 +56,8 @@ The kernel of OpenERP, needed for all installation.
'ir/ir_ui_view_view.xml',
'ir/ir_values_view.xml',
'ir/osv_memory_autovacuum.xml',
- 'ir/report/ir_report.xml',
- 'ir/workflow/workflow_view.xml',
+ 'ir/ir_model_report.xml',
+ 'workflow/workflow_view.xml',
'module/module_view.xml',
'module/module_data.xml',
'module/module_report.xml',
@@ -76,11 +75,9 @@ The kernel of OpenERP, needed for all installation.
'res/res_lang_view.xml',
'res/res_partner_report.xml',
'res/res_partner_view.xml',
- 'res/res_partner_shortcut_data.xml',
'res/res_bank_view.xml',
'res/res_country_view.xml',
'res/res_currency_view.xml',
- 'res/wizard/change_password_wizard_view.xml',
'res/res_users_view.xml',
'res/res_partner_data.xml',
'res/ir_property_view.xml',
diff --git a/openerp/addons/base/base_menu.xml b/openerp/addons/base/base_menu.xml
index 2e6a6d3a27f..19f1e8465e2 100644
--- a/openerp/addons/base/base_menu.xml
+++ b/openerp/addons/base/base_menu.xml
@@ -27,7 +27,6 @@
-
-
+
diff --git a/openerp/addons/base/ir/ir_cron_view.xml b/openerp/addons/base/ir/ir_cron_view.xml
index edbc2d74b86..63c8994b48b 100644
--- a/openerp/addons/base/ir/ir_cron_view.xml
+++ b/openerp/addons/base/ir/ir_cron_view.xml
@@ -86,8 +86,7 @@
{'active_test': False}
-
-
+
diff --git a/openerp/addons/base/ir/ir_model.py b/openerp/addons/base/ir/ir_model.py
index 3b709934a27..396de27903e 100644
--- a/openerp/addons/base/ir/ir_model.py
+++ b/openerp/addons/base/ir/ir_model.py
@@ -219,9 +219,11 @@ class ir_model(osv.osv):
class ir_model_fields(osv.osv):
_name = 'ir.model.fields'
_description = "Fields"
+ _rec_name = 'field_description'
_columns = {
'name': fields.char('Name', required=True, size=64, select=1),
+ 'complete_name': fields.char('Complete Name', size=64, select=1),
'model': fields.char('Object Name', size=64, required=True, select=1,
help="The technical name of the model this field belongs to"),
'relation': fields.char('Object Relation', size=64,
@@ -1134,4 +1136,32 @@ class ir_model_data(osv.osv):
_logger.info('Deleting %s@%s', res_id, model)
self.pool[model].unlink(cr, uid, [res_id])
+class wizard_model_menu(osv.osv_memory):
+ _name = 'wizard.ir.model.menu.create'
+ _columns = {
+ 'menu_id': fields.many2one('ir.ui.menu', 'Parent Menu', required=True),
+ 'name': fields.char('Menu Name', size=64, required=True),
+ }
+
+ def menu_create(self, cr, uid, ids, context=None):
+ if not context:
+ context = {}
+ model_pool = self.pool.get('ir.model')
+ for menu in self.browse(cr, uid, ids, context):
+ model = model_pool.browse(cr, uid, context.get('model_id'), context=context)
+ val = {
+ 'name': menu.name,
+ 'res_model': model.model,
+ 'view_type': 'form',
+ 'view_mode': 'tree,form'
+ }
+ action_id = self.pool.get('ir.actions.act_window').create(cr, uid, val)
+ self.pool.get('ir.ui.menu').create(cr, uid, {
+ 'name': menu.name,
+ 'parent_id': menu.menu_id.id,
+ 'action': 'ir.actions.act_window,%d' % (action_id,),
+ 'icon': 'STOCK_INDENT'
+ }, context)
+ return {'type':'ir.actions.act_window_close'}
+
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/openerp/addons/base/ir/report/modeloverview.sxw b/openerp/addons/base/ir/ir_model_report.sxw
similarity index 100%
rename from openerp/addons/base/ir/report/modeloverview.sxw
rename to openerp/addons/base/ir/ir_model_report.sxw
diff --git a/openerp/addons/base/ir/report/ir_report.xml b/openerp/addons/base/ir/ir_model_report.xml
similarity index 84%
rename from openerp/addons/base/ir/report/ir_report.xml
rename to openerp/addons/base/ir/ir_model_report.xml
index 3d077f453c8..9fcf97b6c49 100644
--- a/openerp/addons/base/ir/report/ir_report.xml
+++ b/openerp/addons/base/ir/ir_model_report.xml
@@ -7,7 +7,7 @@
model="ir.model"
name="ir.model.overview"
report_type="sxw"
- file="base/ir/report/modeloverview.sxw"
+ file="base/ir/ir_model_report.sxw"
header="False"
/>
diff --git a/openerp/addons/base/ir/ir_model_view.xml b/openerp/addons/base/ir/ir_model_view.xml
index 537c046f95d..fb68df9b5ff 100644
--- a/openerp/addons/base/ir/ir_model_view.xml
+++ b/openerp/addons/base/ir/ir_model_view.xml
@@ -1,6 +1,27 @@
+
+
+
+
+
ir.model
diff --git a/openerp/addons/base/ir/ir_rule_view.xml b/openerp/addons/base/ir/ir_rule_view.xml
index 9448af058ea..6a1e3aead43 100644
--- a/openerp/addons/base/ir/ir_rule_view.xml
+++ b/openerp/addons/base/ir/ir_rule_view.xml
@@ -85,5 +85,15 @@
+
+
+
+ Property multi-company
+
+
+ ['|',('company_id','child_of',[user.company_id.id]),('company_id','=',False)]
+
+
+
diff --git a/openerp/addons/base/ir/ir_ui_menu.py b/openerp/addons/base/ir/ir_ui_menu.py
index c23d0e08be8..f01ce54bfc2 100644
--- a/openerp/addons/base/ir/ir_ui_menu.py
+++ b/openerp/addons/base/ir/ir_ui_menu.py
@@ -32,14 +32,6 @@ from openerp import SUPERUSER_ID
MENU_ITEM_SEPARATOR = "/"
-def one_in(setA, setB):
- """Check the presence of an element of setA in setB
- """
- for x in setA:
- if x in setB:
- return True
- return False
-
class ir_ui_menu(osv.osv):
_name = 'ir.ui.menu'
diff --git a/openerp/addons/base/ir/ir_ui_view.py b/openerp/addons/base/ir/ir_ui_view.py
index 353ed4037a4..528865c9d42 100644
--- a/openerp/addons/base/ir/ir_ui_view.py
+++ b/openerp/addons/base/ir/ir_ui_view.py
@@ -868,45 +868,3 @@ class view(osv.osv):
ids = map(itemgetter(0), cr.fetchall())
return self._check_xml(cr, uid, ids)
-
-
-MOVABLE_BRANDING = ['data-oe-model','data-oe-id','data-oe-field','data-oe-xpath']
-
-class view_sc(osv.osv):
- _name = 'ir.ui.view_sc'
- _columns = {
- 'name': fields.char('Shortcut Name', size=64), # Kept for backwards compatibility only - resource name used instead (translatable)
- 'res_id': fields.integer('Resource Ref.', help="Reference of the target resource, whose model/table depends on the 'Resource Name' field."),
- 'sequence': fields.integer('Sequence'),
- 'user_id': fields.many2one('res.users', 'User Ref.', required=True, ondelete='cascade', select=True),
- 'resource': fields.char('Resource Name', size=64, required=True, select=True)
- }
-
- def _auto_init(self, cr, context=None):
- super(view_sc, self)._auto_init(cr, context)
- cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = \'ir_ui_view_sc_user_id_resource\'')
- if not cr.fetchone():
- cr.execute('CREATE INDEX ir_ui_view_sc_user_id_resource ON ir_ui_view_sc (user_id, resource)')
-
- def get_sc(self, cr, uid, user_id, model='ir.ui.menu', context=None):
- ids = self.search(cr, uid, [('user_id','=',user_id),('resource','=',model)], context=context)
- results = self.read(cr, uid, ids, ['res_id'], context=context)
- name_map = dict(self.pool[model].name_get(cr, uid, [x['res_id'] for x in results], context=context))
- # Make sure to return only shortcuts pointing to exisintg menu items.
- filtered_results = filter(lambda result: result['res_id'] in name_map, results)
- for result in filtered_results:
- result.update(name=name_map[result['res_id']])
- return filtered_results
-
- _order = 'sequence,name'
- _defaults = {
- 'resource': 'ir.ui.menu',
- 'user_id': lambda obj, cr, uid, context: uid,
- }
- _sql_constraints = [
- ('shortcut_unique', 'unique(res_id, resource, user_id)', 'Shortcut for this menu already exists!'),
- ]
-
-
-# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
-
diff --git a/openerp/addons/base/ir/ir_ui_view_view.xml b/openerp/addons/base/ir/ir_ui_view_view.xml
index 1c59aae0170..0a59bd36df7 100644
--- a/openerp/addons/base/ir/ir_ui_view_view.xml
+++ b/openerp/addons/base/ir/ir_ui_view_view.xml
@@ -115,27 +115,5 @@
-
-
- ir.ui.view_sc
-
-
-
-
-
- ir.ui.view_sc
-
-
-
-
-
-
-
-
diff --git a/openerp/addons/base/ir/wizard/__init__.py b/openerp/addons/base/ir/wizard/__init__.py
deleted file mode 100644
index 23cc77e8c15..00000000000
--- a/openerp/addons/base/ir/wizard/__init__.py
+++ /dev/null
@@ -1,23 +0,0 @@
-# -*- coding: utf-8 -*-
-##############################################################################
-#
-# OpenERP, Open Source Management Solution
-# Copyright (C) 2004-2009 Tiny SPRL ().
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License as
-# published by the Free Software Foundation, either version 3 of the
-# License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Affero General Public License for more details.
-#
-# You should have received a copy of the GNU Affero General Public License
-# along with this program. If not, see .
-#
-##############################################################################
-import wizard_menu
-# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
-
diff --git a/openerp/addons/base/ir/wizard/wizard_menu.py b/openerp/addons/base/ir/wizard/wizard_menu.py
deleted file mode 100644
index a374b3dad2d..00000000000
--- a/openerp/addons/base/ir/wizard/wizard_menu.py
+++ /dev/null
@@ -1,54 +0,0 @@
-# -*- coding: utf-8 -*-
-##############################################################################
-#
-# OpenERP, Open Source Management Solution
-# Copyright (C) 2004-2009 Tiny SPRL ().
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License as
-# published by the Free Software Foundation, either version 3 of the
-# License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Affero General Public License for more details.
-#
-# You should have received a copy of the GNU Affero General Public License
-# along with this program. If not, see .
-#
-##############################################################################
-
-from openerp.osv import fields, osv
-
-class wizard_model_menu(osv.osv_memory):
- _name = 'wizard.ir.model.menu.create'
- _columns = {
- 'menu_id': fields.many2one('ir.ui.menu', 'Parent Menu', required=True),
- 'name': fields.char('Menu Name', size=64, required=True),
- }
-
- def menu_create(self, cr, uid, ids, context=None):
- if not context:
- context = {}
- model_pool = self.pool.get('ir.model')
- for menu in self.browse(cr, uid, ids, context):
- model = model_pool.browse(cr, uid, context.get('model_id'), context=context)
- val = {
- 'name': menu.name,
- 'res_model': model.model,
- 'view_type': 'form',
- 'view_mode': 'tree,form'
- }
- action_id = self.pool.get('ir.actions.act_window').create(cr, uid, val)
- self.pool.get('ir.ui.menu').create(cr, uid, {
- 'name': menu.name,
- 'parent_id': menu.menu_id.id,
- 'action': 'ir.actions.act_window,%d' % (action_id,),
- 'icon': 'STOCK_INDENT'
- }, context)
- return {'type':'ir.actions.act_window_close'}
-
-
-# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
-
diff --git a/openerp/addons/base/ir/wizard/wizard_menu_view.xml b/openerp/addons/base/ir/wizard/wizard_menu_view.xml
deleted file mode 100644
index 7fd32b256e2..00000000000
--- a/openerp/addons/base/ir/wizard/wizard_menu_view.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-
-
-
-
-
-
-
diff --git a/openerp/addons/base/ir/workflow/__init__.py b/openerp/addons/base/ir/workflow/__init__.py
deleted file mode 100644
index daa5bb1d29a..00000000000
--- a/openerp/addons/base/ir/workflow/__init__.py
+++ /dev/null
@@ -1,25 +0,0 @@
-# -*- coding: utf-8 -*-
-##############################################################################
-#
-# OpenERP, Open Source Management Solution
-# Copyright (C) 2004-2009 Tiny SPRL ().
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License as
-# published by the Free Software Foundation, either version 3 of the
-# License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Affero General Public License for more details.
-#
-# You should have received a copy of the GNU Affero General Public License
-# along with this program. If not, see .
-#
-##############################################################################
-import workflow
-import print_instance
-
-# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
-
diff --git a/openerp/addons/base/module/module.py b/openerp/addons/base/module/module.py
index e160c49481e..6279a5fe6ae 100644
--- a/openerp/addons/base/module/module.py
+++ b/openerp/addons/base/module/module.py
@@ -716,7 +716,7 @@ class module(osv.osv):
if already_installed:
# in this case, force server restart to reload python code...
cr.commit()
- openerp.service.restart_server()
+ openerp.service.server.restart()
return {
'type': 'ir.actions.client',
'tag': 'home',
diff --git a/openerp/addons/base/res/__init__.py b/openerp/addons/base/res/__init__.py
index 59403545112..50807e20116 100644
--- a/openerp/addons/base/res/__init__.py
+++ b/openerp/addons/base/res/__init__.py
@@ -31,8 +31,5 @@ import res_request
import res_lang
import ir_property
-import report
-import wizard
-
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/openerp/addons/base/res/config_pixmaps/1.png b/openerp/addons/base/res/config_pixmaps/1.png
deleted file mode 100755
index 63bd4337369..00000000000
Binary files a/openerp/addons/base/res/config_pixmaps/1.png and /dev/null differ
diff --git a/openerp/addons/base/res/config_pixmaps/2.png b/openerp/addons/base/res/config_pixmaps/2.png
deleted file mode 100755
index 80d4e3602bb..00000000000
Binary files a/openerp/addons/base/res/config_pixmaps/2.png and /dev/null differ
diff --git a/openerp/addons/base/res/config_pixmaps/3.png b/openerp/addons/base/res/config_pixmaps/3.png
deleted file mode 100755
index 63acab34263..00000000000
Binary files a/openerp/addons/base/res/config_pixmaps/3.png and /dev/null differ
diff --git a/openerp/addons/base/res/report/__init__.py b/openerp/addons/base/res/report/__init__.py
deleted file mode 100644
index 0a7dcc8620f..00000000000
--- a/openerp/addons/base/res/report/__init__.py
+++ /dev/null
@@ -1,28 +0,0 @@
-# -*- coding: utf-8 -*-
-##############################################################################
-#
-# OpenERP, Open Source Management Solution
-# Copyright (C) 2004-2009 Tiny SPRL ().
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License as
-# published by the Free Software Foundation, either version 3 of the
-# License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Affero General Public License for more details.
-#
-# You should have received a copy of the GNU Affero General Public License
-# along with this program. If not, see .
-#
-##############################################################################
-
-#from report import report_sxw
-#report_sxw.report_sxw('report.partner.list', 'res.partner', 'addons/base/res/partner/report/partner_list.rml')
-
-
-
-# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
-
diff --git a/openerp/addons/base/res/report/business_card.xml b/openerp/addons/base/res/report/business_card.xml
deleted file mode 100644
index c4695ffa020..00000000000
--- a/openerp/addons/base/res/report/business_card.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
-
-
-
-
-
- Tiny sprl
- Rue du Vieux Chateau, 21
- BE-1457
- Walhain
- http://tiny.be
-
-
diff --git a/openerp/addons/base/res/report/business_card.xsl b/openerp/addons/base/res/report/business_card.xsl
deleted file mode 100644
index f274d9437f6..00000000000
--- a/openerp/addons/base/res/report/business_card.xsl
+++ /dev/null
@@ -1,80 +0,0 @@
-
-
-
-20.5
-0.5
-5.5
-8.5
-5.5cm
-8.5cm
-3
-8
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- cm
-
-
-
- cm
-
-
-
-
-
-
-
- PACKINGLIST
-
-
-
- | Logo |
- |
-
- |
- |
- |
- |
- |
-
-
-
-
-
-
-
-
-
-
-
diff --git a/openerp/addons/base/data/res.country.state.csv b/openerp/addons/base/res/res.country.state.csv
similarity index 100%
rename from openerp/addons/base/data/res.country.state.csv
rename to openerp/addons/base/res/res.country.state.csv
diff --git a/openerp/addons/base/res/res_config.py b/openerp/addons/base/res/res_config.py
index abfe8df7268..c59f56d9fa6 100644
--- a/openerp/addons/base/res/res_config.py
+++ b/openerp/addons/base/res/res_config.py
@@ -131,6 +131,7 @@ class res_config_configurable(osv.osv_memory):
"""
raise NotImplementedError(
'Configuration items need to implement execute')
+
def cancel(self, cr, uid, ids, context=None):
""" Method called when the user click on the ``Skip`` button.
@@ -184,8 +185,6 @@ class res_config_configurable(osv.osv_memory):
if next: return next
return self.next(cr, uid, ids, context=context)
-res_config_configurable()
-
class res_config_installer(osv.osv_memory, res_config_module_installation_mixin):
""" New-style configuration base specialized for addons selection
and installation.
@@ -314,7 +313,6 @@ class res_config_installer(osv.osv_memory, res_config_module_installation_mixin)
context=context),
context=context)
-
def modules_to_install(self, cr, uid, ids, context=None):
""" selects all modules to install:
@@ -397,45 +395,6 @@ class res_config_installer(osv.osv_memory, res_config_module_installation_mixin)
return self._install_modules(cr, uid, modules, context=context)
-res_config_installer()
-
-DEPRECATION_MESSAGE = 'You are using an addon using old-style configuration '\
- 'wizards (ir.actions.configuration.wizard). Old-style configuration '\
- 'wizards have been deprecated.\n'\
- 'The addon should be migrated to res.config objects.'
-class ir_actions_configuration_wizard(osv.osv_memory):
- ''' Compatibility configuration wizard
-
- The old configuration wizard has been replaced by res.config, but in order
- not to break existing but not-yet-migrated addons, the old wizard was
- reintegrated and gutted.
- '''
- _name='ir.actions.configuration.wizard'
- _inherit = 'res.config'
-
- def _next_action_note(self, cr, uid, ids, context=None):
- next = self._next_action(cr, uid)
- if next:
- # if the next one is also an old-style extension, you never know...
- if next.note:
- return next.note
- return _("Click 'Continue' to configure the next addon...")
- return _("Your database is now fully configured.\n\n"\
- "Click 'Continue' and enjoy your OpenERP experience...")
-
- _columns = {
- 'note': fields.text('Next Wizard', readonly=True),
- }
- _defaults = {
- 'note': _next_action_note,
- }
-
- def execute(self, cr, uid, ids, context=None):
- _logger.warning(DEPRECATION_MESSAGE)
-
-ir_actions_configuration_wizard()
-
-
class res_config_settings(osv.osv_memory, res_config_module_installation_mixin):
""" Base configuration wizard for application settings. It provides support for setting
default values, assigning groups to employee users, and installing modules.
@@ -694,4 +653,5 @@ class res_config_settings(osv.osv_memory, res_config_module_installation_mixin):
if (action_id):
return exceptions.RedirectWarning(msg % values, action_id, _('Go to the configuration panel'))
return exceptions.Warning(msg % values)
+
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/openerp/addons/base/res/res_config.xml b/openerp/addons/base/res/res_config.xml
index 342aeaa6e0f..066f9435434 100644
--- a/openerp/addons/base/res/res_config.xml
+++ b/openerp/addons/base/res/res_config.xml
@@ -17,23 +17,6 @@
-
- Compabitiliby configuration wizard
- ir.actions.configuration.wizard
-
-
-
-
-
Inheritable view for installer objects
res.config.installer
diff --git a/openerp/addons/base/currency_data.xml b/openerp/addons/base/res/res_currency_data.xml
similarity index 100%
rename from openerp/addons/base/currency_data.xml
rename to openerp/addons/base/res/res_currency_data.xml
diff --git a/openerp/addons/base/res/res_partner_demo.xml b/openerp/addons/base/res/res_partner_demo.xml
index a79623c5917..723d2c0972d 100644
--- a/openerp/addons/base/res/res_partner_demo.xml
+++ b/openerp/addons/base/res/res_partner_demo.xml
@@ -410,15 +410,7 @@
vauxoo.com
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
-
-
- ASUSTEK-Subject
-
-
-
diff --git a/openerp/addons/base/res/res_partner_report.xml b/openerp/addons/base/res/res_partner_report.xml
index 80ab161e62c..00b8a84608e 100644
--- a/openerp/addons/base/res/res_partner_report.xml
+++ b/openerp/addons/base/res/res_partner_report.xml
@@ -1,9 +1,13 @@
-
-
+
diff --git a/openerp/addons/base/res/report/partner_address.xml b/openerp/addons/base/res/res_partner_report_address.xml
similarity index 100%
rename from openerp/addons/base/res/report/partner_address.xml
rename to openerp/addons/base/res/res_partner_report_address.xml
diff --git a/openerp/addons/base/res/report/partner_address.xsl b/openerp/addons/base/res/res_partner_report_address.xsl
similarity index 100%
rename from openerp/addons/base/res/report/partner_address.xsl
rename to openerp/addons/base/res/res_partner_report_address.xsl
diff --git a/openerp/addons/base/res/res_partner_shortcut_data.xml b/openerp/addons/base/res/res_partner_shortcut_data.xml
deleted file mode 100644
index ee534a2ff6b..00000000000
--- a/openerp/addons/base/res/res_partner_shortcut_data.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
-
-
- Customers
- ir.ui.menu
-
-
-
-
-
-
diff --git a/openerp/addons/base/res/res_request.py b/openerp/addons/base/res/res_request.py
index e91ebacbe2a..a565f3c7844 100644
--- a/openerp/addons/base/res/res_request.py
+++ b/openerp/addons/base/res/res_request.py
@@ -19,74 +19,14 @@
#
##############################################################################
-import time
-
from openerp.osv import osv, fields
-def _links_get(self, cr, uid, context=None):
+def referencable_models(self, cr, uid, context=None):
obj = self.pool.get('res.request.link')
ids = obj.search(cr, uid, [], context=context)
res = obj.read(cr, uid, ids, ['object', 'name'], context)
return [(r['object'], r['name']) for r in res]
-class res_request(osv.osv):
- _name = 'res.request'
-
- def request_send(self, cr, uid, ids, *args):
- for id in ids:
- cr.execute('update res_request set state=%s,date_sent=%s where id=%s', ('waiting', time.strftime('%Y-%m-%d %H:%M:%S'), id))
- cr.execute('select act_from,act_to,body,date_sent from res_request where id=%s', (id,))
- values = cr.dictfetchone()
- if values['body'] and (len(values['body']) > 128):
- values['name'] = values['body'][:125] + '...'
- else:
- values['name'] = values['body'] or '/'
- values['req_id'] = id
- self.pool.get('res.request.history').create(cr, uid, values)
- return True
-
- def request_reply(self, cr, uid, ids, *args):
- for id in ids:
- cr.execute("update res_request set state='active', act_from=%s, act_to=act_from, trigger_date=NULL, body='' where id=%s", (uid,id))
- return True
-
- def request_close(self, cr, uid, ids, *args):
- self.write(cr, uid, ids, {'state':'closed'})
- return True
-
- def request_get(self, cr, uid):
- cr.execute('select id from res_request where act_to=%s and (trigger_date<=%s or trigger_date is null) and active=True and state != %s', (uid,time.strftime('%Y-%m-%d'), 'closed'))
- ids = map(lambda x:x[0], cr.fetchall())
- cr.execute('select id from res_request where act_from=%s and (act_to<>%s) and (trigger_date<=%s or trigger_date is null) and active=True and state != %s', (uid,uid,time.strftime('%Y-%m-%d'), 'closed'))
- ids2 = map(lambda x:x[0], cr.fetchall())
- return ids, ids2
-
- _columns = {
- 'create_date': fields.datetime('Created Date', readonly=True),
- 'name': fields.char('Subject', states={'waiting':[('readonly',True)],'active':[('readonly',True)],'closed':[('readonly',True)]}, required=True, size=128),
- 'active': fields.boolean('Active'),
- 'priority': fields.selection([('0','Low'),('1','Normal'),('2','High')], 'Priority', states={'waiting':[('readonly',True)],'closed':[('readonly',True)]}, required=True),
- 'act_from': fields.many2one('res.users', 'From', required=True, readonly=True, states={'closed':[('readonly',True)]}, select=1),
- 'act_to': fields.many2one('res.users', 'To', required=True, states={'waiting':[('readonly',True)],'closed':[('readonly',True)]}, select=1),
- 'body': fields.text('Request', states={'waiting':[('readonly',True)],'closed':[('readonly',True)]}),
- 'date_sent': fields.datetime('Date', readonly=True),
- 'trigger_date': fields.datetime('Trigger Date', states={'waiting':[('readonly',True)],'closed':[('readonly',True)]}, select=1),
- 'ref_partner_id':fields.many2one('res.partner', 'Partner Ref.', states={'closed':[('readonly',True)]}),
- 'ref_doc1':fields.reference('Document Ref 1', selection=_links_get, size=128, states={'closed':[('readonly',True)]}),
- 'ref_doc2':fields.reference('Document Ref 2', selection=_links_get, size=128, states={'closed':[('readonly',True)]}),
- 'state': fields.selection([('draft','draft'),('waiting','waiting'),('active','active'),('closed','closed')], 'Status', required=True, readonly=True),
- 'history': fields.one2many('res.request.history','req_id', 'History')
- }
- _defaults = {
- 'act_from': lambda obj,cr,uid,context=None: uid,
- 'state': 'draft',
- 'active': True,
- 'priority': '1',
- }
- _order = 'priority desc, trigger_date, create_date desc'
- _table = 'res_request'
-res_request()
-
class res_request_link(osv.osv):
_name = 'res.request.link'
_columns = {
@@ -98,26 +38,6 @@ class res_request_link(osv.osv):
'priority': 5,
}
_order = 'priority'
-res_request_link()
-
-class res_request_history(osv.osv):
- _name = 'res.request.history'
- _columns = {
- 'name': fields.char('Summary', size=128, states={'active':[('readonly',True)],'waiting':[('readonly',True)]}, required=True),
- 'req_id': fields.many2one('res.request', 'Request', required=True, ondelete='cascade', select=True),
- 'act_from': fields.many2one('res.users', 'From', required=True, readonly=True),
- 'act_to': fields.many2one('res.users', 'To', required=True, states={'waiting':[('readonly',True)]}),
- 'body': fields.text('Body', states={'waiting':[('readonly',True)]}),
- 'date_sent': fields.datetime('Date sent', states={'waiting':[('readonly',True)]}, required=True)
- }
- _defaults = {
- 'name': 'NoName',
- 'act_from': lambda obj,cr,uid,context=None: uid,
- 'act_to': lambda obj,cr,uid,context=None: uid,
- 'date_sent': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
- }
-res_request_history()
-
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/openerp/addons/base/res/res_request_view.xml b/openerp/addons/base/res/res_request_view.xml
index 7ad47a53c2e..03713f79926 100644
--- a/openerp/addons/base/res/res_request_view.xml
+++ b/openerp/addons/base/res/res_request_view.xml
@@ -35,16 +35,15 @@
-
- Request Reference Types
+ Referencable Models
ir.actions.act_window
res.request.link
form
-
+
diff --git a/openerp/addons/base/res/res_users.py b/openerp/addons/base/res/res_users.py
index ba709a48175..beb0018dc8d 100644
--- a/openerp/addons/base/res/res_users.py
+++ b/openerp/addons/base/res/res_users.py
@@ -34,7 +34,11 @@ from openerp.tools.translate import _
_logger = logging.getLogger(__name__)
-class groups(osv.osv):
+#----------------------------------------------------------
+# Basic res.groups and res.users
+#----------------------------------------------------------
+
+class res_groups(osv.osv):
_name = "res.groups"
_description = "Access Groups"
_rec_name = 'full_name'
@@ -81,29 +85,27 @@ class groups(osv.osv):
def search(self, cr, uid, args, offset=0, limit=None, order=None, context=None, count=False):
# add explicit ordering if search is sorted on full_name
if order and order.startswith('full_name'):
- ids = super(groups, self).search(cr, uid, args, context=context)
+ ids = super(res_groups, self).search(cr, uid, args, context=context)
gs = self.browse(cr, uid, ids, context)
gs.sort(key=lambda g: g.full_name, reverse=order.endswith('DESC'))
gs = gs[offset:offset+limit] if limit else gs[offset:]
return map(int, gs)
- return super(groups, self).search(cr, uid, args, offset, limit, order, context, count)
+ return super(res_groups, self).search(cr, uid, args, offset, limit, order, context, count)
def copy(self, cr, uid, id, default=None, context=None):
group_name = self.read(cr, uid, [id], ['name'])[0]['name']
default.update({'name': _('%s (copy)')%group_name})
- return super(groups, self).copy(cr, uid, id, default, context)
+ return super(res_groups, self).copy(cr, uid, id, default, context)
def write(self, cr, uid, ids, vals, context=None):
if 'name' in vals:
if vals['name'].startswith('-'):
raise osv.except_osv(_('Error'),
_('The name of the group can not start with "-"'))
- res = super(groups, self).write(cr, uid, ids, vals, context=context)
+ res = super(res_groups, self).write(cr, uid, ids, vals, context=context)
self.pool['ir.model.access'].call_cache_clearing_methods(cr)
return res
-groups()
-
class res_users(osv.osv):
""" User class. A res.users record models an OpenERP user and is different
from an employee.
@@ -516,12 +518,13 @@ class res_users(osv.osv):
(uid, module, ext_id))
return bool(cr.fetchone())
-
-#
-# Extension of res.groups and res.users with a relation for "implied" or
-# "inherited" groups. Once a user belongs to a group, it automatically belongs
-# to the implied groups (transitively).
+#----------------------------------------------------------
+# Implied groups
#
+# Extension of res.groups and res.users with a relation for "implied"
+# or "inherited" groups. Once a user belongs to a group, it
+# automatically belongs to the implied groups (transitively).
+#----------------------------------------------------------
class cset(object):
""" A cset (constrained set) is a set of elements that may be constrained to
@@ -551,7 +554,6 @@ def concat(ls):
return res
-
class groups_implied(osv.osv):
_inherit = 'res.groups'
@@ -618,6 +620,8 @@ class users_implied(osv.osv):
super(users_implied, self).write(cr, uid, [user.id], vals, context)
return res
+#----------------------------------------------------------
+# Vitrual checkbox and selection for res.user form view
#
# Extension of res.groups and res.users for the special groups view in the users
# form. This extension presents groups with selection and boolean widgets:
@@ -638,6 +642,7 @@ class users_implied(osv.osv):
# any of ID1, ..., IDk is in 'groups_id'
# - selection field 'sel_groups_ID1_..._IDk' is ID iff
# ID is in 'groups_id' and ID is maximal in the set {ID1, ..., IDk}
+#----------------------------------------------------------
def name_boolean_group(id): return 'in_group_' + str(id)
def name_boolean_groups(ids): return 'in_groups_' + '_'.join(map(str, ids))
@@ -661,7 +666,6 @@ def partition(f, xs):
return yes, nos
-
class groups_view(osv.osv):
_inherit = 'res.groups'
@@ -869,4 +873,67 @@ class users_view(osv.osv):
}
return res
+#----------------------------------------------------------
+# change password wizard
+#----------------------------------------------------------
+
+class change_password_wizard(osv.TransientModel):
+ """
+ A wizard to manage the change of users' passwords
+ """
+
+ _name = "change.password.wizard"
+ _description = "Change Password Wizard"
+ _columns = {
+ 'user_ids': fields.one2many('change.password.user', 'wizard_id', string='Users'),
+ }
+
+ def default_get(self, cr, uid, fields, context=None):
+ if context == None:
+ context = {}
+ user_ids = context.get('active_ids', [])
+ wiz_id = context.get('active_id', None)
+ res = []
+ users = self.pool.get('res.users').browse(cr, uid, user_ids, context=context)
+ for user in users:
+ res.append((0, 0, {
+ 'wizard_id': wiz_id,
+ 'user_id': user.id,
+ 'user_login': user.login,
+ }))
+ return {'user_ids': res}
+
+
+ def change_password_button(self, cr, uid, id, context=None):
+ wizard = self.browse(cr, uid, id, context=context)[0]
+ user_ids = []
+ for user in wizard.user_ids:
+ user_ids.append(user.id)
+ self.pool.get('change.password.user').change_password_button(cr, uid, user_ids, context=context)
+ return {
+ 'type': 'ir.actions.act_window_close',
+ }
+
+class change_password_user(osv.TransientModel):
+ """
+ A model to configure users in the change password wizard
+ """
+
+ _name = 'change.password.user'
+ _description = 'Change Password Wizard User'
+ _columns = {
+ 'wizard_id': fields.many2one('change.password.wizard', string='Wizard', required=True),
+ 'user_id': fields.many2one('res.users', string='User', required=True),
+ 'user_login': fields.char('User Login', readonly=True),
+ 'new_passwd': fields.char('New Password'),
+ }
+ _defaults = {
+ 'new_passwd': '',
+ }
+
+ def change_password_button(self, cr, uid, ids, context=None):
+ for user in self.browse(cr, uid, ids, context=context):
+ self.pool.get('res.users').write(cr, uid, user.user_id.id, {'password': user.new_passwd})
+
+
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/openerp/addons/base/res/res_users_view.xml b/openerp/addons/base/res/res_users_view.xml
index 770cb45149b..628cfc4f858 100644
--- a/openerp/addons/base/res/res_users_view.xml
+++ b/openerp/addons/base/res/res_users_view.xml
@@ -1,6 +1,41 @@
+
+
+
+ Change Password
+ change.password.wizard
+
+
+
+
+
+ Change Password Users
+ change.password.user
+
+
+
+
+
+
+
+
+
+
res.groups.search
@@ -277,5 +312,6 @@
+
diff --git a/openerp/addons/base/res/wizard/__init__.py b/openerp/addons/base/res/wizard/__init__.py
deleted file mode 100644
index f572814a2cf..00000000000
--- a/openerp/addons/base/res/wizard/__init__.py
+++ /dev/null
@@ -1,22 +0,0 @@
-# -*- coding: utf-8 -*-
-##############################################################################
-#
-# OpenERP, Open Source Management Solution
-# Copyright (C) 2004-2011 OpenERP S.A ().
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License as
-# published by the Free Software Foundation, either version 3 of the
-# License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Affero General Public License for more details.
-#
-# You should have received a copy of the GNU Affero General Public License
-# along with this program. If not, see .
-#
-##############################################################################
-
-import change_password_wizard
\ No newline at end of file
diff --git a/openerp/addons/base/res/wizard/change_password_wizard.py b/openerp/addons/base/res/wizard/change_password_wizard.py
deleted file mode 100644
index 4d1bfe67fce..00000000000
--- a/openerp/addons/base/res/wizard/change_password_wizard.py
+++ /dev/null
@@ -1,81 +0,0 @@
-# -*- coding: utf-8 -*-
-##############################################################################
-#
-# OpenERP, Open Source Management Solution
-# Copyright (C) 2004-2011 OpenERP S.A ().
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License as
-# published by the Free Software Foundation, either version 3 of the
-# License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Affero General Public License for more details.
-#
-# You should have received a copy of the GNU Affero General Public License
-# along with this program. If not, see .
-#
-##############################################################################
-
-from openerp.osv import fields, osv
-
-class change_password_wizard(osv.TransientModel):
- """
- A wizard to manage the change of users' passwords
- """
-
- _name = "change.password.wizard"
- _description = "Change Password Wizard"
- _columns = {
- 'user_ids': fields.one2many('change.password.user', 'wizard_id', string='Users'),
- }
-
- def default_get(self, cr, uid, fields, context=None):
- if context == None:
- context = {}
- user_ids = context.get('active_ids', [])
- wiz_id = context.get('active_id', None)
- res = []
- users = self.pool.get('res.users').browse(cr, uid, user_ids, context=context)
- for user in users:
- res.append((0, 0, {
- 'wizard_id': wiz_id,
- 'user_id': user.id,
- 'user_login': user.login,
- }))
- return {'user_ids': res}
-
-
- def change_password_button(self, cr, uid, id, context=None):
- wizard = self.browse(cr, uid, id, context=context)[0]
- user_ids = []
- for user in wizard.user_ids:
- user_ids.append(user.id)
- self.pool.get('change.password.user').change_password_button(cr, uid, user_ids, context=context)
- return {
- 'type': 'ir.actions.act_window_close',
- }
-
-class change_password_user(osv.TransientModel):
- """
- A model to configure users in the change password wizard
- """
-
- _name = 'change.password.user'
- _description = 'Change Password Wizard User'
- _columns = {
- 'wizard_id': fields.many2one('change.password.wizard', string='Wizard', required=True),
- 'user_id': fields.many2one('res.users', string='User', required=True),
- 'user_login': fields.char('User Login', readonly=True),
- 'new_passwd': fields.char('New Password'),
- }
- _defaults = {
- 'new_passwd': '',
- }
-
- def change_password_button(self, cr, uid, ids, context=None):
- for user in self.browse(cr, uid, ids, context=context):
- self.pool.get('res.users').write(cr, uid, user.user_id.id, {'password': user.new_passwd})
-
diff --git a/openerp/addons/base/res/wizard/change_password_wizard_view.xml b/openerp/addons/base/res/wizard/change_password_wizard_view.xml
deleted file mode 100644
index cc190c1525a..00000000000
--- a/openerp/addons/base/res/wizard/change_password_wizard_view.xml
+++ /dev/null
@@ -1,44 +0,0 @@
-
-
-
-
-
-
-
-
-
- Change Password
- change.password.wizard
-
-
-
-
-
-
-
- Change Password Users
- change.password.user
-
-
-
-
-
-
-
-
-
-
-
diff --git a/openerp/addons/base/security/ir.model.access.csv b/openerp/addons/base/security/ir.model.access.csv
index e4a0891db69..62b68c67aab 100644
--- a/openerp/addons/base/security/ir.model.access.csv
+++ b/openerp/addons/base/security/ir.model.access.csv
@@ -40,7 +40,6 @@
"access_ir_ui_view_group_user","ir_ui_view group_user","model_ir_ui_view",,1,0,0,0
"access_ir_ui_view_group_system","ir_ui_view group_system","model_ir_ui_view","group_system",1,1,1,1
"access_ir_ui_view_custom_group_user","ir_ui_view_custom_group_user","model_ir_ui_view_custom",,1,1,1,1
-"access_ir_ui_view_sc_group_user","ir_ui_view_sc group_user","model_ir_ui_view_sc",,1,1,1,1
"access_ir_values_group_all","ir_values group_all","model_ir_values",,1,1,1,1
"access_res_company_group_erp_manager","res_company group_erp_manager","model_res_company","group_erp_manager",1,1,1,1
"access_res_company_group_user","res_company group_user","model_res_company",,1,0,0,0
@@ -69,10 +68,6 @@
"access_res_partner_category_group_partner_manager","res_partner_category group_partner_manager","model_res_partner_category","group_partner_manager",1,1,1,1
"access_res_partner_title_group_user","res_partner_title group_user","model_res_partner_title","group_partner_manager",1,1,1,1
"access_res_partner_title_group_partner_manager","res_partner_title group_partner_manager","model_res_partner_title",,1,0,0,0
-"access_res_request_group_user","res_request group_user","model_res_request","base.group_user",1,1,1,1
-"access_res_request_history_group_user","res_request_history group_user","model_res_request_history","base.group_user",1,1,1,1
-"access_res_request_group_user_all","res_request group_user all","model_res_request",,1,0,0,0
-"access_res_request_history_group_user_all","res_request_history group_user all","model_res_request_history",,1,0,0,0
"access_res_request_link_group_system","res_request_link group_system","model_res_request_link","group_system",1,1,1,1
"access_res_request_link_group_user","res_request_link group_user","model_res_request_link",,1,0,0,0
"access_res_users_all","res_users all","model_res_users",,1,0,0,0
@@ -85,8 +80,6 @@
"access_ir_actions_act_window_close_group_system","ir_actions_act_window_close_group_system","model_ir_actions_act_window_close","group_system",1,1,1,1
"access_ir_actions_report_xml_all","ir_actions_report_xml","model_ir_actions_report_xml",,1,0,0,0
"access_ir_actions_report_xml_group_system","ir_actions_report_xml_group_system","model_ir_actions_report_xml","group_system",1,1,1,1
-"access_ir_actions_wizard_all","ir_actions_wizard","model_ir_actions_wizard",,1,0,0,0
-"access_ir_actions_wizard_group_system","ir_actions_wizard_group_system","model_ir_actions_wizard","group_system",1,1,1,1
"access_ir_actions_todo_group_system","ir_actions_todo group system","model_ir_actions_todo","group_system",1,1,1,1
"access_workflow_all","workflow_all","model_workflow",,1,0,0,0
"access_workflow_group_system","workflow_group_system","model_workflow","group_system",1,1,1,1
diff --git a/openerp/addons/base/workflow/__init__.py b/openerp/addons/base/workflow/__init__.py
new file mode 100644
index 00000000000..45a9f36fa36
--- /dev/null
+++ b/openerp/addons/base/workflow/__init__.py
@@ -0,0 +1,7 @@
+# -*- coding: utf-8 -*-
+
+import workflow
+import workflow_report
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+
diff --git a/openerp/addons/base/ir/workflow/workflow.py b/openerp/addons/base/workflow/workflow.py
similarity index 100%
rename from openerp/addons/base/ir/workflow/workflow.py
rename to openerp/addons/base/workflow/workflow.py
diff --git a/openerp/addons/base/ir/workflow/print_instance.py b/openerp/addons/base/workflow/workflow_report.py
similarity index 100%
rename from openerp/addons/base/ir/workflow/print_instance.py
rename to openerp/addons/base/workflow/workflow_report.py
diff --git a/openerp/addons/base/ir/workflow/workflow_view.xml b/openerp/addons/base/workflow/workflow_view.xml
similarity index 100%
rename from openerp/addons/base/ir/workflow/workflow_view.xml
rename to openerp/addons/base/workflow/workflow_view.xml
diff --git a/openerp/cli/server.py b/openerp/cli/server.py
index fc1bf61b617..24987c098e4 100644
--- a/openerp/cli/server.py
+++ b/openerp/cli/server.py
@@ -147,112 +147,15 @@ def import_translation():
cr.commit()
cr.close()
-# Variable keeping track of the number of calls to the signal handler defined
-# below. This variable is monitored by ``quit_on_signals()``.
-quit_signals_received = 0
-
-def signal_handler(sig, frame):
- """ Signal handler: exit ungracefully on the second handled signal.
-
- :param sig: the signal number
- :param frame: the interrupted stack frame or None
- """
- global quit_signals_received
- quit_signals_received += 1
- if quit_signals_received > 1:
- # logging.shutdown was already called at this point.
- sys.stderr.write("Forced shutdown.\n")
- os._exit(0)
-
-def dumpstacks(sig, frame):
- """ Signal handler: dump a stack trace for each existing thread."""
- # code from http://stackoverflow.com/questions/132058/getting-stack-trace-from-a-running-python-application#answer-2569696
- # modified for python 2.5 compatibility
- threads_info = dict([(th.ident, {'name': th.name,
- 'uid': getattr(th,'uid','n/a')})
- for th in threading.enumerate()])
- code = []
- for threadId, stack in sys._current_frames().items():
- thread_info = threads_info.get(threadId)
- code.append("\n# Thread: %s (id:%s) (uid:%s)" % \
- (thread_info and thread_info['name'] or 'n/a',
- threadId,
- thread_info and thread_info['uid'] or 'n/a'))
- for filename, lineno, name, line in traceback.extract_stack(stack):
- code.append('File: "%s", line %d, in %s' % (filename, lineno, name))
- if line:
- code.append(" %s" % (line.strip()))
- _logger.info("\n".join(code))
-
-def setup_signal_handlers(signal_handler):
- """ Register the given signal handler. """
- SIGNALS = (signal.SIGINT, signal.SIGTERM)
- if os.name == 'posix':
- map(lambda sig: signal.signal(sig, signal_handler), SIGNALS)
- signal.signal(signal.SIGQUIT, dumpstacks)
- elif os.name == 'nt':
- import win32api
- win32api.SetConsoleCtrlHandler(lambda sig: signal_handler(sig, None), 1)
-
-def quit_on_signals():
- """ Wait for one or two signals then shutdown the server.
-
- The first SIGINT or SIGTERM signal will initiate a graceful shutdown while
- a second one if any will force an immediate exit.
-
- """
- # Wait for a first signal to be handled. (time.sleep will be interrupted
- # by the signal handler.) The try/except is for the win32 case.
- try:
- while quit_signals_received == 0:
- time.sleep(60)
- except KeyboardInterrupt:
- pass
-
- config = openerp.tools.config
- openerp.service.stop_services()
-
- if getattr(openerp, 'phoenix', False):
- # like the phoenix, reborn from ashes...
- openerp.service._reexec()
- return
-
- if config['pidfile']:
- os.unlink(config['pidfile'])
- sys.exit(0)
-
-def watch_parent(beat=4):
- import gevent
- ppid = os.getppid()
- while True:
- if ppid != os.getppid():
- pid = os.getpid()
- _logger.info("LongPolling (%s) Parent changed", pid)
- # suicide !!
- os.kill(pid, signal.SIGTERM)
- return
- gevent.sleep(beat)
-
def main(args):
check_root_user()
openerp.tools.config.parse_config(args)
-
- if openerp.tools.config.options["gevent"]:
- openerp.evented = True
- _logger.info('Using gevent mode')
- import gevent.monkey
- gevent.monkey.patch_all()
- import gevent_psycopg2
- gevent_psycopg2.monkey_patch()
-
check_postgres_user()
openerp.netsvc.init_logger()
report_configuration()
config = openerp.tools.config
- setup_signal_handlers(signal_handler)
-
if config["test_file"]:
run_test_file(config['db_name'], config['test_file'])
sys.exit(0)
@@ -265,28 +168,19 @@ def main(args):
import_translation()
sys.exit(0)
- if not config["stop_after_init"]:
- setup_pid_file()
- # Some module register themselves when they are loaded so we need the
- # services to be running before loading any registry.
- if not openerp.evented:
- if config['workers']:
- openerp.service.start_services_workers()
- else:
- openerp.service.start_services()
- else:
- config['xmlrpc_port'] = config['longpolling_port']
- import gevent
- gevent.spawn(watch_parent)
- openerp.service.start_services()
-
+ # preload registryies, needed for -u --stop_after_init
rc = 0
if config['db_name']:
for dbname in config['db_name'].split(','):
if not preload_registry(dbname):
rc += 1
- if config["stop_after_init"]:
+ if not config["stop_after_init"]:
+ setup_pid_file()
+ openerp.service.server.start()
+ if config['pidfile']:
+ os.unlink(config['pidfile'])
+ else:
sys.exit(rc)
_logger.info('OpenERP server is running, waiting for connections...')
diff --git a/openerp/osv/fields.py b/openerp/osv/fields.py
index 2aeea751f89..52426af751f 100644
--- a/openerp/osv/fields.py
+++ b/openerp/osv/fields.py
@@ -183,7 +183,7 @@ class reference(_column):
_type = 'reference'
_classic_read = False # post-process to handle missing target
- def __init__(self, string, selection, size, **args):
+ def __init__(self, string, selection, size=None, **args):
_column.__init__(self, string=string, size=size, selection=selection, **args)
def get(self, cr, obj, ids, name, uid=None, context=None, values=None):
diff --git a/openerp/service/__init__.py b/openerp/service/__init__.py
index 2c6c80eb80c..6e3ae985fab 100644
--- a/openerp/service/__init__.py
+++ b/openerp/service/__init__.py
@@ -20,29 +20,12 @@
#
##############################################################################
-import logging
-import os
-import signal
-import subprocess
-import sys
-import threading
-import time
-
-import cron
-import wsgi_server
-
-import openerp
-import openerp.modules
-import openerp.netsvc
-import openerp.osv
-from openerp.release import nt_service_name
-import openerp.tools
-from openerp.tools.misc import stripped_sys_argv
-
import common
import db
import model
import report
+import wsgi_server
+import server
#.apidoc title: RPC Services
@@ -55,100 +38,5 @@ import report
low-level behavior of the wire.
"""
-_logger = logging.getLogger(__name__)
-
-def load_server_wide_modules():
- for m in openerp.conf.server_wide_modules:
- try:
- openerp.modules.module.load_openerp_module(m)
- except Exception:
- msg = ''
- if m == 'web':
- msg = """
-The `web` module is provided by the addons found in the `openerp-web` project.
-Maybe you forgot to add those addons in your addons_path configuration."""
- _logger.exception('Failed to load server-wide module `%s`.%s', m, msg)
-
-start_internal_done = False
-main_thread_id = threading.currentThread().ident
-
-def start_internal():
- global start_internal_done
- if start_internal_done:
- return
- openerp.netsvc.init_logger()
-
- load_server_wide_modules()
- start_internal_done = True
-
-def start_services():
- """ Start all services including http, and cron """
- start_internal()
- # Start the WSGI server.
- wsgi_server.start_service()
- # Start the main cron thread.
- if not openerp.evented:
- cron.start_service()
-
-def stop_services():
- """ Stop all services. """
- # stop services
- if not openerp.evented:
- cron.stop_service()
- wsgi_server.stop_service()
-
- _logger.info("Initiating shutdown")
- _logger.info("Hit CTRL-C again or send a second signal to force the shutdown.")
-
- # Manually join() all threads before calling sys.exit() to allow a second signal
- # to trigger _force_quit() in case some non-daemon threads won't exit cleanly.
- # threading.Thread.join() should not mask signals (at least in python 2.5).
- me = threading.currentThread()
- _logger.debug('current thread: %r', me)
- for thread in threading.enumerate():
- _logger.debug('process %r (%r)', thread, thread.isDaemon())
- if thread != me and not thread.isDaemon() and thread.ident != main_thread_id:
- while thread.isAlive():
- _logger.debug('join and sleep')
- # Need a busyloop here as thread.join() masks signals
- # and would prevent the forced shutdown.
- thread.join(0.05)
- time.sleep(0.05)
-
- _logger.debug('--')
- openerp.modules.registry.RegistryManager.delete_all()
- logging.shutdown()
-
-def start_services_workers():
- import openerp.service.workers
- openerp.multi_process = True
- openerp.service.workers.Multicorn(openerp.service.wsgi_server.application).run()
-
-def _reexec():
- """reexecute openerp-server process with (nearly) the same arguments"""
- if openerp.tools.osutil.is_running_as_nt_service():
- subprocess.call('net stop {0} && net start {0}'.format(nt_service_name), shell=True)
- exe = os.path.basename(sys.executable)
- args = stripped_sys_argv()
- if not args or args[0] != exe:
- args.insert(0, exe)
- os.execv(sys.executable, args)
-
-def restart_server():
- if openerp.multi_process:
- raise NotImplementedError("Multicorn is not supported (but gunicorn was)")
- pid = openerp.wsgi.core.arbiter_pid
- os.kill(pid, signal.SIGHUP)
- else:
- if os.name == 'nt':
- def reborn():
- stop_services()
- _reexec()
-
- # run in a thread to let the current thread return response to the caller.
- threading.Thread(target=reborn).start()
- else:
- openerp.phoenix = True
- os.kill(os.getpid(), signal.SIGINT)
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/openerp/service/cron.py b/openerp/service/cron.py
deleted file mode 100644
index f21b27f7fea..00000000000
--- a/openerp/service/cron.py
+++ /dev/null
@@ -1,76 +0,0 @@
-# -*- coding: utf-8 -*-
-##############################################################################
-#
-# OpenERP, Open Source Management Solution
-# Copyright (C) 2004-2011 OpenERP SA ()
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License as
-# published by the Free Software Foundation, either version 3 of the
-# License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Affero General Public License for more details.
-#
-# You should have received a copy of the GNU Affero General Public License
-# along with this program. If not, see .
-#
-##############################################################################
-
-""" Cron jobs scheduling
-
-Cron jobs are defined in the ir_cron table/model. This module deals with all
-cron jobs, for all databases of a single OpenERP server instance.
-
-"""
-
-import logging
-import threading
-import time
-from datetime import datetime
-
-import openerp
-
-_logger = logging.getLogger(__name__)
-
-SLEEP_INTERVAL = 60 # 1 min
-
-def cron_runner(number):
- while True:
- time.sleep(SLEEP_INTERVAL + number) # Steve Reich timing style
- registries = openerp.modules.registry.RegistryManager.registries
- _logger.debug('cron%d polling for jobs', number)
- for db_name, registry in registries.items():
- while True and registry.ready:
- acquired = openerp.addons.base.ir.ir_cron.ir_cron._acquire_job(db_name)
- if not acquired:
- break
-
-def start_service():
- """ Start the above runner function in a daemon thread.
-
- The thread is a typical daemon thread: it will never quit and must be
- terminated when the main process exits - with no consequence (the processing
- threads it spawns are not marked daemon).
-
- """
-
- # Force call to strptime just before starting the cron thread
- # to prevent time.strptime AttributeError within the thread.
- # See: http://bugs.python.org/issue7980
- datetime.strptime('2012-01-01', '%Y-%m-%d')
-
- for i in range(openerp.tools.config['max_cron_threads']):
- def target():
- cron_runner(i)
- t = threading.Thread(target=target, name="openerp.service.cron.cron%d" % i)
- t.setDaemon(True)
- t.start()
- _logger.debug("cron%d started!" % i)
-
-def stop_service():
- pass
-
-# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/openerp/service/server.py b/openerp/service/server.py
new file mode 100644
index 00000000000..cddb0bee444
--- /dev/null
+++ b/openerp/service/server.py
@@ -0,0 +1,866 @@
+#-----------------------------------------------------------
+# Threaded, Gevent and Prefork Servers
+#-----------------------------------------------------------
+import datetime
+import errno
+import fcntl
+import logging
+import os
+import os.path
+import platform
+import psutil
+import random
+import resource
+import select
+import signal
+import socket
+import subprocess
+import sys
+import threading
+import time
+import traceback
+
+import werkzeug.serving
+try:
+ from setproctitle import setproctitle
+except ImportError:
+ setproctitle = lambda x: None
+
+import openerp
+import openerp.tools.config as config
+from openerp.release import nt_service_name
+from openerp.tools.misc import stripped_sys_argv
+
+import wsgi_server
+
+_logger = logging.getLogger(__name__)
+
+SLEEP_INTERVAL = 60 # 1 min
+
+#----------------------------------------------------------
+# Werkzeug WSGI servers patched
+#----------------------------------------------------------
+
+class BaseWSGIServerNoBind(werkzeug.serving.BaseWSGIServer):
+ """ werkzeug Base WSGI Server patched to skip socket binding. PreforkServer
+ use this class, sets the socket and calls the process_request() manually
+ """
+ def __init__(self, app):
+ werkzeug.serving.BaseWSGIServer.__init__(self, "1", "1", app)
+ def server_bind(self):
+ # we dont bind beause we use the listen socket of PreforkServer#socket
+ # instead we close the socket
+ if self.socket:
+ self.socket.close()
+ def server_activate(self):
+ # dont listen as we use PreforkServer#socket
+ pass
+
+# _reexec() should set LISTEN_* to avoid connection refused during reload time. It
+# should also work with systemd socket activation. This is currently untested
+# and not yet used.
+
+class ThreadedWSGIServerReloadable(werkzeug.serving.ThreadedWSGIServer):
+ """ werkzeug Threaded WSGI Server patched to allow reusing a listen socket
+ given by the environement, this is used by autoreload to keep the listen
+ socket open when a reload happens.
+ """
+ def server_bind(self):
+ envfd = os.environ.get('LISTEN_FDS')
+ if envfd and os.environ.get('LISTEN_PID') == str(os.getpid()):
+ self.reload_socket = True
+ self.socket = socket.fromfd(int(envfd), socket.AF_INET, socket.SOCK_STREAM)
+ # should we os.close(int(envfd)) ? it seem python duplicate the fd.
+ else:
+ self.reload_socket = False
+ super(ThreadedWSGIServerReloadable, self).server_bind()
+
+ def server_activate(self):
+ if not self.reload_socket:
+ super(ThreadedWSGIServerReloadable, self).server_activate()
+
+#----------------------------------------------------------
+# AutoReload watcher
+#----------------------------------------------------------
+
+class AutoReload(object):
+ def __init__(self, server):
+ self.server = server
+ self.files = {}
+ self.modules = {}
+ import pyinotify
+ class EventHandler(pyinotify.ProcessEvent):
+ def __init__(self, autoreload):
+ self.autoreload = autoreload
+
+ def process_IN_CREATE(self, event):
+ _logger.debug('File created: %s', event.pathname)
+ self.autoreload.files[event.pathname] = 1
+
+ def process_IN_MODIFY(self, event):
+ _logger.debug('File modified: %s', event.pathname)
+ self.autoreload.files[event.pathname] = 1
+
+ self.wm = pyinotify.WatchManager()
+ self.handler = EventHandler(self)
+ self.notifier = pyinotify.Notifier(self.wm, self.handler, timeout=0)
+ mask = pyinotify.IN_MODIFY | pyinotify.IN_CREATE # IN_MOVED_FROM, IN_MOVED_TO ?
+ for path in openerp.tools.config.options["addons_path"].split(','):
+ _logger.info('Watching addons folder %s', path)
+ self.wm.add_watch(path, mask, rec=True)
+
+ def process_data(self, files):
+ xml_files = [i for i in files if i.endswith('.xml')]
+ addons_path = openerp.tools.config.options["addons_path"].split(',')
+ for i in xml_files:
+ for path in addons_path:
+ if i.startswith(path):
+ # find out wich addons path the file belongs to
+ # and extract it's module name
+ right = i[len(path) + 1:].split('/')
+ if len(right) < 2:
+ continue
+ module = right[0]
+ self.modules[module]=1
+ if self.modules:
+ _logger.info('autoreload: xml change detected, autoreload activated')
+ restart()
+
+ def process_python(self, files):
+ # process python changes
+ py_files = [i for i in files if i.endswith('.py')]
+ py_errors = []
+ # TODO keep python errors until they are ok
+ if py_files:
+ for i in py_files:
+ try:
+ source = open(i, 'rb').read() + '\n'
+ compile(source, i, 'exec')
+ except SyntaxError:
+ py_errors.append(i)
+ if py_errors:
+ _logger.info('autoreload: python code change detected, errors found')
+ for i in py_errors:
+ _logger.info('autoreload: SyntaxError %s',i)
+ else:
+ _logger.info('autoreload: python code updated, autoreload activated')
+ restart()
+
+ def check_thread(self):
+ # Check if some files have been touched in the addons path.
+ # If true, check if the touched file belongs to an installed module
+ # in any of the database used in the registry manager.
+ while 1:
+ while self.notifier.check_events(1000):
+ self.notifier.read_events()
+ self.notifier.process_events()
+ l = self.files.keys()
+ self.files.clear()
+ self.process_data(l)
+ self.process_python(l)
+
+ def run(self):
+ t = threading.Thread(target=self.check_thread)
+ t.setDaemon(True)
+ t.start()
+ _logger.info('AutoReload watcher running')
+
+#----------------------------------------------------------
+# Servers: Threaded, Gevented and Prefork
+#----------------------------------------------------------
+
+class CommonServer(object):
+ def __init__(self, app):
+ # TODO Change the xmlrpc_* options to http_*
+ self.app = app
+ # config
+ self.interface = config['xmlrpc_interface'] or '0.0.0.0'
+ self.port = config['xmlrpc_port']
+ # runtime
+ self.pid = os.getpid()
+
+ def dumpstacks(self):
+ """ Signal handler: dump a stack trace for each existing thread."""
+ # code from http://stackoverflow.com/questions/132058/getting-stack-trace-from-a-running-python-application#answer-2569696
+ # modified for python 2.5 compatibility
+ threads_info = dict([(th.ident, {'name': th.name,
+ 'uid': getattr(th,'uid','n/a')})
+ for th in threading.enumerate()])
+ code = []
+ for threadId, stack in sys._current_frames().items():
+ thread_info = threads_info.get(threadId)
+ code.append("\n# Thread: %s (id:%s) (uid:%s)" % \
+ (thread_info and thread_info['name'] or 'n/a',
+ threadId,
+ thread_info and thread_info['uid'] or 'n/a'))
+ for filename, lineno, name, line in traceback.extract_stack(stack):
+ code.append('File: "%s", line %d, in %s' % (filename, lineno, name))
+ if line:
+ code.append(" %s" % (line.strip()))
+ _logger.info("\n".join(code))
+
+ def close_socket(self, sock):
+ """ Closes a socket instance cleanly
+ :param sock: the network socket to close
+ :type sock: socket.socket
+ """
+ try:
+ sock.shutdown(socket.SHUT_RDWR)
+ except socket.error, e:
+ # On OSX, socket shutdowns both sides if any side closes it
+ # causing an error 57 'Socket is not connected' on shutdown
+ # of the other side (or something), see
+ # http://bugs.python.org/issue4397
+ # note: stdlib fixed test, not behavior
+ if e.errno != errno.ENOTCONN or platform.system() not in ['Darwin', 'Windows']:
+ raise
+ sock.close()
+
+class ThreadedServer(CommonServer):
+ def __init__(self, app):
+ super(ThreadedServer, self).__init__(app)
+ self.main_thread_id = threading.currentThread().ident
+ # Variable keeping track of the number of calls to the signal handler defined
+ # below. This variable is monitored by ``quit_on_signals()``.
+ self.quit_signals_received = 0
+
+ #self.socket = None
+ self.httpd = None
+
+ def signal_handler(self, sig, frame):
+ if sig in [signal.SIGINT,signal.SIGTERM]:
+ # shutdown on kill -INT or -TERM
+ self.quit_signals_received += 1
+ if self.quit_signals_received > 1:
+ # logging.shutdown was already called at this point.
+ sys.stderr.write("Forced shutdown.\n")
+ os._exit(0)
+ elif sig == signal.SIGHUP:
+ # restart on kill -HUP
+ openerp.phoenix = True
+ self.quit_signals_received += 1
+ elif sig == signal.SIGQUIT:
+ # dump stacks on kill -3
+ self.dumpstacks()
+
+ def cron_thread(self, number):
+ while True:
+ time.sleep(SLEEP_INTERVAL + number) # Steve Reich timing style
+ registries = openerp.modules.registry.RegistryManager.registries
+ _logger.debug('cron%d polling for jobs', number)
+ for db_name, registry in registries.items():
+ while True and registry.ready:
+ acquired = openerp.addons.base.ir.ir_cron.ir_cron._acquire_job(db_name)
+ if not acquired:
+ break
+
+ def cron_spawn(self):
+ """ Start the above runner function in a daemon thread.
+
+ The thread is a typical daemon thread: it will never quit and must be
+ terminated when the main process exits - with no consequence (the processing
+ threads it spawns are not marked daemon).
+
+ """
+ # Force call to strptime just before starting the cron thread
+ # to prevent time.strptime AttributeError within the thread.
+ # See: http://bugs.python.org/issue7980
+ datetime.datetime.strptime('2012-01-01', '%Y-%m-%d')
+ for i in range(openerp.tools.config['max_cron_threads']):
+ def target():
+ self.cron_thread(i)
+ t = threading.Thread(target=target, name="openerp.service.cron.cron%d" % i)
+ t.setDaemon(True)
+ t.start()
+ _logger.debug("cron%d started!" % i)
+
+ def http_thread(self):
+ def app(e,s):
+ return self.app(e,s)
+ self.httpd = ThreadedWSGIServerReloadable(self.interface, self.port, app)
+ self.httpd.serve_forever()
+
+ def http_spawn(self):
+ threading.Thread(target=self.http_thread).start()
+ _logger.info('HTTP service (werkzeug) running on %s:%s', self.interface, self.port)
+
+ def start(self):
+ _logger.debug("Setting signal handlers")
+ if os.name == 'posix':
+ signal.signal(signal.SIGINT, self.signal_handler)
+ signal.signal(signal.SIGTERM, self.signal_handler)
+ signal.signal(signal.SIGCHLD, self.signal_handler)
+ signal.signal(signal.SIGHUP, self.signal_handler)
+ signal.signal(signal.SIGQUIT, self.signal_handler)
+ elif os.name == 'nt':
+ import win32api
+ win32api.SetConsoleCtrlHandler(lambda sig: signal_handler(sig, None), 1)
+ self.cron_spawn()
+ self.http_spawn()
+
+ def stop(self):
+ """ Shutdown the WSGI server. Wait for non deamon threads.
+ """
+ _logger.info("Initiating shutdown")
+ _logger.info("Hit CTRL-C again or send a second signal to force the shutdown.")
+
+ self.httpd.shutdown()
+ self.close_socket(self.httpd.socket)
+
+ # Manually join() all threads before calling sys.exit() to allow a second signal
+ # to trigger _force_quit() in case some non-daemon threads won't exit cleanly.
+ # threading.Thread.join() should not mask signals (at least in python 2.5).
+ me = threading.currentThread()
+ _logger.debug('current thread: %r', me)
+ for thread in threading.enumerate():
+ _logger.debug('process %r (%r)', thread, thread.isDaemon())
+ if thread != me and not thread.isDaemon() and thread.ident != self.main_thread_id:
+ while thread.isAlive():
+ _logger.debug('join and sleep')
+ # Need a busyloop here as thread.join() masks signals
+ # and would prevent the forced shutdown.
+ thread.join(0.05)
+ time.sleep(0.05)
+
+ _logger.debug('--')
+ openerp.modules.registry.RegistryManager.delete_all()
+ logging.shutdown()
+
+ def run(self):
+ """ Start the http server and the cron thread then wait for a signal.
+
+ The first SIGINT or SIGTERM signal will initiate a graceful shutdown while
+ a second one if any will force an immediate exit.
+ """
+ self.start()
+
+ # Wait for a first signal to be handled. (time.sleep will be interrupted
+ # by the signal handler.) The try/except is for the win32 case.
+ try:
+ while self.quit_signals_received == 0:
+ time.sleep(60)
+ except KeyboardInterrupt:
+ pass
+
+ self.stop()
+
+ def reload(self):
+ os.kill(self.pid, signal.SIGHUP)
+
+class GeventServer(CommonServer):
+ def __init__(self, app):
+ super(GeventServer, self).__init__(app)
+ self.port = config['longpolling_port']
+ self.httpd = None
+
+ def watch_parent(self, beat=4):
+ import gevent
+ ppid = os.getppid()
+ while True:
+ if ppid != os.getppid():
+ pid = os.getpid()
+ _logger.info("LongPolling (%s) Parent changed", pid)
+ # suicide !!
+ os.kill(pid, signal.SIGTERM)
+ return
+ gevent.sleep(beat)
+
+ def start(self):
+ import gevent
+ from gevent.wsgi import WSGIServer
+ gevent.spawn(self.watch_parent)
+ self.httpd = WSGIServer((self.interface, self.port), self.app)
+ _logger.info('Evented Service (longpolling) running on %s:%s', self.interface, self.port)
+ self.httpd.serve_forever()
+
+ def stop(self):
+ import gevent
+ self.httpd.stop()
+ gevent.shutdown()
+
+ def run(self):
+ self.start()
+ self.stop()
+
+class PreforkServer(CommonServer):
+ """ Multiprocessing inspired by (g)unicorn.
+ PreforkServer (aka Multicorn) currently uses accept(2) as dispatching
+ method between workers but we plan to replace it by a more intelligent
+ dispatcher to will parse the first HTTP request line.
+ """
+ def __init__(self, app):
+ # config
+ self.address = (config['xmlrpc_interface'] or '0.0.0.0', config['xmlrpc_port'])
+ self.population = config['workers']
+ self.timeout = config['limit_time_real']
+ self.limit_request = config['limit_request']
+ # working vars
+ self.beat = 4
+ self.app = app
+ self.pid = os.getpid()
+ self.socket = None
+ self.workers_http = {}
+ self.workers_cron = {}
+ self.workers = {}
+ self.generation = 0
+ self.queue = []
+ self.long_polling_pid = None
+
+ def pipe_new(self):
+ pipe = os.pipe()
+ for fd in pipe:
+ # non_blocking
+ flags = fcntl.fcntl(fd, fcntl.F_GETFL) | os.O_NONBLOCK
+ fcntl.fcntl(fd, fcntl.F_SETFL, flags)
+ # close_on_exec
+ flags = fcntl.fcntl(fd, fcntl.F_GETFD) | fcntl.FD_CLOEXEC
+ fcntl.fcntl(fd, fcntl.F_SETFD, flags)
+ return pipe
+
+ def pipe_ping(self, pipe):
+ try:
+ os.write(pipe[1], '.')
+ except IOError, e:
+ if e.errno not in [errno.EAGAIN, errno.EINTR]:
+ raise
+
+ def signal_handler(self, sig, frame):
+ if len(self.queue) < 5 or sig == signal.SIGCHLD:
+ self.queue.append(sig)
+ self.pipe_ping(self.pipe)
+ else:
+ _logger.warn("Dropping signal: %s", sig)
+
+ def worker_spawn(self, klass, workers_registry):
+ self.generation += 1
+ worker = klass(self)
+ pid = os.fork()
+ if pid != 0:
+ worker.pid = pid
+ self.workers[pid] = worker
+ workers_registry[pid] = worker
+ return worker
+ else:
+ worker.run()
+ sys.exit(0)
+
+ def long_polling_spawn(self):
+ nargs = stripped_sys_argv('--pidfile','--workers')
+ cmd = nargs[0]
+ cmd = os.path.join(os.path.dirname(cmd), "openerp-gevent")
+ nargs[0] = cmd
+ popen = subprocess.Popen(nargs)
+ self.long_polling_pid = popen.pid
+
+ def worker_pop(self, pid):
+ if pid in self.workers:
+ _logger.debug("Worker (%s) unregistered",pid)
+ try:
+ self.workers_http.pop(pid,None)
+ self.workers_cron.pop(pid,None)
+ u = self.workers.pop(pid)
+ u.close()
+ except OSError:
+ return
+
+ def worker_kill(self, pid, sig):
+ try:
+ os.kill(pid, sig)
+ except OSError, e:
+ if e.errno == errno.ESRCH:
+ self.worker_pop(pid)
+
+ def process_signals(self):
+ while len(self.queue):
+ sig = self.queue.pop(0)
+ if sig in [signal.SIGINT,signal.SIGTERM]:
+ raise KeyboardInterrupt
+ elif sig == signal.SIGHUP:
+ # restart on kill -HUP
+ openerp.phoenix = True
+ raise KeyboardInterrupt
+ elif sig == signal.SIGQUIT:
+ # dump stacks on kill -3
+ self.dumpstacks()
+ elif sig == signal.SIGTTIN:
+ # increase number of workers
+ self.population += 1
+ elif sig == signal.SIGTTOU:
+ # decrease number of workers
+ self.population -= 1
+
+ def process_zombie(self):
+ # reap dead workers
+ while 1:
+ try:
+ wpid, status = os.waitpid(-1, os.WNOHANG)
+ if not wpid:
+ break
+ if (status >> 8) == 3:
+ msg = "Critial worker error (%s)"
+ _logger.critical(msg, wpid)
+ raise Exception(msg % wpid)
+ self.worker_pop(wpid)
+ except OSError, e:
+ if e.errno == errno.ECHILD:
+ break
+ raise
+
+ def process_timeout(self):
+ now = time.time()
+ for (pid, worker) in self.workers.items():
+ if (worker.watchdog_timeout is not None) and \
+ (now - worker.watchdog_time >= worker.watchdog_timeout):
+ _logger.error("Worker (%s) timeout", pid)
+ self.worker_kill(pid, signal.SIGKILL)
+
+ def process_spawn(self):
+ while len(self.workers_http) < self.population:
+ self.worker_spawn(WorkerHTTP, self.workers_http)
+ while len(self.workers_cron) < config['max_cron_threads']:
+ self.worker_spawn(WorkerCron, self.workers_cron)
+ if not self.long_polling_pid:
+ self.long_polling_spawn()
+
+ def sleep(self):
+ try:
+ # map of fd -> worker
+ fds = dict([(w.watchdog_pipe[0],w) for k,w in self.workers.items()])
+ fd_in = fds.keys() + [self.pipe[0]]
+ # check for ping or internal wakeups
+ ready = select.select(fd_in, [], [], self.beat)
+ # update worker watchdogs
+ for fd in ready[0]:
+ if fd in fds:
+ fds[fd].watchdog_time = time.time()
+ try:
+ # empty pipe
+ while os.read(fd, 1):
+ pass
+ except OSError, e:
+ if e.errno not in [errno.EAGAIN]:
+ raise
+ except select.error, e:
+ if e[0] not in [errno.EINTR]:
+ raise
+
+ def start(self):
+ # wakeup pipe, python doesnt throw EINTR when a syscall is interrupted
+ # by a signal simulating a pseudo SA_RESTART. We write to a pipe in the
+ # signal handler to overcome this behaviour
+ self.pipe = self.pipe_new()
+ # set signal handlers
+ signal.signal(signal.SIGINT, self.signal_handler)
+ signal.signal(signal.SIGTERM, self.signal_handler)
+ signal.signal(signal.SIGHUP, self.signal_handler)
+ signal.signal(signal.SIGCHLD, self.signal_handler)
+ signal.signal(signal.SIGQUIT, self.signal_handler)
+ signal.signal(signal.SIGTTIN, self.signal_handler)
+ signal.signal(signal.SIGTTOU, self.signal_handler)
+ # listen to socket
+ self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ self.socket.setblocking(0)
+ self.socket.bind(self.address)
+ self.socket.listen(8*self.population)
+
+ def stop(self, graceful=True):
+ if self.long_polling_pid is not None:
+ self.worker_kill(self.long_polling_pid, signal.SIGKILL) # FIXME make longpolling process handle SIGTERM correctly
+ self.long_polling_pid = None
+ if graceful:
+ _logger.info("Stopping gracefully")
+ limit = time.time() + self.timeout
+ for pid in self.workers.keys():
+ self.worker_kill(pid, signal.SIGTERM)
+ while self.workers and time.time() < limit:
+ self.process_zombie()
+ time.sleep(0.1)
+ else:
+ _logger.info("Stopping forcefully")
+ for pid in self.workers.keys():
+ self.worker_kill(pid, signal.SIGTERM)
+ self.socket.close()
+
+ def run(self):
+ self.start()
+ _logger.debug("Multiprocess starting")
+ while 1:
+ try:
+ #_logger.debug("Multiprocess beat (%s)",time.time())
+ self.process_signals()
+ self.process_zombie()
+ self.process_timeout()
+ self.process_spawn()
+ self.sleep()
+ except KeyboardInterrupt:
+ _logger.debug("Multiprocess clean stop")
+ self.stop()
+ break
+ except Exception,e:
+ _logger.exception(e)
+ self.stop(False)
+ sys.exit(-1)
+
+class Worker(object):
+ """ Workers """
+ def __init__(self, multi):
+ self.multi = multi
+ self.watchdog_time = time.time()
+ self.watchdog_pipe = multi.pipe_new()
+ # Can be set to None if no watchdog is desired.
+ self.watchdog_timeout = multi.timeout
+ self.ppid = os.getpid()
+ self.pid = None
+ self.alive = True
+ # should we rename into lifetime ?
+ self.request_max = multi.limit_request
+ self.request_count = 0
+
+ def setproctitle(self, title=""):
+ setproctitle('openerp: %s %s %s' % (self.__class__.__name__, self.pid, title))
+
+ def close(self):
+ os.close(self.watchdog_pipe[0])
+ os.close(self.watchdog_pipe[1])
+
+ def signal_handler(self, sig, frame):
+ self.alive = False
+
+ def sleep(self):
+ try:
+ ret = select.select([self.multi.socket], [], [], self.multi.beat)
+ except select.error, e:
+ if e[0] not in [errno.EINTR]:
+ raise
+
+ def process_limit(self):
+ # If our parent changed sucide
+ if self.ppid != os.getppid():
+ _logger.info("Worker (%s) Parent changed", self.pid)
+ self.alive = False
+ # check for lifetime
+ if self.request_count >= self.request_max:
+ _logger.info("Worker (%d) max request (%s) reached.", self.pid, self.request_count)
+ self.alive = False
+ # Reset the worker if it consumes too much memory (e.g. caused by a memory leak).
+ rss, vms = psutil.Process(os.getpid()).get_memory_info()
+ if vms > config['limit_memory_soft']:
+ _logger.info('Worker (%d) virtual memory limit (%s) reached.', self.pid, vms)
+ self.alive = False # Commit suicide after the request.
+
+ # VMS and RLIMIT_AS are the same thing: virtual memory, a.k.a. address space
+ soft, hard = resource.getrlimit(resource.RLIMIT_AS)
+ resource.setrlimit(resource.RLIMIT_AS, (config['limit_memory_hard'], hard))
+
+ # SIGXCPU (exceeded CPU time) signal handler will raise an exception.
+ r = resource.getrusage(resource.RUSAGE_SELF)
+ cpu_time = r.ru_utime + r.ru_stime
+ def time_expired(n, stack):
+ _logger.info('Worker (%d) CPU time limit (%s) reached.', config['limit_time_cpu'])
+ # We dont suicide in such case
+ raise Exception('CPU time limit exceeded.')
+ signal.signal(signal.SIGXCPU, time_expired)
+ soft, hard = resource.getrlimit(resource.RLIMIT_CPU)
+ resource.setrlimit(resource.RLIMIT_CPU, (cpu_time + config['limit_time_cpu'], hard))
+
+ def process_work(self):
+ pass
+
+ def start(self):
+ self.pid = os.getpid()
+ self.setproctitle()
+ _logger.info("Worker %s (%s) alive", self.__class__.__name__, self.pid)
+ # Reseed the random number generator
+ random.seed()
+ # Prevent fd inherientence close_on_exec
+ flags = fcntl.fcntl(self.multi.socket, fcntl.F_GETFD) | fcntl.FD_CLOEXEC
+ fcntl.fcntl(self.multi.socket, fcntl.F_SETFD, flags)
+ # reset blocking status
+ self.multi.socket.setblocking(0)
+ signal.signal(signal.SIGINT, self.signal_handler)
+ signal.signal(signal.SIGTERM, signal.SIG_DFL)
+ signal.signal(signal.SIGCHLD, signal.SIG_DFL)
+
+ def stop(self):
+ pass
+
+ def run(self):
+ try:
+ self.start()
+ while self.alive:
+ self.process_limit()
+ self.multi.pipe_ping(self.watchdog_pipe)
+ self.sleep()
+ self.process_work()
+ _logger.info("Worker (%s) exiting. request_count: %s.", self.pid, self.request_count)
+ self.stop()
+ except Exception,e:
+ _logger.exception("Worker (%s) Exception occured, exiting..." % self.pid)
+ # should we use 3 to abort everything ?
+ sys.exit(1)
+
+class WorkerHTTP(Worker):
+ """ HTTP Request workers """
+ def process_request(self, client, addr):
+ client.setblocking(1)
+ client.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
+ # Prevent fd inherientence close_on_exec
+ flags = fcntl.fcntl(client, fcntl.F_GETFD) | fcntl.FD_CLOEXEC
+ fcntl.fcntl(client, fcntl.F_SETFD, flags)
+ # do request using BaseWSGIServerNoBind monkey patched with socket
+ self.server.socket = client
+ # tolerate broken pipe when the http client closes the socket before
+ # receiving the full reply
+ try:
+ self.server.process_request(client,addr)
+ except IOError, e:
+ if e.errno != errno.EPIPE:
+ raise
+ self.request_count += 1
+
+ def process_work(self):
+ try:
+ client, addr = self.multi.socket.accept()
+ self.process_request(client, addr)
+ except socket.error, e:
+ if e[0] not in (errno.EAGAIN, errno.ECONNABORTED):
+ raise
+
+ def start(self):
+ Worker.start(self)
+ self.server = BaseWSGIServerNoBind(self.multi.app)
+
+class WorkerCron(Worker):
+ """ Cron workers """
+
+ def __init__(self, multi):
+ super(WorkerCron, self).__init__(multi)
+ # process_work() below process a single database per call.
+ # The variable db_index is keeping track of the next database to
+ # process.
+ self.db_index = 0
+
+ def sleep(self):
+ # Really sleep once all the databases have been processed.
+ if self.db_index == 0:
+ interval = SLEEP_INTERVAL + self.pid % 10 # chorus effect
+ time.sleep(interval)
+
+ def _db_list(self):
+ if config['db_name']:
+ db_names = config['db_name'].split(',')
+ else:
+ db_names = openerp.service.db.exp_list(True)
+ return db_names
+
+ def process_work(self):
+ rpc_request = logging.getLogger('openerp.netsvc.rpc.request')
+ rpc_request_flag = rpc_request.isEnabledFor(logging.DEBUG)
+ _logger.debug("WorkerCron (%s) polling for jobs", self.pid)
+ db_names = self._db_list()
+ if len(db_names):
+ self.db_index = (self.db_index + 1) % len(db_names)
+ db_name = db_names[self.db_index]
+ self.setproctitle(db_name)
+ if rpc_request_flag:
+ start_time = time.time()
+ start_rss, start_vms = psutil.Process(os.getpid()).get_memory_info()
+
+ import openerp.addons.base as base
+ base.ir.ir_cron.ir_cron._acquire_job(db_name)
+ openerp.modules.registry.RegistryManager.delete(db_name)
+
+ # dont keep cursors in multi database mode
+ if len(db_names) > 1:
+ openerp.sql_db.close_db(db_name)
+ if rpc_request_flag:
+ end_time = time.time()
+ end_rss, end_vms = psutil.Process(os.getpid()).get_memory_info()
+ logline = '%s time:%.3fs mem: %sk -> %sk (diff: %sk)' % (db_name, end_time - start_time, start_vms / 1024, end_vms / 1024, (end_vms - start_vms)/1024)
+ _logger.debug("WorkerCron (%s) %s", self.pid, logline)
+
+ self.request_count += 1
+ if self.request_count >= self.request_max and self.request_max < len(db_names):
+ _logger.error("There are more dabatases to process than allowed "
+ "by the `limit_request` configuration variable: %s more.",
+ len(db_names) - self.request_max)
+ else:
+ self.db_index = 0
+
+ def start(self):
+ os.nice(10) # mommy always told me to be nice with others...
+ Worker.start(self)
+ self.multi.socket.close()
+
+ # chorus effect: make cron workers do not all start at first database
+ mct = config['max_cron_threads']
+ p = float(self.pid % mct) / mct
+ self.db_index = int(len(self._db_list()) * p)
+
+#----------------------------------------------------------
+# start/stop public api
+#----------------------------------------------------------
+
+server = None
+
+def load_server_wide_modules():
+ for m in openerp.conf.server_wide_modules:
+ try:
+ openerp.modules.module.load_openerp_module(m)
+ except Exception:
+ msg = ''
+ if m == 'web':
+ msg = """
+The `web` module is provided by the addons found in the `openerp-web` project.
+Maybe you forgot to add those addons in your addons_path configuration."""
+ _logger.exception('Failed to load server-wide module `%s`.%s', m, msg)
+
+def _reexec(updated_modules=None):
+ """reexecute openerp-server process with (nearly) the same arguments"""
+ if openerp.tools.osutil.is_running_as_nt_service():
+ subprocess.call('net stop {0} && net start {0}'.format(nt_service_name), shell=True)
+ exe = os.path.basename(sys.executable)
+ args = stripped_sys_argv()
+ args += ["-u", ','.join(updated_modules)]
+ if not args or args[0] != exe:
+ args.insert(0, exe)
+ os.execv(sys.executable, args)
+
+def start():
+ """ Start the openerp http server and cron processor.
+ """
+ global server
+ load_server_wide_modules()
+ if config['workers']:
+ openerp.multi_process = True
+ server = PreforkServer(openerp.service.wsgi_server.application)
+ elif openerp.evented:
+ server = GeventServer(openerp.service.wsgi_server.application)
+ else:
+ server = ThreadedServer(openerp.service.wsgi_server.application)
+
+ if config['auto_reload']:
+ autoreload = AutoReload(server)
+ autoreload.run()
+
+ server.run()
+
+ # like the legend of the phoenix, all ends with beginnings
+ if getattr(openerp, 'phoenix', False):
+ modules = []
+ if config['auto_reload']:
+ modules = autoreload.modules.keys()
+ _reexec(modules)
+ sys.exit(0)
+
+def restart():
+ """ Restart the server
+ """
+ if os.name == 'nt':
+ # run in a thread to let the current thread return response to the caller.
+ threading.Thread(target=_reexec).start()
+ else:
+ os.kill(server.pid, signal.SIGHUP)
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/openerp/service/workers.py b/openerp/service/workers.py
deleted file mode 100644
index c8b8b84bca2..00000000000
--- a/openerp/service/workers.py
+++ /dev/null
@@ -1,448 +0,0 @@
-#-----------------------------------------------------------
-# Multicorn, multiprocessing inspired by gunicorn
-# TODO rename class: Multicorn -> Arbiter ?
-#-----------------------------------------------------------
-import errno
-import fcntl
-import logging
-import os
-import psutil
-import random
-import resource
-import select
-import signal
-import socket
-import sys
-import time
-import subprocess
-import os.path
-
-import werkzeug.serving
-try:
- from setproctitle import setproctitle
-except ImportError:
- setproctitle = lambda x: None
-
-import openerp
-import openerp.tools.config as config
-from openerp.tools.misc import stripped_sys_argv
-
-_logger = logging.getLogger(__name__)
-
-class Multicorn(object):
- """ Multiprocessing inspired by (g)unicorn.
- Multicorn currently uses accept(2) as dispatching method between workers
- but we plan to replace it by a more intelligent dispatcher to will parse
- the first HTTP request line.
- """
- def __init__(self, app):
- # config
- self.address = (config['xmlrpc_interface'] or '0.0.0.0', config['xmlrpc_port'])
- self.population = config['workers']
- self.timeout = config['limit_time_real']
- self.limit_request = config['limit_request']
- # working vars
- self.beat = 4
- self.app = app
- self.pid = os.getpid()
- self.socket = None
- self.workers_http = {}
- self.workers_cron = {}
- self.workers = {}
- self.generation = 0
- self.queue = []
- self.long_polling_pid = None
-
- def pipe_new(self):
- pipe = os.pipe()
- for fd in pipe:
- # non_blocking
- flags = fcntl.fcntl(fd, fcntl.F_GETFL) | os.O_NONBLOCK
- fcntl.fcntl(fd, fcntl.F_SETFL, flags)
- # close_on_exec
- flags = fcntl.fcntl(fd, fcntl.F_GETFD) | fcntl.FD_CLOEXEC
- fcntl.fcntl(fd, fcntl.F_SETFD, flags)
- return pipe
-
- def pipe_ping(self, pipe):
- try:
- os.write(pipe[1], '.')
- except IOError, e:
- if e.errno not in [errno.EAGAIN, errno.EINTR]:
- raise
-
- def signal_handler(self, sig, frame):
- if len(self.queue) < 5 or sig == signal.SIGCHLD:
- self.queue.append(sig)
- self.pipe_ping(self.pipe)
- else:
- _logger.warn("Dropping signal: %s", sig)
-
- def worker_spawn(self, klass, workers_registry):
- self.generation += 1
- worker = klass(self)
- pid = os.fork()
- if pid != 0:
- worker.pid = pid
- self.workers[pid] = worker
- workers_registry[pid] = worker
- return worker
- else:
- worker.run()
- sys.exit(0)
-
- def long_polling_spawn(self):
- nargs = stripped_sys_argv('--pidfile')
- cmd = nargs[0]
- cmd = os.path.join(os.path.dirname(cmd), "openerp-long-polling")
- nargs[0] = cmd
- popen = subprocess.Popen(nargs)
- self.long_polling_pid = popen.pid
-
- def worker_pop(self, pid):
- if pid in self.workers:
- _logger.debug("Worker (%s) unregistered",pid)
- try:
- self.workers_http.pop(pid,None)
- self.workers_cron.pop(pid,None)
- u = self.workers.pop(pid)
- u.close()
- except OSError:
- return
-
- def worker_kill(self, pid, sig):
- try:
- os.kill(pid, sig)
- except OSError, e:
- if e.errno == errno.ESRCH:
- self.worker_pop(pid)
-
- def process_signals(self):
- while len(self.queue):
- sig = self.queue.pop(0)
- if sig in [signal.SIGINT,signal.SIGTERM]:
- raise KeyboardInterrupt
-
- def process_zombie(self):
- # reap dead workers
- while 1:
- try:
- wpid, status = os.waitpid(-1, os.WNOHANG)
- if not wpid:
- break
- if (status >> 8) == 3:
- msg = "Critial worker error (%s)"
- _logger.critical(msg, wpid)
- raise Exception(msg % wpid)
- self.worker_pop(wpid)
- except OSError, e:
- if e.errno == errno.ECHILD:
- break
- raise
-
- def process_timeout(self):
- now = time.time()
- for (pid, worker) in self.workers.items():
- if (worker.watchdog_timeout is not None) and \
- (now - worker.watchdog_time >= worker.watchdog_timeout):
- _logger.error("Worker (%s) timeout", pid)
- self.worker_kill(pid, signal.SIGKILL)
-
- def process_spawn(self):
- while len(self.workers_http) < self.population:
- self.worker_spawn(WorkerHTTP, self.workers_http)
- while len(self.workers_cron) < config['max_cron_threads']:
- self.worker_spawn(WorkerCron, self.workers_cron)
- if not self.long_polling_pid:
- self.long_polling_spawn()
-
- def sleep(self):
- try:
- # map of fd -> worker
- fds = dict([(w.watchdog_pipe[0],w) for k,w in self.workers.items()])
- fd_in = fds.keys() + [self.pipe[0]]
- # check for ping or internal wakeups
- ready = select.select(fd_in, [], [], self.beat)
- # update worker watchdogs
- for fd in ready[0]:
- if fd in fds:
- fds[fd].watchdog_time = time.time()
- try:
- # empty pipe
- while os.read(fd, 1):
- pass
- except OSError, e:
- if e.errno not in [errno.EAGAIN]:
- raise
- except select.error, e:
- if e[0] not in [errno.EINTR]:
- raise
-
- def start(self):
- # wakeup pipe, python doesnt throw EINTR when a syscall is interrupted
- # by a signal simulating a pseudo SA_RESTART. We write to a pipe in the
- # signal handler to overcome this behaviour
- self.pipe = self.pipe_new()
- # set signal
- signal.signal(signal.SIGINT, self.signal_handler)
- signal.signal(signal.SIGTERM, self.signal_handler)
- signal.signal(signal.SIGCHLD, self.signal_handler)
- # listen to socket
- self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
- self.socket.setblocking(0)
- self.socket.bind(self.address)
- self.socket.listen(8*self.population)
-
- def stop(self, graceful=True):
- if self.long_polling_pid is not None:
- self.worker_kill(self.long_polling_pid, signal.SIGKILL) # FIXME make longpolling process handle SIGTERM correctly
- self.long_polling_pid = None
- if graceful:
- _logger.info("Stopping gracefully")
- limit = time.time() + self.timeout
- for pid in self.workers.keys():
- self.worker_kill(pid, signal.SIGTERM)
- while self.workers and time.time() < limit:
- self.process_zombie()
- time.sleep(0.1)
- else:
- _logger.info("Stopping forcefully")
- for pid in self.workers.keys():
- self.worker_kill(pid, signal.SIGTERM)
- self.socket.close()
- openerp.cli.server.quit_signals_received = 1
-
- def run(self):
- self.start()
- _logger.debug("Multiprocess starting")
- while 1:
- try:
- #_logger.debug("Multiprocess beat (%s)",time.time())
- self.process_signals()
- self.process_zombie()
- self.process_timeout()
- self.process_spawn()
- self.sleep()
- except KeyboardInterrupt:
- _logger.debug("Multiprocess clean stop")
- self.stop()
- break
- except Exception,e:
- _logger.exception(e)
- self.stop(False)
- sys.exit(-1)
-
-class Worker(object):
- """ Workers """
- def __init__(self, multi):
- self.multi = multi
- self.watchdog_time = time.time()
- self.watchdog_pipe = multi.pipe_new()
- # Can be set to None if no watchdog is desired.
- self.watchdog_timeout = multi.timeout
- self.ppid = os.getpid()
- self.pid = None
- self.alive = True
- # should we rename into lifetime ?
- self.request_max = multi.limit_request
- self.request_count = 0
-
- def setproctitle(self, title=""):
- setproctitle('openerp: %s %s %s' % (self.__class__.__name__, self.pid, title))
-
- def close(self):
- os.close(self.watchdog_pipe[0])
- os.close(self.watchdog_pipe[1])
-
- def signal_handler(self, sig, frame):
- self.alive = False
-
- def sleep(self):
- try:
- ret = select.select([self.multi.socket], [], [], self.multi.beat)
- except select.error, e:
- if e[0] not in [errno.EINTR]:
- raise
-
- def process_limit(self):
- # If our parent changed sucide
- if self.ppid != os.getppid():
- _logger.info("Worker (%s) Parent changed", self.pid)
- self.alive = False
- # check for lifetime
- if self.request_count >= self.request_max:
- _logger.info("Worker (%d) max request (%s) reached.", self.pid, self.request_count)
- self.alive = False
- # Reset the worker if it consumes too much memory (e.g. caused by a memory leak).
- rss, vms = psutil.Process(os.getpid()).get_memory_info()
- if vms > config['limit_memory_soft']:
- _logger.info('Worker (%d) virtual memory limit (%s) reached.', self.pid, vms)
- self.alive = False # Commit suicide after the request.
-
- # VMS and RLIMIT_AS are the same thing: virtual memory, a.k.a. address space
- soft, hard = resource.getrlimit(resource.RLIMIT_AS)
- resource.setrlimit(resource.RLIMIT_AS, (config['limit_memory_hard'], hard))
-
- # SIGXCPU (exceeded CPU time) signal handler will raise an exception.
- r = resource.getrusage(resource.RUSAGE_SELF)
- cpu_time = r.ru_utime + r.ru_stime
- def time_expired(n, stack):
- _logger.info('Worker (%d) CPU time limit (%s) reached.', config['limit_time_cpu'])
- # We dont suicide in such case
- raise Exception('CPU time limit exceeded.')
- signal.signal(signal.SIGXCPU, time_expired)
- soft, hard = resource.getrlimit(resource.RLIMIT_CPU)
- resource.setrlimit(resource.RLIMIT_CPU, (cpu_time + config['limit_time_cpu'], hard))
-
- def process_work(self):
- pass
-
- def start(self):
- self.pid = os.getpid()
- self.setproctitle()
- _logger.info("Worker %s (%s) alive", self.__class__.__name__, self.pid)
- # Reseed the random number generator
- random.seed()
- # Prevent fd inherientence close_on_exec
- flags = fcntl.fcntl(self.multi.socket, fcntl.F_GETFD) | fcntl.FD_CLOEXEC
- fcntl.fcntl(self.multi.socket, fcntl.F_SETFD, flags)
- # reset blocking status
- self.multi.socket.setblocking(0)
- signal.signal(signal.SIGINT, self.signal_handler)
- signal.signal(signal.SIGTERM, signal.SIG_DFL)
- signal.signal(signal.SIGCHLD, signal.SIG_DFL)
-
- def stop(self):
- pass
-
- def run(self):
- try:
- self.start()
- while self.alive:
- self.process_limit()
- self.multi.pipe_ping(self.watchdog_pipe)
- self.sleep()
- self.process_work()
- _logger.info("Worker (%s) exiting. request_count: %s.", self.pid, self.request_count)
- self.stop()
- except Exception,e:
- _logger.exception("Worker (%s) Exception occured, exiting..." % self.pid)
- # should we use 3 to abort everything ?
- sys.exit(1)
-
-class WorkerHTTP(Worker):
- """ HTTP Request workers """
- def process_request(self, client, addr):
- client.setblocking(1)
- client.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
- # Prevent fd inherientence close_on_exec
- flags = fcntl.fcntl(client, fcntl.F_GETFD) | fcntl.FD_CLOEXEC
- fcntl.fcntl(client, fcntl.F_SETFD, flags)
- # do request using WorkerBaseWSGIServer monkey patched with socket
- self.server.socket = client
- # tolerate broken pipe when the http client closes the socket before
- # receiving the full reply
- try:
- self.server.process_request(client,addr)
- except IOError, e:
- if e.errno != errno.EPIPE:
- raise
- self.request_count += 1
-
- def process_work(self):
- try:
- client, addr = self.multi.socket.accept()
- self.process_request(client, addr)
- except socket.error, e:
- if e[0] not in (errno.EAGAIN, errno.ECONNABORTED):
- raise
-
- def start(self):
- Worker.start(self)
- self.server = WorkerBaseWSGIServer(self.multi.app)
-
-class WorkerBaseWSGIServer(werkzeug.serving.BaseWSGIServer):
- """ werkzeug WSGI Server patched to allow using an external listen socket
- """
- def __init__(self, app):
- werkzeug.serving.BaseWSGIServer.__init__(self, "1", "1", app)
- def server_bind(self):
- # we dont bind beause we use the listen socket of Multicorn#socket
- # instead we close the socket
- if self.socket:
- self.socket.close()
- def server_activate(self):
- # dont listen as we use Multicorn#socket
- pass
-
-class WorkerCron(Worker):
- """ Cron workers """
-
- def __init__(self, multi):
- super(WorkerCron, self).__init__(multi)
- # process_work() below process a single database per call.
- # The variable db_index is keeping track of the next database to
- # process.
- self.db_index = 0
-
- def sleep(self):
- # Really sleep once all the databases have been processed.
- if self.db_index == 0:
- interval = 60 + self.pid % 10 # chorus effect
- time.sleep(interval)
-
- def _db_list(self):
- if config['db_name']:
- db_names = config['db_name'].split(',')
- else:
- db_names = openerp.service.db.exp_list(True)
- return db_names
-
- def process_work(self):
- rpc_request = logging.getLogger('openerp.netsvc.rpc.request')
- rpc_request_flag = rpc_request.isEnabledFor(logging.DEBUG)
- _logger.debug("WorkerCron (%s) polling for jobs", self.pid)
- db_names = self._db_list()
- if len(db_names):
- self.db_index = (self.db_index + 1) % len(db_names)
- db_name = db_names[self.db_index]
- self.setproctitle(db_name)
- if rpc_request_flag:
- start_time = time.time()
- start_rss, start_vms = psutil.Process(os.getpid()).get_memory_info()
-
- import openerp.addons.base as base
- base.ir.ir_cron.ir_cron._acquire_job(db_name)
- openerp.modules.registry.RegistryManager.delete(db_name)
-
- # dont keep cursors in multi database mode
- if len(db_names) > 1:
- openerp.sql_db.close_db(db_name)
- if rpc_request_flag:
- end_time = time.time()
- end_rss, end_vms = psutil.Process(os.getpid()).get_memory_info()
- logline = '%s time:%.3fs mem: %sk -> %sk (diff: %sk)' % (db_name, end_time - start_time, start_vms / 1024, end_vms / 1024, (end_vms - start_vms)/1024)
- _logger.debug("WorkerCron (%s) %s", self.pid, logline)
-
- self.request_count += 1
- if self.request_count >= self.request_max and self.request_max < len(db_names):
- _logger.error("There are more dabatases to process than allowed "
- "by the `limit_request` configuration variable: %s more.",
- len(db_names) - self.request_max)
- else:
- self.db_index = 0
-
- def start(self):
- os.nice(10) # mommy always told me to be nice with others...
- Worker.start(self)
- self.multi.socket.close()
- openerp.service.start_internal()
-
- # chorus effect: make cron workers do not all start at first database
- mct = config['max_cron_threads']
- p = float(self.pid % mct) / mct
- self.db_index = int(len(self._db_list()) * p)
-
-
-# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/openerp/service/wsgi_server.py b/openerp/service/wsgi_server.py
index acbfc67dd0b..506a07d0295 100644
--- a/openerp/service/wsgi_server.py
+++ b/openerp/service/wsgi_server.py
@@ -57,13 +57,7 @@ RPC_FAULT_CODE_WARNING = 2
RPC_FAULT_CODE_ACCESS_DENIED = 3
RPC_FAULT_CODE_ACCESS_ERROR = 4
-# The new (6.1) versioned RPC paths.
-XML_RPC_PATH = '/openerp/xmlrpc'
-XML_RPC_PATH_1 = '/openerp/xmlrpc/1'
-JSON_RPC_PATH = '/openerp/jsonrpc'
-JSON_RPC_PATH_1 = '/openerp/jsonrpc/1'
-
-def xmlrpc_return(start_response, service, method, params, legacy_exceptions=False):
+def xmlrpc_return(start_response, service, method, params, string_faultcode=False):
"""
Helper to call a service's method with some params, using a wsgi-supplied
``start_response`` callback.
@@ -81,14 +75,14 @@ def xmlrpc_return(start_response, service, method, params, legacy_exceptions=Fal
result = openerp.netsvc.dispatch_rpc(service, method, params)
response = xmlrpclib.dumps((result,), methodresponse=1, allow_none=False, encoding=None)
except Exception, e:
- if legacy_exceptions:
- response = xmlrpc_handle_exception_legacy(e)
+ if string_faultcode:
+ response = xmlrpc_handle_exception_string(e)
else:
- response = xmlrpc_handle_exception(e)
+ response = xmlrpc_handle_exception_int(e)
start_response("200 OK", [('Content-Type','text/xml'), ('Content-Length', str(len(response)))])
return [response]
-def xmlrpc_handle_exception(e):
+def xmlrpc_handle_exception_int(e):
if isinstance(e, openerp.osv.orm.except_orm): # legacy
fault = xmlrpclib.Fault(RPC_FAULT_CODE_WARNING, openerp.tools.ustr(e.value))
response = xmlrpclib.dumps(fault, allow_none=False, encoding=None)
@@ -121,7 +115,7 @@ def xmlrpc_handle_exception(e):
response = xmlrpclib.dumps(fault, allow_none=None, encoding=None)
return response
-def xmlrpc_handle_exception_legacy(e):
+def xmlrpc_handle_exception_string(e):
if isinstance(e, openerp.osv.orm.except_orm):
fault = xmlrpclib.Fault('warning -- ' + e.name + '\n\n' + e.value, '')
response = xmlrpclib.dumps(fault, allow_none=False, encoding=None)
@@ -146,82 +140,29 @@ def xmlrpc_handle_exception_legacy(e):
response = xmlrpclib.dumps(fault, allow_none=None, encoding=None)
return response
-def wsgi_xmlrpc_1(environ, start_response):
- """ The main OpenERP WSGI handler."""
- if environ['REQUEST_METHOD'] == 'POST' and environ['PATH_INFO'].startswith(XML_RPC_PATH_1):
- length = int(environ['CONTENT_LENGTH'])
- data = environ['wsgi.input'].read(length)
-
- params, method = xmlrpclib.loads(data)
-
- path = environ['PATH_INFO'][len(XML_RPC_PATH_1):]
- if path.startswith('/'): path = path[1:]
- if path.endswith('/'): path = path[:-1]
- path = path.split('/')
-
- # All routes are hard-coded.
-
- # No need for a db segment.
- if len(path) == 1:
- service = path[0]
-
- if service == 'common':
- if method in ('server_version',):
- service = 'db'
- return xmlrpc_return(start_response, service, method, params)
-
- # A db segment must be given.
- elif len(path) == 2:
- service, db_name = path
- params = (db_name,) + params
-
- return xmlrpc_return(start_response, service, method, params)
-
- # A db segment and a model segment must be given.
- elif len(path) == 3 and path[0] == 'model':
- service, db_name, model_name = path
- params = (db_name,) + params[:2] + (model_name,) + params[2:]
- service = 'object'
- return xmlrpc_return(start_response, service, method, params)
-
- # The body has been read, need to raise an exception (not return None).
- fault = xmlrpclib.Fault(RPC_FAULT_CODE_CLIENT_ERROR, '')
- response = xmlrpclib.dumps(fault, allow_none=None, encoding=None)
- start_response("200 OK", [('Content-Type','text/xml'), ('Content-Length', str(len(response)))])
- return [response]
-
def wsgi_xmlrpc(environ, start_response):
- """ WSGI handler to return the versions."""
- if environ['REQUEST_METHOD'] == 'POST' and environ['PATH_INFO'].startswith(XML_RPC_PATH):
- length = int(environ['CONTENT_LENGTH'])
- data = environ['wsgi.input'].read(length)
+ """ Two routes are available for XML-RPC
- params, method = xmlrpclib.loads(data)
+ /xmlrpc/ route returns faultCode as strings. This is a historic
+ violation of the protocol kept for compatibility.
- path = environ['PATH_INFO'][len(XML_RPC_PATH):]
- if path.startswith('/'): path = path[1:]
- if path.endswith('/'): path = path[:-1]
- path = path.split('/')
-
- # All routes are hard-coded.
-
- if len(path) == 1 and path[0] == '' and method in ('version',):
- return xmlrpc_return(start_response, 'common', method, ())
-
- # The body has been read, need to raise an exception (not return None).
- fault = xmlrpclib.Fault(RPC_FAULT_CODE_CLIENT_ERROR, '')
- response = xmlrpclib.dumps(fault, allow_none=None, encoding=None)
- start_response("200 OK", [('Content-Type','text/xml'), ('Content-Length', str(len(response)))])
- return [response]
-
-def wsgi_xmlrpc_legacy(environ, start_response):
+ /xmlrpc/2/ is a new route that returns faultCode as int and is
+ therefore fully compliant.
+ """
if environ['REQUEST_METHOD'] == 'POST' and environ['PATH_INFO'].startswith('/xmlrpc/'):
length = int(environ['CONTENT_LENGTH'])
data = environ['wsgi.input'].read(length)
- path = environ['PATH_INFO'][len('/xmlrpc/'):] # expected to be one of db, object, ...
+
+ # Distinguish betweed the 2 faultCode modes
+ string_faultcode = True
+ if environ['PATH_INFO'].startswith('/xmlrpc/2/'):
+ service = environ['PATH_INFO'][len('/xmlrpc/2/'):]
+ string_faultcode = False
+ else:
+ service = environ['PATH_INFO'][len('/xmlrpc/'):]
params, method = xmlrpclib.loads(data)
- return xmlrpc_return(start_response, path, method, params, True)
+ return xmlrpc_return(start_response, service, method, params, string_faultcode)
def wsgi_webdav(environ, start_response):
pi = environ['PATH_INFO']
@@ -400,10 +341,8 @@ def application_unproxied(environ, start_response):
if hasattr(threading.current_thread(), 'dbname'):
del threading.current_thread().dbname
- openerp.service.start_internal()
-
# Try all handlers until one returns some result (i.e. not None).
- wsgi_handlers = [wsgi_xmlrpc_1, wsgi_xmlrpc, wsgi_xmlrpc_legacy, wsgi_webdav]
+ wsgi_handlers = [wsgi_xmlrpc, wsgi_webdav]
wsgi_handlers += module_handlers
for handler in wsgi_handlers:
result = handler(environ, start_response)
@@ -422,69 +361,5 @@ def application(environ, start_response):
else:
return application_unproxied(environ, start_response)
-# The WSGI server, started by start_server(), stopped by stop_server().
-httpd = None
-
-def serve(interface, port, threaded):
- """ Serve HTTP requests via werkzeug development server.
-
- Calling this function is blocking, you might want to call it in its own
- thread.
- """
-
- global httpd
- if not openerp.evented:
- httpd = werkzeug.serving.make_server(interface, port, application, threaded=threaded)
- else:
- from gevent.wsgi import WSGIServer
- httpd = WSGIServer((interface, port), application)
- httpd.serve_forever()
-
-def start_service():
- """ Call serve() in its own thread.
-
- The WSGI server can be shutdown with stop_server() below.
- """
- # TODO Change the xmlrpc_* options to http_*
- interface = config['xmlrpc_interface'] or '0.0.0.0'
- port = config['xmlrpc_port']
- _logger.info('HTTP service (werkzeug) running on %s:%s', interface, port)
- if not openerp.evented:
- threading.Thread(target=serve, args=(interface, port, True)).start()
- else:
- serve(interface, port, True)
-
-def stop_service():
- """ Initiate the shutdown of the WSGI server.
-
- The server is supposed to have been started by start_server() above.
- """
- if httpd:
- if not openerp.evented:
- httpd.shutdown()
- close_socket(httpd.socket)
- else:
- import gevent
- httpd.stop()
- gevent.shutdown()
-
-def close_socket(sock):
- """ Closes a socket instance cleanly
-
- :param sock: the network socket to close
- :type sock: socket.socket
- """
- try:
- sock.shutdown(socket.SHUT_RDWR)
- except socket.error, e:
- # On OSX, socket shutdowns both sides if any side closes it
- # causing an error 57 'Socket is not connected' on shutdown
- # of the other side (or something), see
- # http://bugs.python.org/issue4397
- # note: stdlib fixed test, not behavior
- if e.errno != errno.ENOTCONN or platform.system() not in ['Darwin', 'Windows']:
- raise
- sock.close()
-
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/openerp/tests/common.py b/openerp/tests/common.py
index f8472e81fcf..df00e61f140 100644
--- a/openerp/tests/common.py
+++ b/openerp/tests/common.py
@@ -135,11 +135,11 @@ class RpcCase(unittest2.TestCase):
self.proxy.object_60 = xmlrpclib.ServerProxy(url_60 + 'object')
#self.proxy.edi_60 = xmlrpclib.ServerProxy(url_60 + 'edi')
- # Use the new (6.1) API.
- self.proxy.url_61 = url_61 = 'http://%s:%d/openerp/xmlrpc/1/' % (HOST, PORT)
- self.proxy.common_61 = xmlrpclib.ServerProxy(url_61 + 'common')
- self.proxy.db_61 = xmlrpclib.ServerProxy(url_61 + 'db')
- self.proxy.model_61 = xmlrpclib.ServerProxy(url_61 + 'model/' + DB)
+ # Use the new (8) API.
+ self.proxy.url_8 = url_8 = 'http://%s:%d/xmlrpc/2/' % (HOST, PORT)
+ self.proxy.common_8 = xmlrpclib.ServerProxy(url_8 + 'common')
+ self.proxy.db_8 = xmlrpclib.ServerProxy(url_8 + 'db')
+ self.proxy.object_8 = xmlrpclib.ServerProxy(url_8 + 'object')
@classmethod
def generate_database_name(cls):
diff --git a/openerp/tests/test_xmlrpc.py b/openerp/tests/test_xmlrpc.py
index 8c9d6911e7b..12bc828cb29 100644
--- a/openerp/tests/test_xmlrpc.py
+++ b/openerp/tests/test_xmlrpc.py
@@ -47,21 +47,18 @@ class test_xmlrpc(common.RpcCase):
def test_xmlrpc_ir_model_search(self):
""" Try a search on the object service. """
- ids = self.proxy.object_60.execute(DB, ADMIN_USER_ID, ADMIN_PASSWORD,
- 'ir.model', 'search', [])
+ o = self.proxy.object_60
+ ids = o.execute(DB, ADMIN_USER_ID, ADMIN_PASSWORD, 'ir.model', 'search', [])
assert ids
- ids = self.proxy.object_60.execute(DB, ADMIN_USER_ID, ADMIN_PASSWORD,
- 'ir.model', 'search', [], {})
+ ids = o.execute(DB, ADMIN_USER_ID, ADMIN_PASSWORD, 'ir.model', 'search', [], {})
assert ids
- def test_xmlrpc_61_ir_model_search(self):
+ def test_xmlrpc_8_ir_model_search(self):
""" Try a search on the object service. """
-
- proxy = xmlrpclib.ServerProxy(self.proxy.url_61 + 'model/' + DB +
- '/ir.model')
- ids = proxy.execute(ADMIN_USER_ID, ADMIN_PASSWORD, 'search', [])
+ o = self.proxy.object_8
+ ids = o.execute(DB, ADMIN_USER_ID, ADMIN_PASSWORD, 'ir.model', 'search', [])
assert ids
- ids = proxy.execute(ADMIN_USER_ID, ADMIN_PASSWORD, 'search', [], {})
+ ids = o.execute(DB, ADMIN_USER_ID, ADMIN_PASSWORD, 'ir.model', 'search', [], {})
assert ids
# This test was written to test the creation of a new RPC endpoint, not
diff --git a/openerp/tools/config.py b/openerp/tools/config.py
index cc3c187c775..ebfd8cea20d 100644
--- a/openerp/tools/config.py
+++ b/openerp/tools/config.py
@@ -106,7 +106,6 @@ class configmanager(object):
help="specify additional addons paths (separated by commas).",
action="callback", callback=self._check_addons_path, nargs=1, type="string")
group.add_option("--load", dest="server_wide_modules", help="Comma-separated list of server-wide modules default=web")
- group.add_option("--gevent", dest="gevent", action="store_true", my_default=False, help="Activate the GEvent mode, this also desactivate the cron.")
parser.add_option_group(group)
# XML-RPC / HTTP
@@ -246,6 +245,7 @@ class configmanager(object):
# Advanced options
group = optparse.OptionGroup(parser, "Advanced options")
+ group.add_option('--auto-reload', dest='auto_reload', action='store_true', my_default=False, help='enable auto reload')
group.add_option('--debug', dest='debug_mode', action='store_true', my_default=False, help='enable debug mode')
group.add_option("--stop-after-init", action="store_true", dest="stop_after_init", my_default=False,
help="stop the server after its initialization")
@@ -399,7 +399,7 @@ class configmanager(object):
'list_db', 'xmlrpcs', 'proxy_mode',
'test_file', 'test_enable', 'test_commit', 'test_report_directory',
'osv_memory_count_limit', 'osv_memory_age_limit', 'max_cron_threads', 'unaccent',
- 'workers', 'limit_memory_hard', 'limit_memory_soft', 'limit_time_cpu', 'limit_time_real', 'limit_request', 'gevent'
+ 'workers', 'limit_memory_hard', 'limit_memory_soft', 'limit_time_cpu', 'limit_time_real', 'limit_request', 'auto_reload'
]
for arg in keys:
diff --git a/openerpcommand/web.py b/openerpcommand/web.py
index 5875978ad32..5d9d429244b 100644
--- a/openerpcommand/web.py
+++ b/openerpcommand/web.py
@@ -30,9 +30,9 @@ def run(args):
import gevent
import gevent.monkey
import gevent.wsgi
- import gevent_psycopg2
+ import psycogreen.gevent
gevent.monkey.patch_all()
- gevent_psycopg2.monkey_patch()
+ psycogreen.gevent.patch_psycopg()
import threading
import openerp
import openerp.cli.server
diff --git a/setup.py b/setup.py
index ff801a97933..4f4ffe6e505 100644
--- a/setup.py
+++ b/setup.py
@@ -120,7 +120,7 @@ setuptools.setup(
'feedparser',
'gdata',
'gevent',
- 'gevent-psycopg2',
+ 'psycogreen',
'Jinja2',
'lxml', # windows binary http://www.lfd.uci.edu/~gohlke/pythonlibs/
'mako',