cubicweb/cwctl.py
changeset 12526 b78e3472a7d6
parent 12525 234079d86496
child 12549 e2db422752b4
equal deleted inserted replaced
12525:234079d86496 12526:b78e3472a7d6
    23 # *ctl module should limit the number of import to be imported as quickly as
    23 # *ctl module should limit the number of import to be imported as quickly as
    24 # possible (for cubicweb-ctl reactivity, necessary for instance for usable bash
    24 # possible (for cubicweb-ctl reactivity, necessary for instance for usable bash
    25 # completion). So import locally in command helpers.
    25 # completion). So import locally in command helpers.
    26 import sys
    26 import sys
    27 from warnings import filterwarnings
    27 from warnings import filterwarnings
    28 from os import remove, listdir, system, pathsep
    28 from os import listdir, system, pathsep
    29 from os.path import exists, join, isdir
    29 from os.path import exists, join, isdir
    30 
    30 
    31 try:
    31 try:
    32     from os import kill, getpgid
    32     from os import kill, getpgid
    33 except ImportError:
    33 except ImportError:
   460         print('-> instance %s (%s) deleted.' % (appid, confignames))
   460         print('-> instance %s (%s) deleted.' % (appid, confignames))
   461 
   461 
   462 
   462 
   463 # instance commands ########################################################
   463 # instance commands ########################################################
   464 
   464 
   465 class StartInstanceCommand(InstanceCommandFork):
       
   466     """Start the given instances. If no instance is given, start them all.
       
   467 
       
   468     <instance>...
       
   469       identifiers of the instances to start. If no instance is
       
   470       given, start them all.
       
   471     """
       
   472     name = 'start'
       
   473     actionverb = 'started'
       
   474     options = (
       
   475         ("debug",
       
   476          {'short': 'D', 'action': 'store_true',
       
   477           'help': 'start server in debug mode.'}),
       
   478         ("force",
       
   479          {'short': 'f', 'action': 'store_true',
       
   480           'default': False,
       
   481           'help': 'start the instance even if it seems to be already \
       
   482 running.'}),
       
   483         ('profile',
       
   484          {'short': 'P', 'type': 'string', 'metavar': '<stat file>',
       
   485           'default': None,
       
   486           'help': 'profile code and use the specified file to store stats',
       
   487           }),
       
   488         ('loglevel',
       
   489          {'short': 'l', 'type': 'choice', 'metavar': '<log level>',
       
   490           'default': None, 'choices': ('debug', 'info', 'warning', 'error'),
       
   491           'help': 'debug if -D is set, error otherwise',
       
   492           }),
       
   493         ('param',
       
   494          {'short': 'p', 'type': 'named', 'metavar': 'key1:value1,key2:value2',
       
   495           'default': {},
       
   496           'help': 'override <key> configuration file option with <value>.',
       
   497           }),
       
   498     )
       
   499 
       
   500     def start_instance(self, appid):
       
   501         """start the instance's server"""
       
   502         try:
       
   503             import twisted  # noqa
       
   504         except ImportError:
       
   505             msg = (
       
   506                 "Twisted is required by the 'start' command\n"
       
   507                 "Either install it, or use one of the alternative commands:\n"
       
   508                 "- '{ctl} pyramid {appid}'\n"
       
   509                 "- '{ctl} wsgi {appid}'\n")
       
   510             raise ExecutionError(msg.format(ctl='cubicweb-ctl', appid=appid))
       
   511         config = cwcfg.config_for(appid, debugmode=self['debug'])
       
   512         # override config file values with cmdline options
       
   513         config.cmdline_options = self.config.param
       
   514         init_cmdline_log_threshold(config, self['loglevel'])
       
   515         if self['profile']:
       
   516             config.global_set_option('profile', self.config.profile)
       
   517         helper = self.config_helper(config, cmdname='start')
       
   518         pidf = config['pid-file']
       
   519         if exists(pidf) and not self['force']:
       
   520             msg = "%s seems to be running. Remove %s by hand if necessary or use \
       
   521 the --force option."
       
   522             raise ExecutionError(msg % (appid, pidf))
       
   523         if helper.start_server(config) == 1:
       
   524             print('instance %s started' % appid)
       
   525 
       
   526 
       
   527 def init_cmdline_log_threshold(config, loglevel):
   465 def init_cmdline_log_threshold(config, loglevel):
   528     if loglevel is not None:
   466     if loglevel is not None:
   529         config.global_set_option('log-threshold', loglevel.upper())
   467         config.global_set_option('log-threshold', loglevel.upper())
   530         config.init_log(config['log-threshold'], force=True)
   468         config.init_log(config['log-threshold'], force=True)
   531 
       
   532 
       
   533 class StopInstanceCommand(InstanceCommand):
       
   534     """Stop the given instances.
       
   535 
       
   536     <instance>...
       
   537       identifiers of the instances to stop. If no instance is
       
   538       given, stop them all.
       
   539     """
       
   540     name = 'stop'
       
   541     actionverb = 'stopped'
       
   542 
       
   543     def stop_instance(self, appid):
       
   544         """stop the instance's server"""
       
   545         config = cwcfg.config_for(appid)
       
   546         helper = self.config_helper(config, cmdname='stop')
       
   547         helper.poststop()  # do this anyway
       
   548         pidf = config['pid-file']
       
   549         if not exists(pidf):
       
   550             sys.stderr.write("%s doesn't exist.\n" % pidf)
       
   551             return
       
   552         import signal
       
   553         pid = int(open(pidf).read().strip())
       
   554         try:
       
   555             kill(pid, signal.SIGTERM)
       
   556         except Exception:
       
   557             sys.stderr.write("process %s seems already dead.\n" % pid)
       
   558         else:
       
   559             try:
       
   560                 wait_process_end(pid)
       
   561             except ExecutionError as ex:
       
   562                 sys.stderr.write('%s\ntrying SIGKILL\n' % ex)
       
   563                 try:
       
   564                     kill(pid, signal.SIGKILL)
       
   565                 except Exception:
       
   566                     # probably dead now
       
   567                     pass
       
   568                 wait_process_end(pid)
       
   569         try:
       
   570             remove(pidf)
       
   571         except OSError:
       
   572             # already removed by twistd
       
   573             pass
       
   574         print('instance %s stopped' % appid)
       
   575 
       
   576 
       
   577 class RestartInstanceCommand(StartInstanceCommand):
       
   578     """Restart the given instances.
       
   579 
       
   580     <instance>...
       
   581       identifiers of the instances to restart. If no instance is
       
   582       given, restart them all.
       
   583     """
       
   584     name = 'restart'
       
   585     actionverb = 'restarted'
       
   586 
       
   587     def restart_instance(self, appid):
       
   588         StopInstanceCommand(self.logger).stop_instance(appid)
       
   589         self.start_instance(appid)
       
   590 
       
   591 
       
   592 class ReloadConfigurationCommand(RestartInstanceCommand):
       
   593     """Reload the given instances. This command is equivalent to a
       
   594     restart for now.
       
   595 
       
   596     <instance>...
       
   597       identifiers of the instances to reload. If no instance is
       
   598       given, reload them all.
       
   599     """
       
   600     name = 'reload'
       
   601 
       
   602     def reload_instance(self, appid):
       
   603         self.restart_instance(appid)
       
   604 
       
   605 
       
   606 class StatusCommand(InstanceCommand):
       
   607     """Display status information about the given instances.
       
   608 
       
   609     <instance>...
       
   610       identifiers of the instances to status. If no instance is
       
   611       given, get status information about all registered instances.
       
   612     """
       
   613     name = 'status'
       
   614     options = ()
       
   615 
       
   616     @staticmethod
       
   617     def status_instance(appid):
       
   618         """print running status information for an instance"""
       
   619         status = 0
       
   620         for mode in cwcfg.possible_configurations(appid):
       
   621             config = cwcfg.config_for(appid, mode)
       
   622             print('[%s-%s]' % (appid, mode), end=' ')
       
   623             try:
       
   624                 pidf = config['pid-file']
       
   625             except KeyError:
       
   626                 print('buggy instance, pid file not specified')
       
   627                 continue
       
   628             if not exists(pidf):
       
   629                 print("doesn't seem to be running")
       
   630                 status = 1
       
   631                 continue
       
   632             pid = int(open(pidf).read().strip())
       
   633             # trick to guess whether or not the process is running
       
   634             try:
       
   635                 getpgid(pid)
       
   636             except OSError:
       
   637                 print("should be running with pid %s but the process can not be found" % pid)
       
   638                 status = 1
       
   639                 continue
       
   640             print("running with pid %s" % (pid))
       
   641         return status
       
   642 
   469 
   643 
   470 
   644 class UpgradeInstanceCommand(InstanceCommandFork):
   471 class UpgradeInstanceCommand(InstanceCommandFork):
   645     """Upgrade an instance after cubicweb and/or component(s) upgrade.
   472     """Upgrade an instance after cubicweb and/or component(s) upgrade.
   646 
   473 
   672 
   499 
   673         ('no-config-update',
   500         ('no-config-update',
   674          {'short': 'C', 'action': 'store_true',
   501          {'short': 'C', 'action': 'store_true',
   675           'default': False,
   502           'default': False,
   676           'help': 'do NOT update config file if set.'}),
   503           'help': 'do NOT update config file if set.'}),
   677 
       
   678         ('nostartstop',
       
   679          {'short': 'n', 'action': 'store_true',
       
   680           'default': False,
       
   681           'help': 'don\'t try to stop instance before migration and to restart it after.'}),
       
   682 
   504 
   683         ('verbosity',
   505         ('verbosity',
   684          {'short': 'v', 'type': 'int', 'metavar': '<0..2>',
   506          {'short': 'v', 'type': 'int', 'metavar': '<0..2>',
   685           'default': 1,
   507           'default': 1,
   686           'help': "0: no confirmation, 1: only main commands confirmed, 2 ask \
   508           'help': "0: no confirmation, 1: only main commands confirmed, 2 ask \
   705 
   527 
   706     def upgrade_instance(self, appid):
   528     def upgrade_instance(self, appid):
   707         print('\n' + underline_title('Upgrading the instance %s' % appid))
   529         print('\n' + underline_title('Upgrading the instance %s' % appid))
   708         from logilab.common.changelog import Version
   530         from logilab.common.changelog import Version
   709         config = cwcfg.config_for(appid)
   531         config = cwcfg.config_for(appid)
   710         instance_running = exists(config['pid-file'])
       
   711         config.repairing = True  # notice we're not starting the server
   532         config.repairing = True  # notice we're not starting the server
   712         config.verbosity = self.config.verbosity
   533         config.verbosity = self.config.verbosity
   713         set_sources_mode = getattr(config, 'set_sources_mode', None)
   534         set_sources_mode = getattr(config, 'set_sources_mode', None)
   714         if set_sources_mode is not None:
   535         if set_sources_mode is not None:
   715             set_sources_mode(self.config.ext_sources or ('migration',))
   536             set_sources_mode(self.config.ext_sources or ('migration',))
   737             vcconf['cubicweb'] = applcubicwebversion
   558             vcconf['cubicweb'] = applcubicwebversion
   738         else:
   559         else:
   739             applcubicwebversion = vcconf.get('cubicweb')
   560             applcubicwebversion = vcconf.get('cubicweb')
   740         if cubicwebversion > applcubicwebversion:
   561         if cubicwebversion > applcubicwebversion:
   741             toupgrade.append(('cubicweb', applcubicwebversion, cubicwebversion))
   562             toupgrade.append(('cubicweb', applcubicwebversion, cubicwebversion))
   742         # only stop once we're sure we have something to do
       
   743         if instance_running and not self.config.nostartstop:
       
   744             StopInstanceCommand(self.logger).stop_instance(appid)
       
   745         # run cubicweb/componants migration scripts
   563         # run cubicweb/componants migration scripts
   746         if self.config.fs_only or toupgrade:
   564         if self.config.fs_only or toupgrade:
   747             for cube, fromversion, toversion in toupgrade:
   565             for cube, fromversion, toversion in toupgrade:
   748                 print('-> migration needed from %s to %s for %s' % (fromversion, toversion, cube))
   566                 print('-> migration needed from %s to %s for %s' % (fromversion, toversion, cube))
   749             with mih.cnx:
   567             with mih.cnx:
   761             return
   579             return
   762         print()
   580         print()
   763         if helper:
   581         if helper:
   764             helper.postupgrade(repo)
   582             helper.postupgrade(repo)
   765         print('-> instance migrated.')
   583         print('-> instance migrated.')
   766         if instance_running and not self.config.nostartstop:
       
   767             # restart instance through fork to get a proper environment, avoid
       
   768             # uicfg pb (and probably gettext catalogs, to check...)
       
   769             forkcmd = '%s start %s' % (sys.argv[0], appid)
       
   770             status = system(forkcmd)
       
   771             if status:
       
   772                 print('%s exited with status %s' % (forkcmd, status))
       
   773         print()
   584         print()
   774 
   585 
   775     def i18nupgrade(self, config):
   586     def i18nupgrade(self, config):
   776         # handle i18n upgrade:
   587         # handle i18n upgrade:
   777         # * install new languages
   588         # * install new languages
  1035     CWCTL.register(WSGIStartHandler)
   846     CWCTL.register(WSGIStartHandler)
  1036 
   847 
  1037 
   848 
  1038 for cmdcls in (ListCommand,
   849 for cmdcls in (ListCommand,
  1039                CreateInstanceCommand, DeleteInstanceCommand,
   850                CreateInstanceCommand, DeleteInstanceCommand,
  1040                StartInstanceCommand, StopInstanceCommand, RestartInstanceCommand,
       
  1041                ReloadConfigurationCommand, StatusCommand,
       
  1042                UpgradeInstanceCommand,
   851                UpgradeInstanceCommand,
  1043                ListVersionsInstanceCommand,
   852                ListVersionsInstanceCommand,
  1044                ShellCommand,
   853                ShellCommand,
  1045                RecompileInstanceCatalogsCommand,
   854                RecompileInstanceCatalogsCommand,
  1046                ListInstancesCommand, ListCubesCommand,
   855                ListInstancesCommand, ListCubesCommand,