2013-01-30 14:56:16 +00:00
# -*- coding: utf-8 -*-
2014-03-04 17:56:56 +00:00
from contextlib import closing
from functools import wraps
2013-01-30 14:56:16 +00:00
import logging
2013-03-18 11:02:57 +00:00
import os
2014-03-04 17:56:56 +00:00
import shutil
2013-01-30 14:56:16 +00:00
import threading
import traceback
2014-03-04 17:56:56 +00:00
import tempfile
import zipfile
import psycopg2
2013-01-30 14:56:16 +00:00
2013-03-27 11:10:14 +00:00
import openerp
2013-01-30 14:56:16 +00:00
from openerp import SUPERUSER_ID
2013-03-18 11:02:57 +00:00
import openerp . release
2013-01-30 14:56:16 +00:00
import openerp . sql_db
import openerp . tools
import security
_logger = logging . getLogger ( __name__ )
self_actions = { }
self_id = 0
self_id_protect = threading . Semaphore ( )
# This should be moved to openerp.modules.db, along side initialize().
def _initialize_db ( id , db_name , demo , lang , user_password ) :
try :
2014-01-09 09:32:58 +00:00
self_actions [ id ] [ ' progress ' ] = 0
db = openerp . sql_db . db_connect ( db_name )
with closing ( db . cursor ( ) ) as cr :
2014-03-03 16:08:05 +00:00
# TODO this should be removed as it is done by RegistryManager.new().
openerp . modules . db . initialize ( cr )
2013-02-19 16:30:47 +00:00
openerp . tools . config [ ' lang ' ] = lang
cr . commit ( )
2013-01-30 14:56:16 +00:00
2013-03-27 11:10:14 +00:00
registry = openerp . modules . registry . RegistryManager . new (
2013-04-02 09:18:52 +00:00
db_name , demo , self_actions [ id ] , update_module = True )
2013-01-30 14:56:16 +00:00
2014-01-09 09:32:58 +00:00
with closing ( db . cursor ( ) ) as cr :
2013-02-19 16:30:47 +00:00
if lang :
2013-03-27 11:10:14 +00:00
modobj = registry [ ' ir.module.module ' ]
2013-02-19 16:30:47 +00:00
mids = modobj . search ( cr , SUPERUSER_ID , [ ( ' state ' , ' = ' , ' installed ' ) ] )
modobj . update_translations ( cr , SUPERUSER_ID , mids , lang )
2013-01-30 14:56:16 +00:00
2013-02-19 16:30:47 +00:00
# update admin's password and lang
values = { ' password ' : user_password , ' lang ' : lang }
2013-03-27 11:10:14 +00:00
registry [ ' res.users ' ] . write ( cr , SUPERUSER_ID , [ SUPERUSER_ID ] , values )
2013-01-30 14:56:16 +00:00
2013-02-19 16:30:47 +00:00
cr . execute ( ' SELECT login, password FROM res_users ORDER BY login ' )
self_actions [ id ] . update ( users = cr . dictfetchall ( ) , clean = True )
cr . commit ( )
2014-01-09 09:32:58 +00:00
2013-01-30 14:56:16 +00:00
except Exception , e :
self_actions [ id ] . update ( clean = False , exception = e )
_logger . exception ( ' CREATE DATABASE failed: ' )
self_actions [ id ] [ ' traceback ' ] = traceback . format_exc ( )
def dispatch ( method , params ) :
2014-03-03 16:08:05 +00:00
if method in [ ' create ' , ' get_progress ' , ' drop ' , ' dump ' , ' restore ' , ' rename ' ,
' change_admin_password ' , ' migrate_databases ' ,
' create_database ' , ' duplicate_database ' ] :
2013-01-30 14:56:16 +00:00
passwd = params [ 0 ]
params = params [ 1 : ]
security . check_super ( passwd )
2014-03-03 16:08:05 +00:00
elif method in [ ' db_exist ' , ' list ' , ' list_lang ' , ' server_version ' ] :
2013-01-30 14:56:16 +00:00
# params = params
# No security check for these methods
pass
else :
raise KeyError ( " Method not found: %s " % method )
fn = globals ( ) [ ' exp_ ' + method ]
return fn ( * params )
def _create_empty_database ( name ) :
db = openerp . sql_db . db_connect ( ' postgres ' )
2014-01-09 09:32:58 +00:00
with closing ( db . cursor ( ) ) as cr :
chosen_template = openerp . tools . config [ ' db_template ' ]
cr . execute ( " SELECT datname FROM pg_database WHERE datname = %s " ,
( name , ) )
if cr . fetchall ( ) :
2014-03-03 16:08:05 +00:00
raise openerp . exceptions . Warning ( " database %r already exists! " % ( name , ) )
2014-01-09 09:32:58 +00:00
else :
2014-03-03 16:08:05 +00:00
cr . autocommit ( True ) # avoid transaction block
2014-01-09 09:32:58 +00:00
cr . execute ( """ CREATE DATABASE " %s " ENCODING ' unicode ' TEMPLATE " %s " """ % ( name , chosen_template ) )
2013-01-30 14:56:16 +00:00
def exp_create ( db_name , demo , lang , user_password = ' admin ' ) :
self_id_protect . acquire ( )
global self_id
self_id + = 1
id = self_id
self_id_protect . release ( )
self_actions [ id ] = { ' clean ' : False }
_create_empty_database ( db_name )
_logger . info ( ' CREATE DATABASE %s ' , db_name . lower ( ) )
create_thread = threading . Thread ( target = _initialize_db ,
2014-03-03 16:08:05 +00:00
args = ( id , db_name , demo , lang , user_password ) )
2013-01-30 14:56:16 +00:00
create_thread . start ( )
self_actions [ id ] [ ' thread ' ] = create_thread
return id
def exp_create_database ( db_name , demo , lang , user_password = ' admin ' ) :
""" Similar to exp_create but blocking. """
self_id_protect . acquire ( )
global self_id
self_id + = 1
id = self_id
self_id_protect . release ( )
self_actions [ id ] = { ' clean ' : False }
_logger . info ( ' Create database ` %s `. ' , db_name )
_create_empty_database ( db_name )
_initialize_db ( id , db_name , demo , lang , user_password )
return True
def exp_duplicate_database ( db_original_name , db_name ) :
_logger . info ( ' Duplicate database ` %s ` to ` %s `. ' , db_original_name , db_name )
openerp . sql_db . close_db ( db_original_name )
db = openerp . sql_db . db_connect ( ' postgres ' )
2014-01-09 09:32:58 +00:00
with closing ( db . cursor ( ) ) as cr :
2014-03-03 16:08:05 +00:00
cr . autocommit ( True ) # avoid transaction block
2013-01-30 14:56:16 +00:00
cr . execute ( """ CREATE DATABASE " %s " ENCODING ' unicode ' TEMPLATE " %s " """ % ( db_name , db_original_name ) )
2014-03-21 15:56:59 +00:00
from_fs = openerp . tools . config . filestore ( db_original_name )
to_fs = openerp . tools . config . filestore ( db_name )
if os . path . exists ( from_fs ) and not os . path . exists ( to_fs ) :
shutil . copy ( from_fs , to_fs )
2013-01-30 14:56:16 +00:00
return True
def exp_get_progress ( id ) :
if self_actions [ id ] [ ' thread ' ] . isAlive ( ) :
# return openerp.modules.init_progress[db_name]
2014-03-03 16:08:05 +00:00
return min ( self_actions [ id ] . get ( ' progress ' , 0 ) , 0.95 ) , [ ]
2013-01-30 14:56:16 +00:00
else :
clean = self_actions [ id ] [ ' clean ' ]
if clean :
users = self_actions [ id ] [ ' users ' ]
2013-01-31 13:04:47 +00:00
for user in users :
# Remove the None passwords as they can't be marshalled by XML-RPC.
if user [ ' password ' ] is None :
user [ ' password ' ] = ' '
2013-01-30 14:56:16 +00:00
self_actions . pop ( id )
return 1.0 , users
else :
2014-03-03 16:08:05 +00:00
a = self_actions . pop ( id )
2014-03-04 17:56:56 +00:00
exc , tb = a [ ' exception ' ] , a [ ' traceback ' ]
raise Exception , exc , tb
2013-01-30 14:56:16 +00:00
def exp_drop ( db_name ) :
2014-02-06 11:02:20 +00:00
if db_name not in exp_list ( True ) :
2013-01-30 14:56:16 +00:00
return False
openerp . modules . registry . RegistryManager . delete ( db_name )
openerp . sql_db . close_db ( db_name )
db = openerp . sql_db . db_connect ( ' postgres ' )
2014-01-09 09:32:58 +00:00
with closing ( db . cursor ( ) ) as cr :
2014-03-03 16:08:05 +00:00
cr . autocommit ( True ) # avoid transaction block
2013-01-30 14:56:16 +00:00
# Try to terminate all other connections that might prevent
# dropping the database
try :
# PostgreSQL 9.2 renamed pg_stat_activity.procpid to pid:
# http://www.postgresql.org/docs/9.2/static/release-9-2.html#AEN110389
pid_col = ' pid ' if cr . _cnx . server_version > = 90200 else ' procpid '
cr . execute ( """ SELECT pg_terminate_backend( %(pid_col)s )
FROM pg_stat_activity
2014-03-03 16:08:05 +00:00
WHERE datname = % % s AND
2013-01-30 14:56:16 +00:00
% ( pid_col ) s != pg_backend_pid ( ) """ % { ' pid_col ' : pid_col},
( db_name , ) )
except Exception :
pass
try :
cr . execute ( ' DROP DATABASE " %s " ' % db_name )
except Exception , e :
_logger . error ( ' DROP DB: %s failed: \n %s ' , db_name , e )
raise Exception ( " Couldn ' t drop database %s : %s " % ( db_name , e ) )
else :
_logger . info ( ' DROP DB: %s ' , db_name )
2014-03-21 15:56:59 +00:00
fs = openerp . tools . config . filestore ( db_name )
if os . path . exists ( fs ) :
shutil . rmtree ( fs )
2013-01-30 14:56:16 +00:00
return True
2014-03-04 17:56:56 +00:00
def _set_pg_password_in_environment ( func ) :
2013-04-04 13:07:04 +00:00
""" On systems where pg_restore/pg_dump require an explicit
password ( i . e . when not connecting via unix sockets , and most
importantly on Windows ) , it is necessary to pass the PG user
password in the environment or in a special . pgpass file .
2013-01-30 14:56:16 +00:00
2014-03-04 17:56:56 +00:00
This decorator handles setting
2013-04-04 13:07:04 +00:00
: envvar : ` PGPASSWORD ` if it is not already
2013-01-30 14:56:16 +00:00
set , and removing it afterwards .
2013-04-04 13:07:04 +00:00
See also http : / / www . postgresql . org / docs / 8.4 / static / libpq - envars . html
2014-03-03 16:08:05 +00:00
2013-04-04 13:07:04 +00:00
. . note : : This is not thread - safe , and should never be enabled for
SaaS ( giving SaaS users the super - admin password is not a good idea
anyway )
2013-01-30 14:56:16 +00:00
"""
2014-03-04 17:56:56 +00:00
@wraps ( func )
def wrapper ( * args , * * kwargs ) :
if os . environ . get ( ' PGPASSWORD ' ) or not openerp . tools . config [ ' db_password ' ] :
return func ( * args , * * kwargs )
else :
os . environ [ ' PGPASSWORD ' ] = openerp . tools . config [ ' db_password ' ]
try :
return func ( * args , * * kwargs )
finally :
del os . environ [ ' PGPASSWORD ' ]
return wrapper
2013-01-30 14:56:16 +00:00
def exp_dump ( db_name ) :
2014-03-04 17:56:56 +00:00
with tempfile . TemporaryFile ( ) as t :
dump_db ( db_name , t )
t . seek ( 0 )
return t . read ( ) . encode ( ' base64 ' )
@_set_pg_password_in_environment
def dump_db ( db , stream ) :
""" Dump database `db` into file-like object `stream` """
with openerp . tools . osutil . tempdir ( ) as dump_dir :
registry = openerp . modules . registry . RegistryManager . get ( db )
with registry . cursor ( ) as cr :
filestore = registry [ ' ir.attachment ' ] . _filestore ( cr , SUPERUSER_ID )
if os . path . exists ( filestore ) :
shutil . copytree ( filestore , os . path . join ( dump_dir , ' filestore ' ) )
dump_file = os . path . join ( dump_dir , ' dump.sql ' )
cmd = [ ' pg_dump ' , ' --format=p ' , ' --no-owner ' , ' --file= ' + dump_file ]
2013-01-30 14:56:16 +00:00
if openerp . tools . config [ ' db_user ' ] :
cmd . append ( ' --username= ' + openerp . tools . config [ ' db_user ' ] )
if openerp . tools . config [ ' db_host ' ] :
cmd . append ( ' --host= ' + openerp . tools . config [ ' db_host ' ] )
if openerp . tools . config [ ' db_port ' ] :
cmd . append ( ' --port= ' + str ( openerp . tools . config [ ' db_port ' ] ) )
2014-03-04 17:56:56 +00:00
cmd . append ( db )
2013-01-30 14:56:16 +00:00
2014-03-04 17:56:56 +00:00
if openerp . tools . exec_pg_command ( * cmd ) :
2014-03-03 16:08:05 +00:00
_logger . error ( ' DUMP DB: %s failed! Please verify the configuration of the database '
' password on the server. You may need to create a .pgpass file for '
' authentication, or specify `db_password` in the server configuration '
2014-03-04 17:56:56 +00:00
' file. ' , db )
2014-03-03 16:08:05 +00:00
raise Exception ( " Couldn ' t dump database " )
2013-01-30 14:56:16 +00:00
2014-03-04 17:56:56 +00:00
openerp . tools . osutil . zip_dir ( dump_dir , stream , include_dir = False )
_logger . info ( ' DUMP DB successful: %s ' , db )
def exp_restore ( db_name , data , copy = False ) :
data_file = tempfile . NamedTemporaryFile ( delete = False )
try :
data_file . write ( data . decode ( ' base64 ' ) )
data_file . close ( )
restore_db ( db_name , data_file . name , copy = copy )
finally :
os . unlink ( data_file . name )
return True
@_set_pg_password_in_environment
def restore_db ( db , dump_file , copy = False ) :
assert isinstance ( db , basestring )
if exp_db_exist ( db ) :
_logger . warning ( ' RESTORE DB: %s already exists ' , db )
raise Exception ( " Database already exists " )
_create_empty_database ( db )
2013-01-30 14:56:16 +00:00
2014-03-04 17:56:56 +00:00
filestore_path = None
with openerp . tools . osutil . tempdir ( ) as dump_dir :
if zipfile . is_zipfile ( dump_file ) :
# v8 format
with zipfile . ZipFile ( dump_file , ' r ' ) as z :
# only extract known members!
filestore = [ m for m in z . namelist ( ) if m . startswith ( ' filestore/ ' ) ]
z . extractall ( dump_dir , [ ' dump.sql ' ] + filestore )
2013-01-30 14:56:16 +00:00
2014-03-04 17:56:56 +00:00
if filestore :
filestore_path = os . path . join ( dump_dir , ' filestore ' )
pg_cmd = ' psql '
pg_args = [ ' -q ' , ' -f ' , os . path . join ( dump_dir , ' dump.sql ' ) ]
else :
# <= 7.0 format (raw pg_dump output)
pg_cmd = ' pg_restore '
pg_args = [ ' --no-owner ' , dump_file ]
2013-01-30 14:56:16 +00:00
2014-03-04 17:56:56 +00:00
args = [ ]
2013-01-30 14:56:16 +00:00
if openerp . tools . config [ ' db_user ' ] :
2014-03-04 17:56:56 +00:00
args . append ( ' --username= ' + openerp . tools . config [ ' db_user ' ] )
2013-01-30 14:56:16 +00:00
if openerp . tools . config [ ' db_host ' ] :
2014-03-04 17:56:56 +00:00
args . append ( ' --host= ' + openerp . tools . config [ ' db_host ' ] )
2013-01-30 14:56:16 +00:00
if openerp . tools . config [ ' db_port ' ] :
2014-03-04 17:56:56 +00:00
args . append ( ' --port= ' + str ( openerp . tools . config [ ' db_port ' ] ) )
args . append ( ' --dbname= ' + db )
pg_args = args + pg_args
if openerp . tools . exec_pg_command ( pg_cmd , * pg_args ) :
2014-03-03 16:08:05 +00:00
raise Exception ( " Couldn ' t restore database " )
2013-01-30 14:56:16 +00:00
2014-03-04 17:56:56 +00:00
registry = openerp . modules . registry . RegistryManager . new ( db )
with registry . cursor ( ) as cr :
if copy :
# if it's a copy of a database, force generation of a new dbuuid
registry [ ' ir.config_parameter ' ] . init ( cr , force = True )
if filestore_path :
filestore_dest = registry [ ' ir.attachment ' ] . _filestore ( cr , SUPERUSER_ID )
shutil . move ( filestore_path , filestore_dest )
if openerp . tools . config [ ' unaccent ' ] :
try :
with cr . savepoint ( ) :
cr . execute ( " CREATE EXTENSION unaccent " )
except psycopg2 . Error :
pass
_logger . info ( ' RESTORE DB: %s ' , db )
2013-01-30 14:56:16 +00:00
def exp_rename ( old_name , new_name ) :
openerp . modules . registry . RegistryManager . delete ( old_name )
openerp . sql_db . close_db ( old_name )
db = openerp . sql_db . db_connect ( ' postgres ' )
2014-01-09 09:32:58 +00:00
with closing ( db . cursor ( ) ) as cr :
2014-03-03 16:08:05 +00:00
cr . autocommit ( True ) # avoid transaction block
2013-01-30 14:56:16 +00:00
try :
cr . execute ( ' ALTER DATABASE " %s " RENAME TO " %s " ' % ( old_name , new_name ) )
2014-01-16 18:54:15 +00:00
_logger . info ( ' RENAME DB: %s -> %s ' , old_name , new_name )
2013-01-30 14:56:16 +00:00
except Exception , e :
_logger . error ( ' RENAME DB: %s -> %s failed: \n %s ' , old_name , new_name , e )
raise Exception ( " Couldn ' t rename database %s to %s : %s " % ( old_name , new_name , e ) )
2014-03-21 15:56:59 +00:00
old_fs = openerp . tools . config . filestore ( old_name )
new_fs = openerp . tools . config . filestore ( new_name )
if os . path . exists ( old_fs ) and not os . path . exists ( new_fs ) :
shutil . move ( old_fs , new_fs )
2013-01-30 14:56:16 +00:00
return True
2014-03-04 17:56:56 +00:00
@openerp.tools.mute_logger ( ' openerp.sql_db ' )
2013-01-30 14:56:16 +00:00
def exp_db_exist ( db_name ) :
## Not True: in fact, check if connection to database is possible. The database may exists
return bool ( openerp . sql_db . db_connect ( db_name ) )
def exp_list ( document = False ) :
if not openerp . tools . config [ ' list_db ' ] and not document :
raise openerp . exceptions . AccessDenied ( )
chosen_template = openerp . tools . config [ ' db_template ' ]
templates_list = tuple ( set ( [ ' template0 ' , ' template1 ' , ' postgres ' , chosen_template ] ) )
db = openerp . sql_db . db_connect ( ' postgres ' )
2014-01-09 09:32:58 +00:00
with closing ( db . cursor ( ) ) as cr :
2013-01-30 14:56:16 +00:00
try :
db_user = openerp . tools . config [ " db_user " ]
if not db_user and os . name == ' posix ' :
import pwd
db_user = pwd . getpwuid ( os . getuid ( ) ) [ 0 ]
if not db_user :
cr . execute ( " select usename from pg_user where usesysid=(select datdba from pg_database where datname= %s ) " , ( openerp . tools . config [ " db_name " ] , ) )
res = cr . fetchone ( )
db_user = res and str ( res [ 0 ] )
if db_user :
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 ) )
else :
cr . execute ( " select datname from pg_database where datname not in %s order by datname " , ( templates_list , ) )
2014-02-20 13:25:48 +00:00
res = [ openerp . tools . ustr ( name ) for ( name , ) in cr . fetchall ( ) ]
2013-01-30 14:56:16 +00:00
except Exception :
res = [ ]
res . sort ( )
return res
def exp_change_admin_password ( new_password ) :
openerp . tools . config [ ' admin_passwd ' ] = new_password
openerp . tools . config . save ( )
return True
def exp_list_lang ( ) :
return openerp . tools . scan_languages ( )
def exp_server_version ( ) :
""" Return the version of the server
Used by the client to verify the compatibility with its own version
"""
2013-03-18 11:02:57 +00:00
return openerp . release . version
2013-01-30 14:56:16 +00:00
def exp_migrate_databases ( databases ) :
for db in databases :
_logger . info ( ' migrate database %s ' , db )
openerp . tools . config [ ' update ' ] [ ' base ' ] = True
2013-03-27 11:10:14 +00:00
openerp . modules . registry . RegistryManager . new ( db , force_demo = False , update_module = True )
2013-01-30 14:56:16 +00:00
return True
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: