1 # -*- coding: utf-8 -*-
2 from contextlib import closing
3 from functools import wraps
15 from openerp import SUPERUSER_ID
16 import openerp.release
22 _logger = logging.getLogger(__name__)
26 self_id_protect = threading.Semaphore()
28 # This should be moved to openerp.modules.db, along side initialize().
29 def _initialize_db(id, db_name, demo, lang, user_password):
31 self_actions[id]['progress'] = 0
32 db = openerp.sql_db.db_connect(db_name)
33 with closing(db.cursor()) as cr:
34 # TODO this should be removed as it is done by RegistryManager.new().
35 openerp.modules.db.initialize(cr)
36 openerp.tools.config['lang'] = lang
39 registry = openerp.modules.registry.RegistryManager.new(
40 db_name, demo, self_actions[id], update_module=True)
42 with closing(db.cursor()) as cr:
44 modobj = registry['ir.module.module']
45 mids = modobj.search(cr, SUPERUSER_ID, [('state', '=', 'installed')])
46 modobj.update_translations(cr, SUPERUSER_ID, mids, lang)
48 # update admin's password and lang
49 values = {'password': user_password, 'lang': lang}
50 registry['res.users'].write(cr, SUPERUSER_ID, [SUPERUSER_ID], values)
52 cr.execute('SELECT login, password FROM res_users ORDER BY login')
53 self_actions[id].update(users=cr.dictfetchall(), clean=True)
57 self_actions[id].update(clean=False, exception=e)
58 _logger.exception('CREATE DATABASE failed:')
59 self_actions[id]['traceback'] = traceback.format_exc()
61 def dispatch(method, params):
62 if method in ['create', 'get_progress', 'drop', 'dump', 'restore', 'rename',
63 'change_admin_password', 'migrate_databases',
64 'create_database', 'duplicate_database']:
67 security.check_super(passwd)
68 elif method in ['db_exist', 'list', 'list_lang', 'server_version']:
70 # No security check for these methods
73 raise KeyError("Method not found: %s" % method)
74 fn = globals()['exp_' + method]
77 def _create_empty_database(name):
78 db = openerp.sql_db.db_connect('postgres')
79 with closing(db.cursor()) as cr:
80 chosen_template = openerp.tools.config['db_template']
81 cr.execute("SELECT datname FROM pg_database WHERE datname = %s",
84 raise openerp.exceptions.Warning("database %r already exists!" % (name,))
86 cr.autocommit(True) # avoid transaction block
87 cr.execute("""CREATE DATABASE "%s" ENCODING 'unicode' TEMPLATE "%s" """ % (name, chosen_template))
89 def exp_create(db_name, demo, lang, user_password='admin'):
90 self_id_protect.acquire()
94 self_id_protect.release()
96 self_actions[id] = {'clean': False}
98 _create_empty_database(db_name)
100 _logger.info('CREATE DATABASE %s', db_name.lower())
101 create_thread = threading.Thread(target=_initialize_db,
102 args=(id, db_name, demo, lang, user_password))
103 create_thread.start()
104 self_actions[id]['thread'] = create_thread
107 def exp_create_database(db_name, demo, lang, user_password='admin'):
108 """ Similar to exp_create but blocking."""
109 self_id_protect.acquire()
113 self_id_protect.release()
115 self_actions[id] = {'clean': False}
117 _logger.info('Create database `%s`.', db_name)
118 _create_empty_database(db_name)
119 _initialize_db(id, db_name, demo, lang, user_password)
122 def exp_duplicate_database(db_original_name, db_name):
123 _logger.info('Duplicate database `%s` to `%s`.', db_original_name, db_name)
124 openerp.sql_db.close_db(db_original_name)
125 db = openerp.sql_db.db_connect('postgres')
126 with closing(db.cursor()) as cr:
127 cr.autocommit(True) # avoid transaction block
128 cr.execute("""CREATE DATABASE "%s" ENCODING 'unicode' TEMPLATE "%s" """ % (db_name, db_original_name))
130 from_fs = openerp.tools.config.filestore(db_original_name)
131 to_fs = openerp.tools.config.filestore(db_name)
132 if os.path.exists(from_fs) and not os.path.exists(to_fs):
133 shutil.copy(from_fs, to_fs)
136 def exp_get_progress(id):
137 if self_actions[id]['thread'].isAlive():
138 # return openerp.modules.init_progress[db_name]
139 return min(self_actions[id].get('progress', 0), 0.95), []
141 clean = self_actions[id]['clean']
143 users = self_actions[id]['users']
145 # Remove the None passwords as they can't be marshalled by XML-RPC.
146 if user['password'] is None:
147 user['password'] = ''
151 a = self_actions.pop(id)
152 exc, tb = a['exception'], a['traceback']
153 raise Exception, exc, tb
155 def exp_drop(db_name):
156 if db_name not in exp_list(True):
158 openerp.modules.registry.RegistryManager.delete(db_name)
159 openerp.sql_db.close_db(db_name)
161 db = openerp.sql_db.db_connect('postgres')
162 with closing(db.cursor()) as cr:
163 cr.autocommit(True) # avoid transaction block
164 # Try to terminate all other connections that might prevent
165 # dropping the database
167 # PostgreSQL 9.2 renamed pg_stat_activity.procpid to pid:
168 # http://www.postgresql.org/docs/9.2/static/release-9-2.html#AEN110389
169 pid_col = 'pid' if cr._cnx.server_version >= 90200 else 'procpid'
171 cr.execute("""SELECT pg_terminate_backend(%(pid_col)s)
172 FROM pg_stat_activity
173 WHERE datname = %%s AND
174 %(pid_col)s != pg_backend_pid()""" % {'pid_col': pid_col},
180 cr.execute('DROP DATABASE "%s"' % db_name)
182 _logger.error('DROP DB: %s failed:\n%s', db_name, e)
183 raise Exception("Couldn't drop database %s: %s" % (db_name, e))
185 _logger.info('DROP DB: %s', db_name)
187 fs = openerp.tools.config.filestore(db_name)
188 if os.path.exists(fs):
192 def _set_pg_password_in_environment(func):
193 """ On systems where pg_restore/pg_dump require an explicit
194 password (i.e. when not connecting via unix sockets, and most
195 importantly on Windows), it is necessary to pass the PG user
196 password in the environment or in a special .pgpass file.
198 This decorator handles setting
199 :envvar:`PGPASSWORD` if it is not already
200 set, and removing it afterwards.
202 See also http://www.postgresql.org/docs/8.4/static/libpq-envars.html
204 .. note:: This is not thread-safe, and should never be enabled for
205 SaaS (giving SaaS users the super-admin password is not a good idea
209 def wrapper(*args, **kwargs):
210 if os.environ.get('PGPASSWORD') or not openerp.tools.config['db_password']:
211 return func(*args, **kwargs)
213 os.environ['PGPASSWORD'] = openerp.tools.config['db_password']
215 return func(*args, **kwargs)
217 del os.environ['PGPASSWORD']
220 def exp_dump(db_name):
221 with tempfile.TemporaryFile() as t:
224 return t.read().encode('base64')
226 @_set_pg_password_in_environment
227 def dump_db(db, stream):
228 """Dump database `db` into file-like object `stream`"""
229 with openerp.tools.osutil.tempdir() as dump_dir:
230 registry = openerp.modules.registry.RegistryManager.get(db)
231 with registry.cursor() as cr:
232 filestore = registry['ir.attachment']._filestore(cr, SUPERUSER_ID)
233 if os.path.exists(filestore):
234 shutil.copytree(filestore, os.path.join(dump_dir, 'filestore'))
236 dump_file = os.path.join(dump_dir, 'dump.sql')
237 cmd = ['pg_dump', '--format=p', '--no-owner', '--file=' + dump_file]
238 if openerp.tools.config['db_user']:
239 cmd.append('--username=' + openerp.tools.config['db_user'])
240 if openerp.tools.config['db_host']:
241 cmd.append('--host=' + openerp.tools.config['db_host'])
242 if openerp.tools.config['db_port']:
243 cmd.append('--port=' + str(openerp.tools.config['db_port']))
246 if openerp.tools.exec_pg_command(*cmd):
247 _logger.error('DUMP DB: %s failed! Please verify the configuration of the database '
248 'password on the server. You may need to create a .pgpass file for '
249 'authentication, or specify `db_password` in the server configuration '
251 raise Exception("Couldn't dump database")
253 openerp.tools.osutil.zip_dir(dump_dir, stream, include_dir=False)
255 _logger.info('DUMP DB successful: %s', db)
257 def exp_restore(db_name, data, copy=False):
258 data_file = tempfile.NamedTemporaryFile(delete=False)
260 data_file.write(data.decode('base64'))
262 restore_db(db_name, data_file.name, copy=copy)
264 os.unlink(data_file.name)
267 @_set_pg_password_in_environment
268 def restore_db(db, dump_file, copy=False):
269 assert isinstance(db, basestring)
271 _logger.warning('RESTORE DB: %s already exists', db)
272 raise Exception("Database already exists")
274 _create_empty_database(db)
276 filestore_path = None
277 with openerp.tools.osutil.tempdir() as dump_dir:
278 if zipfile.is_zipfile(dump_file):
280 with zipfile.ZipFile(dump_file, 'r') as z:
281 # only extract known members!
282 filestore = [m for m in z.namelist() if m.startswith('filestore/')]
283 z.extractall(dump_dir, ['dump.sql'] + filestore)
286 filestore_path = os.path.join(dump_dir, 'filestore')
289 pg_args = ['-q', '-f', os.path.join(dump_dir, 'dump.sql')]
292 # <= 7.0 format (raw pg_dump output)
293 pg_cmd = 'pg_restore'
294 pg_args = ['--no-owner', dump_file]
297 if openerp.tools.config['db_user']:
298 args.append('--username=' + openerp.tools.config['db_user'])
299 if openerp.tools.config['db_host']:
300 args.append('--host=' + openerp.tools.config['db_host'])
301 if openerp.tools.config['db_port']:
302 args.append('--port=' + str(openerp.tools.config['db_port']))
303 args.append('--dbname=' + db)
304 pg_args = args + pg_args
306 if openerp.tools.exec_pg_command(pg_cmd, *pg_args):
307 raise Exception("Couldn't restore database")
309 registry = openerp.modules.registry.RegistryManager.new(db)
310 with registry.cursor() as cr:
312 # if it's a copy of a database, force generation of a new dbuuid
313 registry['ir.config_parameter'].init(cr, force=True)
315 filestore_dest = registry['ir.attachment']._filestore(cr, SUPERUSER_ID)
316 shutil.move(filestore_path, filestore_dest)
318 if openerp.tools.config['unaccent']:
321 cr.execute("CREATE EXTENSION unaccent")
322 except psycopg2.Error:
325 _logger.info('RESTORE DB: %s', db)
327 def exp_rename(old_name, new_name):
328 openerp.modules.registry.RegistryManager.delete(old_name)
329 openerp.sql_db.close_db(old_name)
331 db = openerp.sql_db.db_connect('postgres')
332 with closing(db.cursor()) as cr:
333 cr.autocommit(True) # avoid transaction block
335 cr.execute('ALTER DATABASE "%s" RENAME TO "%s"' % (old_name, new_name))
336 _logger.info('RENAME DB: %s -> %s', old_name, new_name)
338 _logger.error('RENAME DB: %s -> %s failed:\n%s', old_name, new_name, e)
339 raise Exception("Couldn't rename database %s to %s: %s" % (old_name, new_name, e))
341 old_fs = openerp.tools.config.filestore(old_name)
342 new_fs = openerp.tools.config.filestore(new_name)
343 if os.path.exists(old_fs) and not os.path.exists(new_fs):
344 shutil.move(old_fs, new_fs)
347 @openerp.tools.mute_logger('openerp.sql_db')
348 def exp_db_exist(db_name):
349 ## Not True: in fact, check if connection to database is possible. The database may exists
350 return bool(openerp.sql_db.db_connect(db_name))
352 def exp_list(document=False):
353 if not openerp.tools.config['list_db'] and not document:
354 raise openerp.exceptions.AccessDenied()
355 chosen_template = openerp.tools.config['db_template']
356 templates_list = tuple(set(['template0', 'template1', 'postgres', chosen_template]))
357 db = openerp.sql_db.db_connect('postgres')
358 with closing(db.cursor()) as cr:
360 db_user = openerp.tools.config["db_user"]
361 if not db_user and os.name == 'posix':
363 db_user = pwd.getpwuid(os.getuid())[0]
365 cr.execute("select usename from pg_user where usesysid=(select datdba from pg_database where datname=%s)", (openerp.tools.config["db_name"],))
367 db_user = res and str(res[0])
369 cr.execute("select datname from pg_database where datdba=(select usesysid from pg_user where usename=%s) and datname not in %s order by datname", (db_user, templates_list))
371 cr.execute("select datname from pg_database where datname not in %s order by datname", (templates_list,))
372 res = [openerp.tools.ustr(name) for (name,) in cr.fetchall()]
378 def exp_change_admin_password(new_password):
379 openerp.tools.config['admin_passwd'] = new_password
380 openerp.tools.config.save()
384 return openerp.tools.scan_languages()
386 def exp_server_version():
387 """ Return the version of the server
388 Used by the client to verify the compatibility with its own version
390 return openerp.release.version
392 def exp_migrate_databases(databases):
394 _logger.info('migrate database %s', db)
395 openerp.tools.config['update']['base'] = True
396 openerp.modules.registry.RegistryManager.new(db, force_demo=False, update_module=True)
399 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: