1 # -*- coding: utf-8 -*-
11 from openerp import SUPERUSER_ID
12 import openerp.release
18 _logger = logging.getLogger(__name__)
22 self_id_protect = threading.Semaphore()
24 # This should be moved to openerp.modules.db, along side initialize().
25 def _initialize_db(id, db_name, demo, lang, user_password):
29 self_actions[id]['progress'] = 0
30 cr = openerp.sql_db.db_connect(db_name).cursor()
31 openerp.modules.db.initialize(cr) # TODO this should be removed as it is done by RegistryManager.new().
32 openerp.tools.config['lang'] = lang
39 registry = openerp.modules.registry.RegistryManager.new(
40 db_name, demo, self_actions[id], update_module=True)
43 cr = openerp.sql_db.db_connect(db_name).cursor()
46 modobj = registry['ir.module.module']
47 mids = modobj.search(cr, SUPERUSER_ID, [('state', '=', 'installed')])
48 modobj.update_translations(cr, SUPERUSER_ID, mids, lang)
50 # update admin's password and lang
51 values = {'password': user_password, 'lang': lang}
52 registry['res.users'].write(cr, SUPERUSER_ID, [SUPERUSER_ID], values)
54 cr.execute('SELECT login, password FROM res_users ORDER BY login')
55 self_actions[id].update(users=cr.dictfetchall(), clean=True)
61 self_actions[id].update(clean=False, exception=e)
62 _logger.exception('CREATE DATABASE failed:')
63 self_actions[id]['traceback'] = traceback.format_exc()
65 def dispatch(method, params):
66 if method in [ 'create', 'get_progress', 'drop', 'dump',
68 'change_admin_password', 'migrate_databases',
69 'create_database', 'duplicate_database' ]:
72 security.check_super(passwd)
73 elif method in [ 'db_exist', 'list', 'list_lang', 'server_version' ]:
75 # No security check for these methods
78 raise KeyError("Method not found: %s" % method)
79 fn = globals()['exp_' + method]
82 def _create_empty_database(name):
83 db = openerp.sql_db.db_connect('postgres')
85 chosen_template = openerp.tools.config['db_template']
86 cr.execute("""SELECT datname
88 WHERE datname = %s """,
91 raise openerp.exceptions.Warning(" %s database already exists!" % name )
93 cr.autocommit(True) # avoid transaction block
94 cr.execute("""CREATE DATABASE "%s" ENCODING 'unicode' TEMPLATE "%s" """ % (name, chosen_template))
98 def exp_create(db_name, demo, lang, user_password='admin'):
99 self_id_protect.acquire()
103 self_id_protect.release()
105 self_actions[id] = {'clean': False}
107 _create_empty_database(db_name)
109 _logger.info('CREATE DATABASE %s', db_name.lower())
110 create_thread = threading.Thread(target=_initialize_db,
111 args=(id, db_name, demo, lang, user_password))
112 create_thread.start()
113 self_actions[id]['thread'] = create_thread
116 def exp_create_database(db_name, demo, lang, user_password='admin'):
117 """ Similar to exp_create but blocking."""
118 self_id_protect.acquire()
122 self_id_protect.release()
124 self_actions[id] = {'clean': False}
126 _logger.info('Create database `%s`.', db_name)
127 _create_empty_database(db_name)
128 _initialize_db(id, db_name, demo, lang, user_password)
131 def exp_duplicate_database(db_original_name, db_name):
132 _logger.info('Duplicate database `%s` to `%s`.', db_original_name, db_name)
133 openerp.sql_db.close_db(db_original_name)
134 db = openerp.sql_db.db_connect('postgres')
137 cr.autocommit(True) # avoid transaction block
138 cr.execute("""CREATE DATABASE "%s" ENCODING 'unicode' TEMPLATE "%s" """ % (db_name, db_original_name))
143 def exp_get_progress(id):
144 if self_actions[id]['thread'].isAlive():
145 # return openerp.modules.init_progress[db_name]
146 return min(self_actions[id].get('progress', 0),0.95), []
148 clean = self_actions[id]['clean']
150 users = self_actions[id]['users']
152 # Remove the None passwords as they can't be marshalled by XML-RPC.
153 if user['password'] is None:
154 user['password'] = ''
158 e = self_actions[id]['exception'] # TODO this seems wrong: actions[id]['traceback'] is set, but not 'exception'.
162 def exp_drop(db_name):
163 if not exp_db_exist(db_name):
165 openerp.modules.registry.RegistryManager.delete(db_name)
166 openerp.sql_db.close_db(db_name)
168 db = openerp.sql_db.db_connect('postgres')
170 cr.autocommit(True) # avoid transaction block
172 # Try to terminate all other connections that might prevent
173 # dropping the database
176 # PostgreSQL 9.2 renamed pg_stat_activity.procpid to pid:
177 # http://www.postgresql.org/docs/9.2/static/release-9-2.html#AEN110389
178 pid_col = 'pid' if cr._cnx.server_version >= 90200 else 'procpid'
180 cr.execute("""SELECT pg_terminate_backend(%(pid_col)s)
181 FROM pg_stat_activity
182 WHERE datname = %%s AND
183 %(pid_col)s != pg_backend_pid()""" % {'pid_col': pid_col},
189 cr.execute('DROP DATABASE "%s"' % db_name)
191 _logger.error('DROP DB: %s failed:\n%s', db_name, e)
192 raise Exception("Couldn't drop database %s: %s" % (db_name, e))
194 _logger.info('DROP DB: %s', db_name)
199 @contextlib.contextmanager
200 def _set_pg_password_in_environment():
201 """ On systems where pg_restore/pg_dump require an explicit
202 password (i.e. when not connecting via unix sockets, and most
203 importantly on Windows), it is necessary to pass the PG user
204 password in the environment or in a special .pgpass file.
206 This context management method handles setting
207 :envvar:`PGPASSWORD` if it is not already
208 set, and removing it afterwards.
210 See also http://www.postgresql.org/docs/8.4/static/libpq-envars.html
212 .. note:: This is not thread-safe, and should never be enabled for
213 SaaS (giving SaaS users the super-admin password is not a good idea
216 if os.environ.get('PGPASSWORD') or not openerp.tools.config['db_password']:
219 os.environ['PGPASSWORD'] = openerp.tools.config['db_password']
223 del os.environ['PGPASSWORD']
226 def exp_dump(db_name):
227 with _set_pg_password_in_environment():
228 cmd = ['pg_dump', '--format=c', '--no-owner']
229 if openerp.tools.config['db_user']:
230 cmd.append('--username=' + openerp.tools.config['db_user'])
231 if openerp.tools.config['db_host']:
232 cmd.append('--host=' + openerp.tools.config['db_host'])
233 if openerp.tools.config['db_port']:
234 cmd.append('--port=' + str(openerp.tools.config['db_port']))
237 stdin, stdout = openerp.tools.exec_pg_command_pipe(*tuple(cmd))
244 'DUMP DB: %s failed! Please verify the configuration of the database password on the server. '
245 'You may need to create a .pgpass file for authentication, or specify `db_password` in the '
246 'server configuration file.\n %s', db_name, data)
247 raise Exception, "Couldn't dump database"
248 _logger.info('DUMP DB successful: %s', db_name)
250 return base64.encodestring(data)
252 def exp_restore(db_name, data):
253 with _set_pg_password_in_environment():
254 if exp_db_exist(db_name):
255 _logger.warning('RESTORE DB: %s already exists', db_name)
256 raise Exception, "Database already exists"
258 _create_empty_database(db_name)
260 cmd = ['pg_restore', '--no-owner']
261 if openerp.tools.config['db_user']:
262 cmd.append('--username=' + openerp.tools.config['db_user'])
263 if openerp.tools.config['db_host']:
264 cmd.append('--host=' + openerp.tools.config['db_host'])
265 if openerp.tools.config['db_port']:
266 cmd.append('--port=' + str(openerp.tools.config['db_port']))
267 cmd.append('--dbname=' + db_name)
270 buf=base64.decodestring(data)
272 tmpfile = (os.environ['TMP'] or 'C:\\') + os.tmpnam()
273 file(tmpfile, 'wb').write(buf)
275 args2.append(tmpfile)
277 stdin, stdout = openerp.tools.exec_pg_command_pipe(*args2)
278 if not os.name == "nt":
279 stdin.write(base64.decodestring(data))
283 raise Exception, "Couldn't restore database"
284 _logger.info('RESTORE DB: %s', db_name)
288 def exp_rename(old_name, new_name):
289 openerp.modules.registry.RegistryManager.delete(old_name)
290 openerp.sql_db.close_db(old_name)
292 db = openerp.sql_db.db_connect('postgres')
294 cr.autocommit(True) # avoid transaction block
297 cr.execute('ALTER DATABASE "%s" RENAME TO "%s"' % (old_name, new_name))
299 _logger.error('RENAME DB: %s -> %s failed:\n%s', old_name, new_name, e)
300 raise Exception("Couldn't rename database %s to %s: %s" % (old_name, new_name, e))
302 fs = os.path.join(openerp.tools.config['root_path'], 'filestore')
303 if os.path.exists(os.path.join(fs, old_name)):
304 os.rename(os.path.join(fs, old_name), os.path.join(fs, new_name))
306 _logger.info('RENAME DB: %s -> %s', old_name, new_name)
311 def exp_db_exist(db_name):
312 ## Not True: in fact, check if connection to database is possible. The database may exists
313 return bool(openerp.sql_db.db_connect(db_name))
315 def exp_list(document=False):
316 if not openerp.tools.config['list_db'] and not document:
317 raise openerp.exceptions.AccessDenied()
318 chosen_template = openerp.tools.config['db_template']
319 templates_list = tuple(set(['template0', 'template1', 'postgres', chosen_template]))
320 db = openerp.sql_db.db_connect('postgres')
324 db_user = openerp.tools.config["db_user"]
325 if not db_user and os.name == 'posix':
327 db_user = pwd.getpwuid(os.getuid())[0]
329 cr.execute("select usename from pg_user where usesysid=(select datdba from pg_database where datname=%s)", (openerp.tools.config["db_name"],))
331 db_user = res and str(res[0])
333 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))
335 cr.execute("select datname from pg_database where datname not in %s order by datname", (templates_list,))
336 res = [str(name) for (name,) in cr.fetchall()]
344 def exp_change_admin_password(new_password):
345 openerp.tools.config['admin_passwd'] = new_password
346 openerp.tools.config.save()
350 return openerp.tools.scan_languages()
352 def exp_server_version():
353 """ Return the version of the server
354 Used by the client to verify the compatibility with its own version
356 return openerp.release.version
358 def exp_migrate_databases(databases):
360 _logger.info('migrate database %s', db)
361 openerp.tools.config['update']['base'] = True
362 openerp.modules.registry.RegistryManager.new(db, force_demo=False, update_module=True)
365 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: