[MERGE] trunk-service-thu (remove the class openerp.netsvc.ExportService)
[odoo/odoo.git] / openerp / service / db.py
1 # -*- coding: utf-8 -*-
2
3 import contextlib
4 import logging
5 import threading
6 import traceback
7
8 from openerp import SUPERUSER_ID
9 import openerp.pooler
10 import openerp.sql_db
11 import openerp.tools
12
13 import security
14
15 _logger = logging.getLogger(__name__)
16
17 self_actions = {}
18 self_id = 0
19 self_id_protect = threading.Semaphore()
20
21 # This should be moved to openerp.modules.db, along side initialize().
22 def _initialize_db(id, db_name, demo, lang, user_password):
23     cr = None
24     try:
25         self_actions[id]['progress'] = 0
26         cr = openerp.sql_db.db_connect(db_name).cursor()
27         openerp.modules.db.initialize(cr) # TODO this should be removed as it is done by pooler.restart_pool.
28         openerp.tools.config['lang'] = lang
29         cr.commit()
30         cr.close()
31
32         pool = openerp.pooler.restart_pool(db_name, demo, self_actions[id],
33                                    update_module=True)[1]
34
35         cr = openerp.sql_db.db_connect(db_name).cursor()
36
37         if lang:
38             modobj = pool.get('ir.module.module')
39             mids = modobj.search(cr, SUPERUSER_ID, [('state', '=', 'installed')])
40             modobj.update_translations(cr, SUPERUSER_ID, mids, lang)
41
42         # update admin's password and lang
43         values = {'password': user_password, 'lang': lang}
44         pool.get('res.users').write(cr, SUPERUSER_ID, [SUPERUSER_ID], values)
45
46         cr.execute('SELECT login, password FROM res_users ORDER BY login')
47         self_actions[id].update(users=cr.dictfetchall(), clean=True)
48         cr.commit()
49         cr.close()
50     except Exception, e:
51         self_actions[id].update(clean=False, exception=e)
52         _logger.exception('CREATE DATABASE failed:')
53         self_actions[id]['traceback'] = traceback.format_exc()
54         if cr:
55             cr.close()
56
57 def dispatch(method, params):
58     if method in [ 'create', 'get_progress', 'drop', 'dump',
59         'restore', 'rename',
60         'change_admin_password', 'migrate_databases',
61         'create_database', 'duplicate_database' ]:
62         passwd = params[0]
63         params = params[1:]
64         security.check_super(passwd)
65     elif method in [ 'db_exist', 'list', 'list_lang', 'server_version' ]:
66         # params = params
67         # No security check for these methods
68         pass
69     else:
70         raise KeyError("Method not found: %s" % method)
71     fn = globals()['exp_' + method]
72     return fn(*params)
73
74 def _create_empty_database(name):
75     db = openerp.sql_db.db_connect('postgres')
76     cr = db.cursor()
77     chosen_template = openerp.tools.config['db_template']
78     cr.execute("""SELECT datname 
79                           FROM pg_database
80                           WHERE datname = %s """,
81                        (name,))
82     if cr.fetchall():
83         raise openerp.exceptions.Warning(" %s database already exists!" % name )
84     try:
85         cr.autocommit(True) # avoid transaction block
86         cr.execute("""CREATE DATABASE "%s" ENCODING 'unicode' TEMPLATE "%s" """ % (name, chosen_template))
87     finally:
88         cr.close()
89
90 def exp_create(db_name, demo, lang, user_password='admin'):
91     self_id_protect.acquire()
92     global self_id
93     self_id += 1
94     id = self_id
95     self_id_protect.release()
96
97     self_actions[id] = {'clean': False}
98
99     _create_empty_database(db_name)
100
101     _logger.info('CREATE DATABASE %s', db_name.lower())
102     create_thread = threading.Thread(target=_initialize_db,
103             args=(id, db_name, demo, lang, user_password))
104     create_thread.start()
105     self_actions[id]['thread'] = create_thread
106     return id
107
108 def exp_create_database(db_name, demo, lang, user_password='admin'):
109     """ Similar to exp_create but blocking."""
110     self_id_protect.acquire()
111     global self_id
112     self_id += 1
113     id = self_id
114     self_id_protect.release()
115
116     self_actions[id] = {'clean': False}
117
118     _logger.info('Create database `%s`.', db_name)
119     _create_empty_database(db_name)
120     _initialize_db(id, db_name, demo, lang, user_password)
121     return True
122
123 def exp_duplicate_database(db_original_name, db_name):
124     _logger.info('Duplicate database `%s` to `%s`.', db_original_name, db_name)
125     openerp.sql_db.close_db(db_original_name)
126     db = openerp.sql_db.db_connect('postgres')
127     cr = db.cursor()
128     try:
129         cr.autocommit(True) # avoid transaction block
130         cr.execute("""CREATE DATABASE "%s" ENCODING 'unicode' TEMPLATE "%s" """ % (db_name, db_original_name))
131     finally:
132         cr.close()
133     return True
134
135 def exp_get_progress(id):
136     if self_actions[id]['thread'].isAlive():
137 #       return openerp.modules.init_progress[db_name]
138         return min(self_actions[id].get('progress', 0),0.95), []
139     else:
140         clean = self_actions[id]['clean']
141         if clean:
142             users = self_actions[id]['users']
143             for user in users:
144                 # Remove the None passwords as they can't be marshalled by XML-RPC.
145                 if user['password'] is None:
146                     user['password'] = ''
147             self_actions.pop(id)
148             return 1.0, users
149         else:
150             e = self_actions[id]['exception'] # TODO this seems wrong: actions[id]['traceback'] is set, but not 'exception'.
151             self_actions.pop(id)
152             raise Exception, e
153
154 def exp_drop(db_name):
155     if not exp_db_exist(db_name):
156         return False
157     openerp.modules.registry.RegistryManager.delete(db_name)
158     openerp.sql_db.close_db(db_name)
159
160     db = openerp.sql_db.db_connect('postgres')
161     cr = db.cursor()
162     cr.autocommit(True) # avoid transaction block
163     try:
164         # Try to terminate all other connections that might prevent
165         # dropping the database
166         try:
167
168             # PostgreSQL 9.2 renamed pg_stat_activity.procpid to pid:
169             # http://www.postgresql.org/docs/9.2/static/release-9-2.html#AEN110389
170             pid_col = 'pid' if cr._cnx.server_version >= 90200 else 'procpid'
171
172             cr.execute("""SELECT pg_terminate_backend(%(pid_col)s)
173                           FROM pg_stat_activity
174                           WHERE datname = %%s AND 
175                                 %(pid_col)s != pg_backend_pid()""" % {'pid_col': pid_col},
176                        (db_name,))
177         except Exception:
178             pass
179
180         try:
181             cr.execute('DROP DATABASE "%s"' % db_name)
182         except Exception, e:
183             _logger.error('DROP DB: %s failed:\n%s', db_name, e)
184             raise Exception("Couldn't drop database %s: %s" % (db_name, e))
185         else:
186             _logger.info('DROP DB: %s', db_name)
187     finally:
188         cr.close()
189     return True
190
191 @contextlib.contextmanager
192 def _set_pg_password_in_environment():
193     """ On Win32, pg_dump (and pg_restore) require that
194     :envvar:`PGPASSWORD` be set
195
196     This context management method handles setting
197     :envvar:`PGPASSWORD` iif win32 and the envvar is not already
198     set, and removing it afterwards.
199     """
200     if os.name != 'nt' or os.environ.get('PGPASSWORD'):
201         yield
202     else:
203         os.environ['PGPASSWORD'] = openerp.tools.config['db_password']
204         try:
205             yield
206         finally:
207             del os.environ['PGPASSWORD']
208
209
210 def exp_dump(db_name):
211     with _set_pg_password_in_environment():
212         cmd = ['pg_dump', '--format=c', '--no-owner']
213         if openerp.tools.config['db_user']:
214             cmd.append('--username=' + openerp.tools.config['db_user'])
215         if openerp.tools.config['db_host']:
216             cmd.append('--host=' + openerp.tools.config['db_host'])
217         if openerp.tools.config['db_port']:
218             cmd.append('--port=' + str(openerp.tools.config['db_port']))
219         cmd.append(db_name)
220
221         stdin, stdout = openerp.tools.exec_pg_command_pipe(*tuple(cmd))
222         stdin.close()
223         data = stdout.read()
224         res = stdout.close()
225
226         if not data or res:
227             _logger.error(
228                     'DUMP DB: %s failed! Please verify the configuration of the database password on the server. '
229                     'It should be provided as a -w <PASSWD> command-line option, or as `db_password` in the '
230                     'server configuration file.\n %s', db_name, data)
231             raise Exception, "Couldn't dump database"
232         _logger.info('DUMP DB successful: %s', db_name)
233
234         return base64.encodestring(data)
235
236 def exp_restore(db_name, data):
237     with _set_pg_password_in_environment():
238         if exp_db_exist(db_name):
239             _logger.warning('RESTORE DB: %s already exists', db_name)
240             raise Exception, "Database already exists"
241
242         _create_empty_database(db_name)
243
244         cmd = ['pg_restore', '--no-owner']
245         if openerp.tools.config['db_user']:
246             cmd.append('--username=' + openerp.tools.config['db_user'])
247         if openerp.tools.config['db_host']:
248             cmd.append('--host=' + openerp.tools.config['db_host'])
249         if openerp.tools.config['db_port']:
250             cmd.append('--port=' + str(openerp.tools.config['db_port']))
251         cmd.append('--dbname=' + db_name)
252         args2 = tuple(cmd)
253
254         buf=base64.decodestring(data)
255         if os.name == "nt":
256             tmpfile = (os.environ['TMP'] or 'C:\\') + os.tmpnam()
257             file(tmpfile, 'wb').write(buf)
258             args2=list(args2)
259             args2.append(tmpfile)
260             args2=tuple(args2)
261         stdin, stdout = openerp.tools.exec_pg_command_pipe(*args2)
262         if not os.name == "nt":
263             stdin.write(base64.decodestring(data))
264         stdin.close()
265         res = stdout.close()
266         if res:
267             raise Exception, "Couldn't restore database"
268         _logger.info('RESTORE DB: %s', db_name)
269
270         return True
271
272 def exp_rename(old_name, new_name):
273     openerp.modules.registry.RegistryManager.delete(old_name)
274     openerp.sql_db.close_db(old_name)
275
276     db = openerp.sql_db.db_connect('postgres')
277     cr = db.cursor()
278     cr.autocommit(True) # avoid transaction block
279     try:
280         try:
281             cr.execute('ALTER DATABASE "%s" RENAME TO "%s"' % (old_name, new_name))
282         except Exception, e:
283             _logger.error('RENAME DB: %s -> %s failed:\n%s', old_name, new_name, e)
284             raise Exception("Couldn't rename database %s to %s: %s" % (old_name, new_name, e))
285         else:
286             fs = os.path.join(openerp.tools.config['root_path'], 'filestore')
287             if os.path.exists(os.path.join(fs, old_name)):
288                 os.rename(os.path.join(fs, old_name), os.path.join(fs, new_name))
289
290             _logger.info('RENAME DB: %s -> %s', old_name, new_name)
291     finally:
292         cr.close()
293     return True
294
295 def exp_db_exist(db_name):
296     ## Not True: in fact, check if connection to database is possible. The database may exists
297     return bool(openerp.sql_db.db_connect(db_name))
298
299 def exp_list(document=False):
300     if not openerp.tools.config['list_db'] and not document:
301         raise openerp.exceptions.AccessDenied()
302     chosen_template = openerp.tools.config['db_template']
303     templates_list = tuple(set(['template0', 'template1', 'postgres', chosen_template]))
304     db = openerp.sql_db.db_connect('postgres')
305     cr = db.cursor()
306     try:
307         try:
308             db_user = openerp.tools.config["db_user"]
309             if not db_user and os.name == 'posix':
310                 import pwd
311                 db_user = pwd.getpwuid(os.getuid())[0]
312             if not db_user:
313                 cr.execute("select usename from pg_user where usesysid=(select datdba from pg_database where datname=%s)", (openerp.tools.config["db_name"],))
314                 res = cr.fetchone()
315                 db_user = res and str(res[0])
316             if db_user:
317                 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))
318             else:
319                 cr.execute("select datname from pg_database where datname not in %s order by datname", (templates_list,))
320             res = [str(name) for (name,) in cr.fetchall()]
321         except Exception:
322             res = []
323     finally:
324         cr.close()
325     res.sort()
326     return res
327
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
333 def exp_list_lang():
334     return openerp.tools.scan_languages()
335
336 def exp_server_version():
337     """ Return the version of the server
338         Used by the client to verify the compatibility with its own version
339     """
340     return release.version
341
342 def exp_migrate_databases(databases):
343     for db in databases:
344         _logger.info('migrate database %s', db)
345         openerp.tools.config['update']['base'] = True
346         openerp.pooler.restart_pool(db, force_demo=False, update_module=True)
347     return True
348
349 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: