server/serverctl.py
changeset 0 b97547f5f1fa
child 136 ff51a18c66a3
equal deleted inserted replaced
-1:000000000000 0:b97547f5f1fa
       
     1 """cubicweb-ctl commands and command handlers specific to the server.serverconfig
       
     2 
       
     3 :organization: Logilab
       
     4 :copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
       
     5 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
       
     6 """
       
     7 __docformat__ = "restructuredtext en"
       
     8 
       
     9 import os
       
    10 
       
    11 from logilab.common.configuration import REQUIRED, Configuration, ini_format_section
       
    12 
       
    13 from cubicweb import AuthenticationError, ExecutionError, ConfigurationError
       
    14 from cubicweb.toolsutils import Command, CommandHandler, pop_arg, cmd_run, \
       
    15      register_commands, confirm, restrict_perms_to_user
       
    16 from cubicweb.server.serverconfig import ServerConfiguration
       
    17 
       
    18 
       
    19 # utility functions ###########################################################
       
    20 
       
    21 def source_cnx(source, dbname=None, special_privs=False):
       
    22     """open and return a connection to the system database defined in the
       
    23     given server.serverconfig
       
    24     """
       
    25     from getpass import getpass
       
    26     from logilab.common.db import get_connection
       
    27     dbhost = source['db-host']
       
    28     if dbname is None:
       
    29         dbname = source['db-name']
       
    30     driver = source['db-driver']
       
    31     print '**** connecting to %s database %s@%s' % (driver, dbname, dbhost),
       
    32     if not special_privs and source.get('db-user'):
       
    33         user = source['db-user']
       
    34         print 'as', user
       
    35         if source.get('db-password'):
       
    36             password = source['db-password']
       
    37         else:
       
    38             password = getpass('password: ')
       
    39     else:
       
    40         print
       
    41         if special_privs:
       
    42             print 'WARNING'
       
    43             print 'the user will need the following special access rights on the database:'
       
    44             print special_privs
       
    45             print
       
    46         default_user = source.get('db-user', os.environ.get('USER', ''))
       
    47         user = raw_input('user (%r by default): ' % default_user)
       
    48         user = user or default_user
       
    49         if user == source.get('db-user') and source.get('db-password'):
       
    50             password = source['db-password']
       
    51         else:
       
    52             password = getpass('password: ')
       
    53     return get_connection(driver, dbhost, dbname, user, password=password,
       
    54                           port=source.get('db-port'))
       
    55 
       
    56 def system_source_cnx(source, dbms_system_base=False, special_privs=None):
       
    57     """shortcut to get a connextion to the application system database
       
    58     defined in the given config. If <dbms_system_base> is True,
       
    59     connect to the dbms system database instead (for task such as
       
    60     create/drop the application database)
       
    61     """
       
    62     if dbms_system_base:
       
    63         from logilab.common.adbh import get_adv_func_helper
       
    64         system_db = get_adv_func_helper(source['db-driver']).system_database()
       
    65         special_privs = special_privs or 'CREATE/DROP DATABASE'
       
    66         return source_cnx(source, system_db, special_privs=special_privs)
       
    67     return source_cnx(source, special_privs=special_privs)
       
    68 
       
    69 def _db_sys_cnx(source, what, db=None, user=None):
       
    70     """return a connection on the RDMS system table (to create/drop a user
       
    71     or a database
       
    72     """
       
    73     from logilab.common.adbh import get_adv_func_helper
       
    74     special_privs = ''
       
    75     driver = source['db-driver']
       
    76     helper = get_adv_func_helper(driver)
       
    77     if user is not None and helper.users_support:
       
    78         special_privs += '%s USER' % what
       
    79     if db is not None:
       
    80         special_privs += ' %s DATABASE' % what
       
    81     # connect on the dbms system base to create our base
       
    82     cnx = system_source_cnx(source, True, special_privs=special_privs)
       
    83     # disable autocommit (isolation_level(1)) because DROP and
       
    84     # CREATE DATABASE can't be executed in a transaction
       
    85     try:
       
    86         cnx.set_isolation_level(0)
       
    87     except AttributeError:
       
    88         # set_isolation_level() is psycopg specific
       
    89         pass
       
    90     return cnx
       
    91     
       
    92 def generate_sources_file(sourcesfile, sourcescfg, keys=None):
       
    93     """serialize repository'sources configuration into a INI like file
       
    94 
       
    95     the `keys` parameter may be used to sort sections
       
    96     """
       
    97     from cubicweb.server.sources import SOURCE_TYPES
       
    98     if keys is None:
       
    99         keys = sourcescfg.keys()
       
   100     else:
       
   101         for key in sourcescfg:
       
   102             if not key in keys:
       
   103                 keys.append(key)
       
   104     stream = open(sourcesfile, 'w')
       
   105     for uri in keys:
       
   106         sconfig = sourcescfg[uri]
       
   107         if isinstance(sconfig, dict):
       
   108             # get a Configuration object
       
   109             _sconfig = Configuration(options=SOURCE_TYPES[sconfig['adapter']].options)
       
   110             for attr, val in sconfig.items():
       
   111                 if attr == 'uri': 
       
   112                     continue
       
   113                 if attr == 'adapter':
       
   114                     _sconfig.adapter = val
       
   115                 else:
       
   116                     _sconfig.set_option(attr, val)
       
   117             sconfig = _sconfig
       
   118         optsbysect = list(sconfig.options_by_section())
       
   119         assert len(optsbysect) == 1
       
   120         ini_format_section(stream, uri, optsbysect[0][1])
       
   121         if hasattr(sconfig, 'adapter'):
       
   122             print >> stream
       
   123             print >> stream, '# adapter for this source (YOU SHOULD NOT CHANGE THIS)'
       
   124             print >> stream, 'adapter=%s' % sconfig.adapter
       
   125         print >> stream
       
   126 
       
   127 def repo_cnx(config):
       
   128     """return a in-memory repository and a db api connection it"""
       
   129     from cubicweb.dbapi import in_memory_cnx
       
   130     from cubicweb.server.utils import manager_userpasswd
       
   131     try:
       
   132         login = config.sources()['admin']['login']
       
   133         pwd = config.sources()['admin']['password']
       
   134     except KeyError:
       
   135         login, pwd = manager_userpasswd()
       
   136     while True:
       
   137         try:
       
   138             return in_memory_cnx(config, login, pwd)
       
   139         except AuthenticationError:
       
   140             print 'wrong user/password'
       
   141         login, pwd = manager_userpasswd()
       
   142     
       
   143 # repository specific command handlers ########################################
       
   144 
       
   145 class RepositoryCreateHandler(CommandHandler):
       
   146     cmdname = 'create'
       
   147     cfgname = 'repository'
       
   148 
       
   149     def bootstrap(self, cubes, inputlevel=0):
       
   150         """create an application by copying files from the given cube and by
       
   151         asking information necessary to build required configuration files
       
   152         """
       
   153         from cubicweb.server.sources import SOURCE_TYPES
       
   154         config = self.config
       
   155         print 'application\'s repository configuration'
       
   156         print '-' * 72
       
   157         config.input_config('email', inputlevel)
       
   158         if config.pyro_enabled():
       
   159             config.input_config('pyro-server', inputlevel)
       
   160         print
       
   161         print 'repository sources configuration'
       
   162         print '-' * 72
       
   163         sourcesfile = config.sources_file()
       
   164         sconfig = Configuration(options=SOURCE_TYPES['native'].options)
       
   165         sconfig.adapter = 'native'
       
   166         sconfig.input_config(inputlevel=inputlevel)
       
   167         sourcescfg = {'system': sconfig}
       
   168         while raw_input('enter another source [y/N]: ').strip().lower() == 'y':
       
   169             sourcetype = raw_input('source type (%s): ' % ', '.join(SOURCE_TYPES.keys()))
       
   170             sconfig = Configuration(options=SOURCE_TYPES[sourcetype].options)
       
   171             sconfig.adapter = sourcetype
       
   172             sourceuri = raw_input('source uri: ').strip()
       
   173             assert not sourceuri in sourcescfg
       
   174             sconfig.input_config(inputlevel=inputlevel)
       
   175             sourcescfg[sourceuri] = sconfig
       
   176             # module names look like cubes.mycube.themodule
       
   177             sourcecube = SOURCE_TYPES[sourcetype].module.split('.', 2)[1]
       
   178             # if the source adapter is coming from an external component, ensure
       
   179             # it's specified in used cubes
       
   180             if sourcecube != 'cubicweb' and not sourcecube in cubes:
       
   181                 cubes.append(sourcecube)
       
   182         sconfig = Configuration(options=USER_OPTIONS)
       
   183         sconfig.input_config(inputlevel=inputlevel)
       
   184         sourcescfg['admin'] = sconfig
       
   185         generate_sources_file(sourcesfile, sourcescfg, ['admin', 'system'])
       
   186         restrict_perms_to_user(sourcesfile)
       
   187         # remember selected cubes for later initialization of the database
       
   188         config.write_bootstrap_cubes_file(cubes)
       
   189         
       
   190     def postcreate(self):
       
   191         if confirm('do you want to create repository\'s system database?'):
       
   192             cmd_run('db-create', self.config.appid)
       
   193         else:
       
   194             print 'nevermind, you can do it later using the db-create command'
       
   195             
       
   196 USER_OPTIONS =  (
       
   197     ('login', {'type' : 'string',
       
   198                'default': REQUIRED,
       
   199                'help': "cubicweb manager account's login "
       
   200                '(this user will be created)',
       
   201                'inputlevel': 0,
       
   202                }),
       
   203     ('password', {'type' : 'password',
       
   204                   'help': "cubicweb manager account's password",
       
   205                   'inputlevel': 0,
       
   206                   }),
       
   207     )
       
   208 
       
   209 
       
   210 class RepositoryDeleteHandler(CommandHandler):
       
   211     cmdname = 'delete'
       
   212     cfgname = 'repository'
       
   213 
       
   214     def cleanup(self):
       
   215         """remove application's configuration and database"""
       
   216         from logilab.common.adbh import get_adv_func_helper
       
   217         source = self.config.sources()['system']
       
   218         dbname = source['db-name']
       
   219         helper = get_adv_func_helper(source['db-driver'])
       
   220         if confirm('delete database %s ?' % dbname):
       
   221             user = source['db-user'] or None
       
   222             cnx = _db_sys_cnx(source, 'DROP DATABASE', user=user)
       
   223             cursor = cnx.cursor()
       
   224             try:
       
   225                 cursor.execute('DROP DATABASE %s' % dbname)
       
   226                 print 'database %s dropped' % dbname
       
   227                 # XXX should check we are not connected as user
       
   228                 if user and helper.users_support and \
       
   229                        confirm('delete user %s ?' % user, default_is_yes=False):
       
   230                     cursor.execute('DROP USER %s' % user)
       
   231                     print 'user %s dropped' % user
       
   232                 cnx.commit()
       
   233             except:
       
   234                 cnx.rollback()
       
   235                 raise
       
   236 
       
   237     
       
   238 class RepositoryStartHandler(CommandHandler):
       
   239     cmdname = 'start'
       
   240     cfgname = 'repository'
       
   241 
       
   242     def start_command(self, ctlconf, debug):
       
   243         command = ['cubicweb-ctl start-repository ']
       
   244         if debug:
       
   245             command.append('--debug')
       
   246         command.append(self.config.appid)
       
   247         return ' '.join(command)
       
   248         
       
   249 
       
   250 class RepositoryStopHandler(CommandHandler):
       
   251     cmdname = 'stop'
       
   252     cfgname = 'repository'
       
   253 
       
   254     def poststop(self):
       
   255         """if pyro is enabled, ensure the repository is correctly
       
   256         unregistered
       
   257         """
       
   258         if self.config.pyro_enabled():
       
   259             from cubicweb.server.repository import pyro_unregister
       
   260             pyro_unregister(self.config)
       
   261     
       
   262 
       
   263 # repository specific commands ################################################
       
   264 class CreateApplicationDBCommand(Command):
       
   265     """Create the system database of an application (run after 'create').
       
   266     
       
   267     You will be prompted for a login / password to use to connect to
       
   268     the system database.  The given user should have almost all rights
       
   269     on the database (ie a super user on the dbms allowed to create
       
   270     database, users, languages...).
       
   271 
       
   272     <application>
       
   273       the identifier of the application to initialize.
       
   274     """
       
   275     name = 'db-create'
       
   276     arguments = '<application>'
       
   277     
       
   278     options = (
       
   279         ("create-db",
       
   280          {'short': 'c', 'type': "yn", 'metavar': '<y or n>',
       
   281           'default': True,
       
   282           'help': 'create the database (yes by default)'}),
       
   283         )
       
   284     def run(self, args):
       
   285         """run the command with its specific arguments"""
       
   286         from logilab.common.adbh import get_adv_func_helper
       
   287         from indexer import get_indexer
       
   288         appid = pop_arg(args, msg="No application specified !")
       
   289         config = ServerConfiguration.config_for(appid)
       
   290         create_db = self.config.create_db
       
   291         source = config.sources()['system']
       
   292         driver = source['db-driver']
       
   293         helper = get_adv_func_helper(driver)
       
   294         if create_db:
       
   295             # connect on the dbms system base to create our base
       
   296             dbcnx = _db_sys_cnx(source, 'CREATE DATABASE and / or USER')
       
   297             cursor = dbcnx.cursor()
       
   298             try:
       
   299                 if helper.users_support:
       
   300                     user = source['db-user']
       
   301                     if not helper.user_exists(cursor, user) and \
       
   302                            confirm('create db user %s ?' % user, default_is_yes=False):
       
   303                         helper.create_user(source['db-user'], source['db-password'])
       
   304                         print 'user %s created' % user
       
   305                 dbname = source['db-name']
       
   306                 if dbname in helper.list_databases(cursor):
       
   307                     if confirm('DB %s already exists -- do you want to drop it ?' % dbname):
       
   308                         cursor.execute('DROP DATABASE %s' % dbname)
       
   309                     else:
       
   310                         return
       
   311                 if dbcnx.logged_user != source['db-user']:
       
   312                     helper.create_database(cursor, dbname, source['db-user'],
       
   313                                            source['db-encoding'])
       
   314                 else:
       
   315                     helper.create_database(cursor, dbname,
       
   316                                            encoding=source['db-encoding'])
       
   317                 dbcnx.commit()
       
   318                 print 'database %s created' % source['db-name']
       
   319             except:
       
   320                 dbcnx.rollback()
       
   321                 raise
       
   322         cnx = system_source_cnx(source, special_privs='LANGUAGE C') 
       
   323         cursor = cnx.cursor()
       
   324         indexer = get_indexer(driver)
       
   325         indexer.init_extensions(cursor)
       
   326         # postgres specific stuff        
       
   327         if driver == 'postgres':
       
   328             # install plpythonu/plpgsql language if not installed by the cube
       
   329             for extlang in ('plpythonu', 'plpgsql'):
       
   330                 helper.create_language(cursor, extlang)
       
   331         cursor.close()
       
   332         cnx.commit()
       
   333         print 'database for application %s created and necessary extensions installed' % appid
       
   334         print
       
   335         if confirm('do you want to initialize the system database?'):
       
   336             cmd_run('db-init', config.appid)
       
   337         else:
       
   338             print 'nevermind, you can do it later using the db-init command'
       
   339 
       
   340     
       
   341 class InitApplicationCommand(Command):
       
   342     """Initialize the system database of an application (run after 'db-create').
       
   343     
       
   344     You will be prompted for a login / password to use to connect to
       
   345     the system database.  The given user should have the create tables,
       
   346     and grant permissions.
       
   347 
       
   348     <application>
       
   349       the identifier of the application to initialize.
       
   350     """
       
   351     name = 'db-init'
       
   352     arguments = '<application>'
       
   353     
       
   354     options = (
       
   355         ("drop",
       
   356          {'short': 'd', 'action': 'store_true',
       
   357           'default': False,
       
   358           'help': 'insert drop statements to remove previously existant \
       
   359 tables, indexes... (no by default)'}),
       
   360         )
       
   361 
       
   362     def run(self, args):
       
   363         from cubicweb.server import init_repository
       
   364         appid = pop_arg(args, msg="No application specified !")
       
   365         config = ServerConfiguration.config_for(appid)
       
   366         init_repository(config, drop=self.config.drop)
       
   367 
       
   368 
       
   369 class GrantUserOnApplicationCommand(Command):
       
   370     """Grant a database user on a repository system database.
       
   371     
       
   372     <application>
       
   373       the identifier of the application
       
   374     <user>
       
   375       the database's user requiring grant access
       
   376     """
       
   377     name = 'db-grant-user'
       
   378     arguments = '<application> <user>'
       
   379 
       
   380     options = (
       
   381         ("set-owner",
       
   382          {'short': 'o', 'type' : "yn", 'metavar' : '<yes or no>', 
       
   383           'default' : False,
       
   384           'help': 'Set the user as tables owner if yes (no by default).'}
       
   385          ),
       
   386         )
       
   387     def run(self, args):
       
   388         """run the command with its specific arguments"""
       
   389         from cubicweb.server.sqlutils import sqlexec, sqlgrants
       
   390         appid = pop_arg(args, 1, msg="No application specified !")
       
   391         user = pop_arg(args, msg="No user specified !")
       
   392         config = ServerConfiguration.config_for(appid)
       
   393         source = config.sources()['system']
       
   394         set_owner = self.config.set_owner
       
   395         cnx = system_source_cnx(source, special_privs='GRANT')
       
   396         cursor = cnx.cursor()
       
   397         schema = config.load_schema()
       
   398         try:
       
   399             sqlexec(sqlgrants(schema, source['db-driver'], user,
       
   400                               set_owner=set_owner), cursor)
       
   401         except Exception, ex:
       
   402             cnx.rollback()
       
   403             import traceback
       
   404             traceback.print_exc()
       
   405             print 'An error occured:', ex
       
   406         else:
       
   407             cnx.commit()
       
   408             print 'grants given to %s on application %s' % (appid, user)
       
   409 
       
   410 
       
   411     
       
   412 class StartRepositoryCommand(Command):
       
   413     """Start an CubicWeb RQL server for a given application.
       
   414     
       
   415     The server will be accessible through pyro
       
   416 
       
   417     <application>
       
   418       the identifier of the application to initialize.
       
   419     """
       
   420     name = 'start-repository'
       
   421     arguments = '<application>'
       
   422     
       
   423     options = (
       
   424         ("debug",
       
   425          {'short': 'D', 'action' : 'store_true',
       
   426           'help': 'start server in debug mode.'}),
       
   427         )
       
   428 
       
   429     def run(self, args):
       
   430         from cubicweb.server.server import RepositoryServer
       
   431         appid = pop_arg(args, msg="No application specified !")
       
   432         config = ServerConfiguration.config_for(appid)
       
   433         debug = self.config.debug
       
   434         # create the server
       
   435         server = RepositoryServer(config, debug)
       
   436         # go ! (don't daemonize in debug mode)
       
   437         if not debug and server.daemonize(config['pid-file']) == -1:
       
   438             return
       
   439         uid = config['uid']
       
   440         if uid is not None:
       
   441             try:
       
   442                 uid = int(uid)
       
   443             except ValueError:
       
   444                 from pwd import getpwnam
       
   445                 uid = getpwnam(uid).pw_uid
       
   446             os.setuid(uid)
       
   447         server.install_sig_handlers()
       
   448         server.connect(config['host'], 0)
       
   449         server.run()
       
   450 
       
   451 
       
   452 def _remote_dump(host, appid, output, sudo=False):
       
   453     dmpcmd = 'cubicweb-ctl db-dump -o /tmp/%s.dump %s' % (appid, appid)
       
   454     if sudo:
       
   455         dmpcmd = 'sudo %s' % (dmpcmd)
       
   456     dmpcmd = 'ssh -t %s "%s"' % (host, dmpcmd)
       
   457     print dmpcmd
       
   458     if os.system(dmpcmd):
       
   459         raise ExecutionError('Error while dumping the database')
       
   460     if output is None:
       
   461         from mx.DateTime import today
       
   462         date = today().strftime('%Y-%m-%d')
       
   463         output = '%s-%s.dump' % (appid, date)
       
   464     cmd = 'scp %s:/tmp/%s.dump %s' % (host, appid, output)
       
   465     print cmd
       
   466     if os.system(cmd):
       
   467         raise ExecutionError('Error while retrieving the dump')
       
   468     rmcmd = 'ssh -t %s "rm -f /tmp/%s.dump"' % (host, appid)
       
   469     print rmcmd
       
   470     if os.system(rmcmd) and not confirm('an error occured while deleting remote dump. Continue anyway?'):
       
   471         raise ExecutionError('Error while deleting remote dump')
       
   472 
       
   473 def _local_dump(appid, output):
       
   474     config = ServerConfiguration.config_for(appid)
       
   475     # schema=1 to avoid unnecessary schema loading
       
   476     mih = config.migration_handler(connect=False, schema=1)
       
   477     mih.backup_database(output, askconfirm=False)
       
   478 
       
   479 def _local_restore(appid, backupfile, drop):
       
   480     config = ServerConfiguration.config_for(appid)
       
   481     # schema=1 to avoid unnecessary schema loading
       
   482     mih = config.migration_handler(connect=False, schema=1)
       
   483     mih.restore_database(backupfile, drop)
       
   484     repo = mih.repo_connect()
       
   485     # version of the database
       
   486     dbversions = repo.get_versions()
       
   487     mih.shutdown()
       
   488     if not dbversions:
       
   489         print "bad or missing version information in the database, don't upgrade file system"
       
   490         return
       
   491     # version of installed software
       
   492     eversion = dbversions['cubicweb']
       
   493     status = application_status(config, eversion, dbversions)
       
   494     # * database version > installed software
       
   495     if status == 'needsoftupgrade':
       
   496         print "database is using some earlier version than installed software!"
       
   497         print "please upgrade your software and then upgrade the instance"
       
   498         print "using command 'cubicweb-ctl upgrade %s'" % config.appid
       
   499         return
       
   500     # * database version < installed software, an upgrade will be necessary
       
   501     #   anyway, just rewrite vc.conf and warn user he has to upgrade
       
   502     if status == 'needapplupgrade':
       
   503         print "database is using some older version than installed software."
       
   504         print "You'll have to upgrade the instance using command"
       
   505         print "'cubicweb-ctl upgrade %s'" % config.appid
       
   506         return
       
   507     # * database version = installed software, database version = instance fs version
       
   508     #   ok!
       
   509 
       
   510 
       
   511 def application_status(config, cubicwebapplversion, vcconf):
       
   512     cubicwebversion = config.cubicweb_version()
       
   513     if cubicwebapplversion > cubicwebversion:
       
   514         return 'needsoftupgrade'
       
   515     if cubicwebapplversion < cubicwebversion:
       
   516         return 'needapplupgrade'
       
   517     for cube in config.cubes():
       
   518         try:
       
   519             softversion = config.cube_version(cube)
       
   520         except ConfigurationError:
       
   521             print "no cube version information for %s, is the cube installed?" % cube
       
   522             continue
       
   523         try:
       
   524             applversion = vcconf[cube]
       
   525         except KeyError:
       
   526             print "no cube version information for %s in version configuration" % cube
       
   527             continue            
       
   528         if softversion == applversion:
       
   529             continue
       
   530         if softversion > applversion:
       
   531             return 'needsoftupgrade'
       
   532         elif softversion < applversion:
       
   533             return 'needapplupgrade'
       
   534     return None
       
   535     
       
   536 
       
   537 class DBDumpCommand(Command):
       
   538     """Backup the system database of an application.
       
   539     
       
   540     <application>
       
   541       the identifier of the application to backup
       
   542       format [[user@]host:]appname
       
   543     """
       
   544     name = 'db-dump'
       
   545     arguments = '<application>'
       
   546 
       
   547     options = (
       
   548         ("output",
       
   549          {'short': 'o', 'type' : "string", 'metavar' : '<file>', 
       
   550           'default' : None,
       
   551           'help': 'Specify the backup file where the backup will be stored.'}
       
   552          ),
       
   553         ('sudo',
       
   554          {'short': 's', 'action' : 'store_true',
       
   555           'default' : False,
       
   556           'help': 'Use sudo on the remote host.'}
       
   557          ),
       
   558         )
       
   559 
       
   560     def run(self, args):
       
   561         appid = pop_arg(args, 1, msg="No application specified !")
       
   562         if ':' in appid:
       
   563             host, appid = appid.split(':')
       
   564             _remote_dump(host, appid, self.config.output, self.config.sudo)
       
   565         else:
       
   566             _local_dump(appid, self.config.output)
       
   567 
       
   568 
       
   569 class DBRestoreCommand(Command):
       
   570     """Restore the system database of an application.
       
   571     
       
   572     <application>
       
   573       the identifier of the application to restore
       
   574     """
       
   575     name = 'db-restore'
       
   576     arguments = '<application> <backupfile>'
       
   577 
       
   578     options = (
       
   579         ("no-drop",
       
   580          {'short': 'n', 'action' : 'store_true', 
       
   581           'default' : False,
       
   582           'help': 'for some reason the database doesn\'t exist and so '
       
   583           'should not be dropped.'}
       
   584          ),
       
   585         )
       
   586 
       
   587     def run(self, args):
       
   588         appid = pop_arg(args, 1, msg="No application specified !")
       
   589         backupfile = pop_arg(args, msg="No backup file specified !")
       
   590         _local_restore(appid, backupfile, not self.config.no_drop)
       
   591 
       
   592 
       
   593 class DBCopyCommand(Command):
       
   594     """Copy the system database of an application (backup and restore).
       
   595     
       
   596     <src-application>
       
   597       the identifier of the application to backup
       
   598       format [[user@]host:]appname
       
   599 
       
   600     <dest-application>
       
   601       the identifier of the application to restore
       
   602     """
       
   603     name = 'db-copy'
       
   604     arguments = '<src-application> <dest-application>'
       
   605 
       
   606     options = (
       
   607         ("no-drop",
       
   608          {'short': 'n', 'action' : 'store_true', 
       
   609           'default' : False,
       
   610           'help': 'For some reason the database doesn\'t exist and so '
       
   611           'should not be dropped.'}
       
   612          ),
       
   613         ("keep-dump",
       
   614          {'short': 'k', 'action' : 'store_true',
       
   615           'default' : False,
       
   616           'help': 'Specify that the dump file should not be automatically removed.'}
       
   617          ),
       
   618         ('sudo',
       
   619          {'short': 's', 'action' : 'store_true',
       
   620           'default' : False,
       
   621           'help': 'Use sudo on the remote host.'}
       
   622          ),
       
   623         )
       
   624 
       
   625     def run(self, args):
       
   626         import tempfile
       
   627         srcappid = pop_arg(args, 1, msg="No source application specified !")
       
   628         destappid = pop_arg(args, msg="No destination application specified !")
       
   629         output = tempfile.mktemp()
       
   630         if ':' in srcappid:
       
   631             host, srcappid = srcappid.split(':')
       
   632             _remote_dump(host, srcappid, output, self.config.sudo)
       
   633         else:
       
   634             _local_dump(srcappid, output)
       
   635         _local_restore(destappid, output, not self.config.no_drop)
       
   636         if self.config.keep_dump:
       
   637             print 'you can get the dump file at', output
       
   638         else:
       
   639             os.remove(output)
       
   640 
       
   641         
       
   642 class CheckRepositoryCommand(Command):
       
   643     """Check integrity of the system database of an application.
       
   644     
       
   645     <application>
       
   646       the identifier of the application to check
       
   647     """
       
   648     name = 'db-check'
       
   649     arguments = '<application>'
       
   650 
       
   651     options = (
       
   652         ("checks",
       
   653          {'short': 'c', 'type' : "csv", 'metavar' : '<check list>', 
       
   654           'default' : ('entities', 'relations', 'metadata', 'schema', 'text_index'),
       
   655           'help': 'Comma separated list of check to run. By default run all \
       
   656 checks, i.e. entities, relations, text_index and metadata.'}
       
   657          ),
       
   658         
       
   659         ("autofix",
       
   660          {'short': 'a', 'type' : "yn", 'metavar' : '<yes or no>', 
       
   661           'default' : False,
       
   662           'help': 'Automatically correct integrity problems if this option \
       
   663 is set to "y" or "yes", else only display them'}
       
   664          ),
       
   665         ("reindex",
       
   666          {'short': 'r', 'type' : "yn", 'metavar' : '<yes or no>', 
       
   667           'default' : False,
       
   668           'help': 're-indexes the database for full text search if this \
       
   669 option is set to "y" or "yes" (may be long for large database).'}
       
   670          ),
       
   671         
       
   672         )
       
   673 
       
   674     def run(self, args):
       
   675         from cubicweb.server.checkintegrity import check
       
   676         appid = pop_arg(args, 1, msg="No application specified !")
       
   677         config = ServerConfiguration.config_for(appid)
       
   678         repo, cnx = repo_cnx(config)
       
   679         check(repo, cnx,
       
   680               self.config.checks, self.config.reindex, self.config.autofix)
       
   681 
       
   682 
       
   683 class RebuildFTICommand(Command):
       
   684     """Rebuild the full-text index of the system database of an application.
       
   685     
       
   686     <application>
       
   687       the identifier of the application to rebuild
       
   688     """
       
   689     name = 'db-rebuild-fti'
       
   690     arguments = '<application>'
       
   691 
       
   692     options = ()
       
   693 
       
   694     def run(self, args):
       
   695         from cubicweb.server.checkintegrity import reindex_entities
       
   696         appid = pop_arg(args, 1, msg="No application specified !")
       
   697         config = ServerConfiguration.config_for(appid)
       
   698         repo, cnx = repo_cnx(config)
       
   699         session = repo._get_session(cnx.sessionid, setpool=True)
       
   700         reindex_entities(repo.schema, session)
       
   701         cnx.commit()
       
   702 
       
   703     
       
   704 class SynchronizeApplicationSchemaCommand(Command):
       
   705     """Synchronize persistent schema with cube schema.
       
   706         
       
   707     Will synchronize common stuff between the cube schema and the
       
   708     actual persistent schema, but will not add/remove any entity or relation.
       
   709 
       
   710     <application>
       
   711       the identifier of the application to synchronize.
       
   712     """
       
   713     name = 'schema-sync'
       
   714     arguments = '<application>'
       
   715 
       
   716     def run(self, args):
       
   717         appid = pop_arg(args, msg="No application specified !")
       
   718         config = ServerConfiguration.config_for(appid)
       
   719         mih = config.migration_handler()
       
   720         mih.cmd_synchronize_schema()
       
   721 
       
   722 
       
   723 register_commands( (CreateApplicationDBCommand,                   
       
   724                     InitApplicationCommand,
       
   725                     GrantUserOnApplicationCommand,
       
   726                     StartRepositoryCommand,
       
   727                     DBDumpCommand,
       
   728                     DBRestoreCommand,
       
   729                     DBCopyCommand,
       
   730                     CheckRepositoryCommand,
       
   731                     RebuildFTICommand,
       
   732                     SynchronizeApplicationSchemaCommand,
       
   733                     ) )