PageRenderTime 52ms CodeModel.GetById 22ms RepoModel.GetById 1ms app.codeStats 0ms

/openerp/service/db.py

https://github.com/sandeepksrana/odoo
Python | 409 lines | 372 code | 26 blank | 11 comment | 36 complexity | 8105c8d4575454d04a616ef7dc85be0b MD5 | raw file
Possible License(s): WTFPL, Apache-2.0, BSD-3-Clause, GPL-3.0, AGPL-3.0, LGPL-2.1
  1. # -*- coding: utf-8 -*-
  2. from contextlib import closing
  3. from functools import wraps
  4. import logging
  5. import os
  6. import shutil
  7. import threading
  8. import traceback
  9. import tempfile
  10. import zipfile
  11. import psycopg2
  12. import openerp
  13. from openerp import SUPERUSER_ID
  14. from openerp.exceptions import Warning
  15. import openerp.release
  16. import openerp.sql_db
  17. import openerp.tools
  18. import security
  19. _logger = logging.getLogger(__name__)
  20. self_actions = {}
  21. self_id = 0
  22. self_id_protect = threading.Semaphore()
  23. class DatabaseExists(Warning):
  24. pass
  25. # This should be moved to openerp.modules.db, along side initialize().
  26. def _initialize_db(id, db_name, demo, lang, user_password):
  27. try:
  28. self_actions[id]['progress'] = 0
  29. db = openerp.sql_db.db_connect(db_name)
  30. with closing(db.cursor()) as cr:
  31. # TODO this should be removed as it is done by RegistryManager.new().
  32. openerp.modules.db.initialize(cr)
  33. openerp.tools.config['lang'] = lang
  34. cr.commit()
  35. registry = openerp.modules.registry.RegistryManager.new(
  36. db_name, demo, self_actions[id], update_module=True)
  37. with closing(db.cursor()) as cr:
  38. if lang:
  39. modobj = registry['ir.module.module']
  40. mids = modobj.search(cr, SUPERUSER_ID, [('state', '=', 'installed')])
  41. modobj.update_translations(cr, SUPERUSER_ID, mids, lang)
  42. # update admin's password and lang
  43. values = {'password': user_password, 'lang': lang}
  44. registry['res.users'].write(cr, SUPERUSER_ID, [SUPERUSER_ID], values)
  45. cr.execute('SELECT login, password FROM res_users ORDER BY login')
  46. self_actions[id].update(users=cr.dictfetchall(), clean=True)
  47. cr.commit()
  48. except Exception, e:
  49. self_actions[id].update(clean=False, exception=e)
  50. _logger.exception('CREATE DATABASE failed:')
  51. self_actions[id]['traceback'] = traceback.format_exc()
  52. def dispatch(method, params):
  53. if method in ['create', 'get_progress', 'drop', 'dump', 'restore', 'rename',
  54. 'change_admin_password', 'migrate_databases',
  55. 'create_database', 'duplicate_database']:
  56. passwd = params[0]
  57. params = params[1:]
  58. security.check_super(passwd)
  59. elif method in ['db_exist', 'list', 'list_lang', 'server_version']:
  60. # params = params
  61. # No security check for these methods
  62. pass
  63. else:
  64. raise KeyError("Method not found: %s" % method)
  65. fn = globals()['exp_' + method]
  66. return fn(*params)
  67. def _create_empty_database(name):
  68. db = openerp.sql_db.db_connect('postgres')
  69. with closing(db.cursor()) as cr:
  70. chosen_template = openerp.tools.config['db_template']
  71. cr.execute("SELECT datname FROM pg_database WHERE datname = %s",
  72. (name,))
  73. if cr.fetchall():
  74. raise DatabaseExists("database %r already exists!" % (name,))
  75. else:
  76. cr.autocommit(True) # avoid transaction block
  77. cr.execute("""CREATE DATABASE "%s" ENCODING 'unicode' TEMPLATE "%s" """ % (name, chosen_template))
  78. def exp_create(db_name, demo, lang, user_password='admin'):
  79. self_id_protect.acquire()
  80. global self_id
  81. self_id += 1
  82. id = self_id
  83. self_id_protect.release()
  84. self_actions[id] = {'clean': False}
  85. _create_empty_database(db_name)
  86. _logger.info('CREATE DATABASE %s', db_name.lower())
  87. create_thread = threading.Thread(target=_initialize_db,
  88. args=(id, db_name, demo, lang, user_password))
  89. create_thread.start()
  90. self_actions[id]['thread'] = create_thread
  91. return id
  92. def exp_create_database(db_name, demo, lang, user_password='admin'):
  93. """ Similar to exp_create but blocking."""
  94. self_id_protect.acquire()
  95. global self_id
  96. self_id += 1
  97. id = self_id
  98. self_id_protect.release()
  99. self_actions[id] = {'clean': False}
  100. _logger.info('Create database `%s`.', db_name)
  101. _create_empty_database(db_name)
  102. _initialize_db(id, db_name, demo, lang, user_password)
  103. return True
  104. def exp_duplicate_database(db_original_name, db_name):
  105. _logger.info('Duplicate database `%s` to `%s`.', db_original_name, db_name)
  106. openerp.sql_db.close_db(db_original_name)
  107. db = openerp.sql_db.db_connect('postgres')
  108. with closing(db.cursor()) as cr:
  109. cr.autocommit(True) # avoid transaction block
  110. cr.execute("""CREATE DATABASE "%s" ENCODING 'unicode' TEMPLATE "%s" """ % (db_name, db_original_name))
  111. from_fs = openerp.tools.config.filestore(db_original_name)
  112. to_fs = openerp.tools.config.filestore(db_name)
  113. if os.path.exists(from_fs) and not os.path.exists(to_fs):
  114. shutil.copytree(from_fs, to_fs)
  115. return True
  116. def exp_get_progress(id):
  117. if self_actions[id]['thread'].isAlive():
  118. # return openerp.modules.init_progress[db_name]
  119. return min(self_actions[id].get('progress', 0), 0.95), []
  120. else:
  121. clean = self_actions[id]['clean']
  122. if clean:
  123. users = self_actions[id]['users']
  124. for user in users:
  125. # Remove the None passwords as they can't be marshalled by XML-RPC.
  126. if user['password'] is None:
  127. user['password'] = ''
  128. self_actions.pop(id)
  129. return 1.0, users
  130. else:
  131. a = self_actions.pop(id)
  132. exc, tb = a['exception'], a['traceback']
  133. raise Exception, exc, tb
  134. def _drop_conn(cr, db_name):
  135. # Try to terminate all other connections that might prevent
  136. # dropping the database
  137. try:
  138. # PostgreSQL 9.2 renamed pg_stat_activity.procpid to pid:
  139. # http://www.postgresql.org/docs/9.2/static/release-9-2.html#AEN110389
  140. pid_col = 'pid' if cr._cnx.server_version >= 90200 else 'procpid'
  141. cr.execute("""SELECT pg_terminate_backend(%(pid_col)s)
  142. FROM pg_stat_activity
  143. WHERE datname = %%s AND
  144. %(pid_col)s != pg_backend_pid()""" % {'pid_col': pid_col},
  145. (db_name,))
  146. except Exception:
  147. pass
  148. def exp_drop(db_name):
  149. if db_name not in exp_list(True):
  150. return False
  151. openerp.modules.registry.RegistryManager.delete(db_name)
  152. openerp.sql_db.close_db(db_name)
  153. db = openerp.sql_db.db_connect('postgres')
  154. with closing(db.cursor()) as cr:
  155. cr.autocommit(True) # avoid transaction block
  156. _drop_conn(cr, db_name)
  157. try:
  158. cr.execute('DROP DATABASE "%s"' % db_name)
  159. except Exception, e:
  160. _logger.error('DROP DB: %s failed:\n%s', db_name, e)
  161. raise Exception("Couldn't drop database %s: %s" % (db_name, e))
  162. else:
  163. _logger.info('DROP DB: %s', db_name)
  164. fs = openerp.tools.config.filestore(db_name)
  165. if os.path.exists(fs):
  166. shutil.rmtree(fs)
  167. return True
  168. def _set_pg_password_in_environment(func):
  169. """ On systems where pg_restore/pg_dump require an explicit
  170. password (i.e. when not connecting via unix sockets, and most
  171. importantly on Windows), it is necessary to pass the PG user
  172. password in the environment or in a special .pgpass file.
  173. This decorator handles setting
  174. :envvar:`PGPASSWORD` if it is not already
  175. set, and removing it afterwards.
  176. See also http://www.postgresql.org/docs/8.4/static/libpq-envars.html
  177. .. note:: This is not thread-safe, and should never be enabled for
  178. SaaS (giving SaaS users the super-admin password is not a good idea
  179. anyway)
  180. """
  181. @wraps(func)
  182. def wrapper(*args, **kwargs):
  183. if os.environ.get('PGPASSWORD') or not openerp.tools.config['db_password']:
  184. return func(*args, **kwargs)
  185. else:
  186. os.environ['PGPASSWORD'] = openerp.tools.config['db_password']
  187. try:
  188. return func(*args, **kwargs)
  189. finally:
  190. del os.environ['PGPASSWORD']
  191. return wrapper
  192. def exp_dump(db_name):
  193. with tempfile.TemporaryFile() as t:
  194. dump_db(db_name, t)
  195. t.seek(0)
  196. return t.read().encode('base64')
  197. @_set_pg_password_in_environment
  198. def dump_db(db, stream):
  199. """Dump database `db` into file-like object `stream`"""
  200. with openerp.tools.osutil.tempdir() as dump_dir:
  201. registry = openerp.modules.registry.RegistryManager.get(db)
  202. with registry.cursor() as cr:
  203. filestore = registry['ir.attachment']._filestore(cr, SUPERUSER_ID)
  204. if os.path.exists(filestore):
  205. shutil.copytree(filestore, os.path.join(dump_dir, 'filestore'))
  206. dump_file = os.path.join(dump_dir, 'dump.sql')
  207. cmd = ['pg_dump', '--format=p', '--no-owner', '--file=' + dump_file]
  208. if openerp.tools.config['db_user']:
  209. cmd.append('--username=' + openerp.tools.config['db_user'])
  210. if openerp.tools.config['db_host']:
  211. cmd.append('--host=' + openerp.tools.config['db_host'])
  212. if openerp.tools.config['db_port']:
  213. cmd.append('--port=' + str(openerp.tools.config['db_port']))
  214. cmd.append(db)
  215. if openerp.tools.exec_pg_command(*cmd):
  216. _logger.error('DUMP DB: %s failed! Please verify the configuration of the database '
  217. 'password on the server. You may need to create a .pgpass file for '
  218. 'authentication, or specify `db_password` in the server configuration '
  219. 'file.', db)
  220. raise Exception("Couldn't dump database")
  221. openerp.tools.osutil.zip_dir(dump_dir, stream, include_dir=False)
  222. _logger.info('DUMP DB successful: %s', db)
  223. def exp_restore(db_name, data, copy=False):
  224. data_file = tempfile.NamedTemporaryFile(delete=False)
  225. try:
  226. data_file.write(data.decode('base64'))
  227. data_file.close()
  228. restore_db(db_name, data_file.name, copy=copy)
  229. finally:
  230. os.unlink(data_file.name)
  231. return True
  232. @_set_pg_password_in_environment
  233. def restore_db(db, dump_file, copy=False):
  234. assert isinstance(db, basestring)
  235. if exp_db_exist(db):
  236. _logger.warning('RESTORE DB: %s already exists', db)
  237. raise Exception("Database already exists")
  238. _create_empty_database(db)
  239. filestore_path = None
  240. with openerp.tools.osutil.tempdir() as dump_dir:
  241. if zipfile.is_zipfile(dump_file):
  242. # v8 format
  243. with zipfile.ZipFile(dump_file, 'r') as z:
  244. # only extract known members!
  245. filestore = [m for m in z.namelist() if m.startswith('filestore/')]
  246. z.extractall(dump_dir, ['dump.sql'] + filestore)
  247. if filestore:
  248. filestore_path = os.path.join(dump_dir, 'filestore')
  249. pg_cmd = 'psql'
  250. pg_args = ['-q', '-f', os.path.join(dump_dir, 'dump.sql')]
  251. else:
  252. # <= 7.0 format (raw pg_dump output)
  253. pg_cmd = 'pg_restore'
  254. pg_args = ['--no-owner', dump_file]
  255. args = []
  256. if openerp.tools.config['db_user']:
  257. args.append('--username=' + openerp.tools.config['db_user'])
  258. if openerp.tools.config['db_host']:
  259. args.append('--host=' + openerp.tools.config['db_host'])
  260. if openerp.tools.config['db_port']:
  261. args.append('--port=' + str(openerp.tools.config['db_port']))
  262. args.append('--dbname=' + db)
  263. pg_args = args + pg_args
  264. if openerp.tools.exec_pg_command(pg_cmd, *pg_args):
  265. raise Exception("Couldn't restore database")
  266. registry = openerp.modules.registry.RegistryManager.new(db)
  267. with registry.cursor() as cr:
  268. if copy:
  269. # if it's a copy of a database, force generation of a new dbuuid
  270. registry['ir.config_parameter'].init(cr, force=True)
  271. if filestore_path:
  272. filestore_dest = registry['ir.attachment']._filestore(cr, SUPERUSER_ID)
  273. shutil.move(filestore_path, filestore_dest)
  274. if openerp.tools.config['unaccent']:
  275. try:
  276. with cr.savepoint():
  277. cr.execute("CREATE EXTENSION unaccent")
  278. except psycopg2.Error:
  279. pass
  280. _logger.info('RESTORE DB: %s', db)
  281. def exp_rename(old_name, new_name):
  282. openerp.modules.registry.RegistryManager.delete(old_name)
  283. openerp.sql_db.close_db(old_name)
  284. db = openerp.sql_db.db_connect('postgres')
  285. with closing(db.cursor()) as cr:
  286. cr.autocommit(True) # avoid transaction block
  287. _drop_conn(cr, old_name)
  288. try:
  289. cr.execute('ALTER DATABASE "%s" RENAME TO "%s"' % (old_name, new_name))
  290. _logger.info('RENAME DB: %s -> %s', old_name, new_name)
  291. except Exception, e:
  292. _logger.error('RENAME DB: %s -> %s failed:\n%s', old_name, new_name, e)
  293. raise Exception("Couldn't rename database %s to %s: %s" % (old_name, new_name, e))
  294. old_fs = openerp.tools.config.filestore(old_name)
  295. new_fs = openerp.tools.config.filestore(new_name)
  296. if os.path.exists(old_fs) and not os.path.exists(new_fs):
  297. shutil.move(old_fs, new_fs)
  298. return True
  299. @openerp.tools.mute_logger('openerp.sql_db')
  300. def exp_db_exist(db_name):
  301. ## Not True: in fact, check if connection to database is possible. The database may exists
  302. return bool(openerp.sql_db.db_connect(db_name))
  303. def exp_list(document=False):
  304. if not openerp.tools.config['list_db'] and not document:
  305. raise openerp.exceptions.AccessDenied()
  306. chosen_template = openerp.tools.config['db_template']
  307. templates_list = tuple(set(['template0', 'template1', 'postgres', chosen_template]))
  308. db = openerp.sql_db.db_connect('postgres')
  309. with closing(db.cursor()) as cr:
  310. try:
  311. db_user = openerp.tools.config["db_user"]
  312. if not db_user and os.name == 'posix':
  313. import pwd
  314. db_user = pwd.getpwuid(os.getuid())[0]
  315. if not db_user:
  316. cr.execute("select usename from pg_user where usesysid=(select datdba from pg_database where datname=%s)", (openerp.tools.config["db_name"],))
  317. res = cr.fetchone()
  318. db_user = res and str(res[0])
  319. if db_user:
  320. 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))
  321. else:
  322. cr.execute("select datname from pg_database where datname not in %s order by datname", (templates_list,))
  323. res = [openerp.tools.ustr(name) for (name,) in cr.fetchall()]
  324. except Exception:
  325. res = []
  326. res.sort()
  327. return res
  328. def exp_change_admin_password(new_password):
  329. openerp.tools.config['admin_passwd'] = new_password
  330. openerp.tools.config.save()
  331. return True
  332. def exp_list_lang():
  333. return openerp.tools.scan_languages()
  334. def exp_server_version():
  335. """ Return the version of the server
  336. Used by the client to verify the compatibility with its own version
  337. """
  338. return openerp.release.version
  339. def exp_migrate_databases(databases):
  340. for db in databases:
  341. _logger.info('migrate database %s', db)
  342. openerp.tools.config['update']['base'] = True
  343. openerp.modules.registry.RegistryManager.new(db, force_demo=False, update_module=True)
  344. return True
  345. # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: