From a70abb85b9f44a0ddebc30af8015c889c3e29f4b Mon Sep 17 00:00:00 2001 From: vsr Date: Tue, 3 Dec 2013 08:01:13 +0000 Subject: [PATCH] Merge from agr_portmanager_branch_131004 03/12/2013 --- CMakeLists.txt | 6 +- SalomeKERNELConfig.cmake.in | 5 + bin/CMakeLists.txt | 9 + bin/PortManager.py | 185 ++++- bin/killSalomeWithPort.py | 12 + bin/launchConfigureParser.py | 7 + bin/salomeRunner.py | 678 +++++++++---------- bin/searchFreePort.py | 130 ++-- src/LifeCycleCORBA/SALOME_LifeCycleCORBA.cxx | 12 + 9 files changed, 632 insertions(+), 412 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2996e3354..19a87994a 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -63,7 +63,8 @@ OPTION(SALOME_USE_LIBBATCH "Use LibBatch in KERNEL" OFF) OPTION(SALOME_USE_SIMAN "Add SIMAN support" OFF) OPTION(SALOME_PACO_PARALLEL "Build with PACO (implies SALOME_USE_MPI)" OFF) OPTION(SALOME_LAUNCHER_ONLY "Build only the Launcher part" OFF) -MARK_AS_ADVANCED(SALOME_LIGHT_ONLY SALOME_USE_LIBBATCH SALOME_USE_SIMAN SALOME_PACO_PARALLEL SALOME_LAUNCHER_ONLY) +OPTION(SALOME_USE_PORTMANAGER "Add PortManager support" OFF) +MARK_AS_ADVANCED(SALOME_LIGHT_ONLY SALOME_USE_LIBBATCH SALOME_USE_SIMAN SALOME_PACO_PARALLEL SALOME_LAUNCHER_ONLY SALOME_USE_PORTMANAGER) # Required prerequisites # Find "big" prerequisites first - they reference themselves many others @@ -105,6 +106,9 @@ ENDIF() IF(SALOME_PACO_PARALLEL) FIND_PACKAGE(SalomePaco REQUIRED) ENDIF() +IF(SALOME_USE_PORTMANAGER) + ADD_DEFINITIONS(-DWITH_PORTMANAGER) +ENDIF() IF(SALOME_BUILD_TESTS) ENABLE_TESTING() FIND_PACKAGE(SalomeCppUnit) diff --git a/SalomeKERNELConfig.cmake.in b/SalomeKERNELConfig.cmake.in index a5e884660..3077cb592 100644 --- a/SalomeKERNELConfig.cmake.in +++ b/SalomeKERNELConfig.cmake.in @@ -73,6 +73,11 @@ IF(SALOME_USE_SIMAN) LIST(APPEND KERNEL_DEFINITIONS "-DWITH_SIMANIO") ENDIF() +SET(SALOME_USE_PORTMANAGER @SALOME_USE_PORTMANAGER@) +IF(SALOME_USE_PORTMANAGER) + LIST(APPEND KERNEL_DEFINITIONS "-DWITH_PORTMANAGER") +ENDIF() + # Prerequisites: IF(SALOME_KERNEL_BUILD_TESTS) SET_AND_CHECK(CPPUNIT_ROOT_DIR_EXP "@PACKAGE_CPPUNIT_ROOT_DIR@") diff --git a/bin/CMakeLists.txt b/bin/CMakeLists.txt index a562f96f8..b2847db74 100755 --- a/bin/CMakeLists.txt +++ b/bin/CMakeLists.txt @@ -61,4 +61,13 @@ SET(SCRIPTS waitContainers.py waitNS.py ) + +IF(SALOME_USE_PORTMANAGER) + SET(PORTMANAGER_SCRIPTS + PortManager.py + Singleton.py + ) + LIST(APPEND SCRIPTS ${PORTMANAGER_SCRIPTS}) +ENDIF() + SALOME_INSTALL_SCRIPTS("${SCRIPTS}" ${SALOME_INSTALL_SCRIPT_SCRIPTS}) diff --git a/bin/PortManager.py b/bin/PortManager.py index da1e06cd6..0ad853a9b 100644 --- a/bin/PortManager.py +++ b/bin/PortManager.py @@ -27,6 +27,7 @@ import multiprocessing import time import socket +import os import sys import threading import SocketServer @@ -39,6 +40,20 @@ except: import struct import ctypes +import logging +def createLogger(): + logger = logging.getLogger(__name__) +# logger.setLevel(logging.DEBUG) + ch = logging.StreamHandler() + ch.setLevel(logging.DEBUG) + formatter = logging.Formatter("%(levelname)s:%(threadName)s:%(message)s") + ch.setFormatter(formatter) + logger.addHandler(ch) + return logger +# +logger = createLogger() + + if sys.platform == 'win32': import multiprocessing.reduction # make sockets pickable/inheritable @@ -78,6 +93,7 @@ class _PortManager(object): # :TODO: must manage lock owner return port # def releasePort(self, port): + logger.debug("PortManager.releasePort %s"%port) with self.__lock: if port in self.__lockedPorts: self.__lockedPorts.remove(port) @@ -151,9 +167,11 @@ GET_PORT_MSG = "GET_PORT" GET_PREFERED_PORT_MSG = "GET_PREFERED_PORT" RELEASE_PORT_MSG = "RELEASE_PORT" STOP_SERVER_MSG = "STOP_SERVER" +TEST_SERVER_MSG = "TEST_SERVER" GET_PORT_ACK_MSG = "GET_PORT" RELEASE_PORT_ACK_MSG = "RELEASE_PORT" +TEST_SERVER_ACK_MSG = "TEST_SERVER" class _ThreadedTCPRequestHandler(SocketServer.BaseRequestHandler): def handle(self): @@ -175,35 +193,150 @@ class _ThreadedTCPRequestHandler(SocketServer.BaseRequestHandler): pm.releasePort(port) response = "%s" % (RELEASE_PORT_ACK_MSG) _send(self.request, response) - #print "RELEASE_PORT:", port + logger.debug("RELEASE_PORT: %s"%port) if not pm.isBusy(): - #print "Close server" + logger.debug("Close server") + config_file, lock_file = _getConfigurationFilename() + try: + os.remove(config_file) + pmlock.release() + os.remove(lock_file) + except: + pass self.server.shutdown() #print pm elif data == STOP_SERVER_MSG: - #print "Close server" + logger.debug("Close server") self.server.shutdown() + elif data == TEST_SERVER_MSG: + _send(self.request, TEST_SERVER_ACK_MSG) # class _ThreadedTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer): pass -pm_address = ('127.0.0.1', 51843) -def __getServer(address): +#------------------------------------ +# A file locker (Linux only) +import fcntl +class PortManagerLock: + def __init__(self, filename, readonly=False, blocking=True): + self.filename = filename + # This will create it if it does not exist already + logger.debug("Create lock on %s"%filename) + mode = 'w' + if readonly: + mode = 'r' + self.handle = open(filename, mode) + self.handle.seek(0) # go back to beginning of file to read it multiple times + self.__blocking = blocking + + def acquire(self): + mode = fcntl.LOCK_EX + if not self.__blocking: # Raise an IOError exception if file has already been locked + mode = mode | fcntl.LOCK_NB + fcntl.flock(self.handle, mode) + logger.debug("lock acquired %s"%self.__blocking) + + def release(self): + fcntl.flock(self.handle, fcntl.LOCK_UN) + logger.debug("lock released") + + def __del__(self): + if logger: + logger.debug("Close lock file") + self.handle.close() +# +#------------------------------------ + +# Server address has to be shared by different processes, without any common +# ancestor. +# The "simplest" solution is to define it here as a global variable. Unfortunately, +# availability of the corresponding socket is not guaranted at all. If PortManager +# tries to use a socket it does not own, server is not created (it is identified as +# already existing), clients then connect on this socket but message passing +# between clients and server will not work and SALOME launch will crash. +# We can introduce a port verification procedure automatically called by importing +# this module (i.e. when creating the server). This procedure consists in creating +# a client which sends a specific message to the server that has to be tested. And +# loop on port numbers until a free socket is found and associated to a new server. +# +# Another approach is to let Python socket API select a free port number, then store +# it to a file on server host machine in order to be shared with clients. +# The logical part can be defined as follows. When server is started (by importing +# this module), write server port number to a specific file (do not use a temporary +# file name). Each client then read server address from this same file ; if file is +# not nound, it is an error (add appropriate processing). +# Server could also check file existence and try to use the same address as previous +# server in order to avoid opening too many unecessary sockets ; but we need to apply +# the above verification procedure. This processing is not necessary because TCP socket +# timeout will automatically close unused sockets. + +def _getConfigurationFilename(): + omniorbUserPath = os.getenv("OMNIORB_USER_PATH") + + from salome_utils import generateFileName + portmanager_config = generateFileName(omniorbUserPath, + prefix="omniORB", + suffix="PortManager", + extension="cfg", + hidden=True) + lock_file = portmanager_config + "-lock" + return (portmanager_config, lock_file) +# + +def __checkServer(): + while True: + logger.debug("CHECKING SERVER") + status = __newClient(TEST_SERVER_MSG) + if status == TEST_SERVER_ACK_MSG: + break + return (status == TEST_SERVER_ACK_MSG) +# + +def __getServerAddress(readonly=True): + address = ("localhost", 0) + try: + config_file, lock_file = _getConfigurationFilename() + lock = PortManagerLock(config_file, readonly, blocking=True) + lock.acquire() + address = eval(lock.handle.read()) + lock.release() + except (IOError, SyntaxError) as e: + logger.debug("no configuration file") + pass + finally: + return address +# + +def __setServerAddress(address): + config_file, lock_file = _getConfigurationFilename() + lock = PortManagerLock(config_file, readonly=False, blocking=True) + lock.acquire() + logger.debug("setServerAddress: %s"%str(address)) + lock.handle.write(str(address)) + lock.release() +# + +def __getServer(): + address = __getServerAddress(readonly=False) SocketServer.ThreadingTCPServer.allow_reuse_address = True # can be restarted immediately server = _ThreadedTCPServer(address, _ThreadedTCPRequestHandler, False) # Do not automatically bind server.allow_reuse_address = True # Prevent 'cannot bind to address' errors on restart + server.server_bind() # Manually bind, to support allow_reuse_address + __setServerAddress(server.server_address) + server.server_activate() return server # +pmlock = None def __startServer(): - global pm_address try: - server = __getServer(pm_address) - server.server_bind() # Manually bind, to support allow_reuse_address - server.server_activate() - pm_address = server.server_address + config_file, lock_file = _getConfigurationFilename() + global pmlock + pmlock = PortManagerLock(lock_file, readonly=False, blocking=False) + pmlock.acquire() + server = __getServer() # Start a thread with the server -- that thread will then start one # more thread for each request server_thread = threading.Thread(target=server.serve_forever, name="SALOME_PortManager") @@ -211,50 +344,54 @@ def __startServer(): #server_thread.setDaemon(True) server_thread.start() #print "Server loop running in thread:", server_thread.getName() - #print "Server address:", pm_address - #return address except: - #print "Server already started" - #print "Server address:", pm_address - #return pm_address + logger.debug("Server already started") pass # -def __newClient(address, message): +def __newClient(message): + address = __getServerAddress(readonly=True) try: sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - #print "connect client to", address + logger.debug("connect client to %s"%str(address)) sock.connect(address) _send(sock, message) response = _receive(sock) if response.startswith(GET_PORT_ACK_MSG): port = int(response[len(GET_PORT_ACK_MSG)+1:]) - #print "GET_PORT:", port + logger.debug("GET_PORT: %s"%port) return port elif response == RELEASE_PORT_ACK_MSG: - #print "Received: %s" % response + logger.debug("Received: %s" % response) return 0 pass + elif response == TEST_SERVER_ACK_MSG: + logger.debug("Server is ok") + return TEST_SERVER_ACK_MSG + pass sock.close() except socket.error: - #print "Unable to connect to server" + logger.debug("Unable to connect to server") return -1 # def getPort(preferedPort=None): if preferedPort: - return __newClient(pm_address, "%s: %s"%(GET_PREFERED_PORT_MSG,preferedPort)) + return __newClient("%s: %s"%(GET_PREFERED_PORT_MSG,preferedPort)) else: - return __newClient(pm_address, GET_PORT_MSG) + return __newClient(GET_PORT_MSG) # def releasePort(port): - __newClient(pm_address, "%s: %s"%(RELEASE_PORT_MSG,port)) + logger.debug("application asks for releasePort %s"%port) + __newClient("%s: %s"%(RELEASE_PORT_MSG,port)) # def stopServer(): - __newClient(pm_address, STOP_SERVER_MSG) + __newClient(STOP_SERVER_MSG) # # Auto start: unique instance ; no effect if called multiple times __startServer() +logger.debug("Server started... do check...") +assert(__checkServer()) diff --git a/bin/killSalomeWithPort.py b/bin/killSalomeWithPort.py index 84774cef3..71dd45173 100755 --- a/bin/killSalomeWithPort.py +++ b/bin/killSalomeWithPort.py @@ -141,6 +141,12 @@ def shutdownMyPort(port, cleanup=True): """ if not port: return + try: + from PortManager import releasePort + releasePort(port) + except ImportError: + pass + from salome_utils import generateFileName # set OMNIORB_CONFIG variable to the proper file @@ -186,6 +192,12 @@ def killMyPort(port): Parameters: - port - port number """ + try: + import PortManager + PortManager.releasePort(port) + except ImportError: + pass + from salome_utils import getShortHostName, getHostName # try to shutdown session nomally diff --git a/bin/launchConfigureParser.py b/bin/launchConfigureParser.py index bf280c913..0fb63c0d2 100755 --- a/bin/launchConfigureParser.py +++ b/bin/launchConfigureParser.py @@ -923,6 +923,13 @@ def get_env(theAdditionalOptions=[], appname=salomeappname, cfgname=salomecfgnam from searchFreePort import searchFreePort searchFreePort({}) print "port:%s"%(os.environ['NSPORT']) + + try: + import PortManager + PortManager.releasePort(os.environ['NSPORT']) + except ImportError: + pass + sys.exit(0) pass diff --git a/bin/salomeRunner.py b/bin/salomeRunner.py index 859cdaef4..f8b8deb40 100644 --- a/bin/salomeRunner.py +++ b/bin/salomeRunner.py @@ -1,339 +1,339 @@ -import os -import sys -import logging -import ConfigParser - -from parseConfigFile import parseConfigFile -from parseConfigFile import convertEnvFileToConfigFile - -import tempfile -import pickle -import subprocess -import platform - -from salomeLauncherUtils import SalomeRunnerException -from salomeLauncherUtils import getScriptsAndArgs, formatScriptsAndArgs - -def usage(): - #exeName = os.path.splitext(os.path.basename(__file__))[0] - - msg = '''\ -Usage: salome [command] [options] [--config=file1,...,filen] - -Commands: - start Launches SALOME virtual application [DEFAULT] - shell Executes a script under SALOME application environment - connect Connects a Python console to the active SALOME session - killall Kill all SALOME running sessions - info Display some information about SALOME - help Show this message - coffee Yes! SALOME can also make coffee!!" - -Use salome start --help or salome shell --help -to show help on start and shell commands. -''' - - print msg -# - -""" -The SalomeRunner class in an API to configure SALOME environment then -start SALOME using a single python command. - -""" -class SalomeRunner: - """ - Initialize environment from a list of configuration files - identified by their names. - These files should be in appropriate (new .cfg) format. - However you can give old .sh environment files; in this case, - the SalomeRunner class will try to automatically convert them - to .cfg format before setting the environment. - """ - def __init__(self, configFileNames=[]): - #it could be None explicitely (if user use multiples setEnviron...for standalone) - if configFileNames==None: - return - - if len(configFileNames) == 0: - raise SalomeRunnerException("No configuration files given") - - reserved=['PATH', 'LD_LIBRARY_PATH', 'PYTHONPATH', 'MANPATH', 'PV_PLUGIN_PATH'] - for filename in configFileNames: - basename, extension = os.path.splitext(filename) - if extension == ".cfg": - self.__setEnvironmentFromConfigFile(filename, reserved) - elif extension == ".sh": - #new convert procedures, temporary could be use not to be automatically deleted - #temp = tempfile.NamedTemporaryFile(suffix='.cfg', delete=False) - temp = tempfile.NamedTemporaryFile(suffix='.cfg') - try: - convertEnvFileToConfigFile(filename, temp.name, reserved) - self.__setEnvironmentFromConfigFile(temp.name, reserved) - except ConfigParser.ParsingError, e: - self.getLogger().warning("Invalid token found when parsing file: %s\n"%(filename)) - print e - print '\n' - finally: - # Automatically cleans up the file - temp.close() - else: - self.getLogger().warning("Unrecognized extension for configuration file: %s", filename) - # - - def go(self, args): - # Run this module as a script, in order to use appropriate Python interpreter - # according to current path (initialized from environment files). - absoluteAppliPath = os.getenv('ABSOLUTE_APPLI_PATH','') - proc = subprocess.Popen(['python', os.path.join(absoluteAppliPath,"bin","salome","salomeRunner.py"), pickle.dumps(self), pickle.dumps(args)], shell=False, close_fds=True) - proc.wait() - # - - """Append value to PATH environment variable""" - def addToPath(self, value): - self.addToEnviron('PATH', value) - # - - """Append value to LD_LIBRARY_PATH environment variable""" - def addToLdLibraryPath(self, value): - self.addToEnviron('LD_LIBRARY_PATH', value) - # - - """Append value to PYTHONPATH environment variable""" - def addToPythonPath(self, value): - self.addToEnviron('PYTHONPATH', value) - # - - """Set environment variable to value""" - def setEnviron(self, name, value, overwrite=False): - env = os.getenv(name, '') - if env and not overwrite: - self.getLogger().warning("Environment variable already existing (and not overwritten): %s=%s", name, value) - return - - if env: - self.getLogger().warning("Overwriting environment variable: %s=%s", name, value) - - value = os.path.expandvars(value) # expand environment variables - self.getLogger().debug("Set environment variable: %s=%s", name, value) - os.environ[name] = value - # - - """Unset environment variable""" - def unsetEnviron(self, name): - if os.environ.has_key(name): - del os.environ[name] - # - - """Append value to environment variable""" - def addToEnviron(self, name, value, separator=os.pathsep): - if value == '': - return - - value = os.path.expandvars(value) # expand environment variables - self.getLogger().debug("Add to %s: %s", name, value) - env = os.getenv(name, None) - if env is None: - os.environ[name] = value - else: - os.environ[name] = value + separator + env - # - - ################################### - # This begins the private section # - ################################### - - def __parseArguments(self, args): - if len(args) == 0 or args[0].startswith("-"): - return None, args - - command = args[0] - options = args[1:] - - availableCommands = { - 'start' : '_runAppli', - 'shell' : '_runSession', - 'connect' : '_runConsole', - 'killall': '_killAll', - 'info': '_showInfo', - 'help': '_usage', - 'coffee' : '_makeCoffee' - } - - if not command in availableCommands.keys(): - command = "start" - options = args - - return availableCommands[command], options - # - - """ - Run SALOME! - Args consist in a mandatory command followed by optionnal parameters. - See usage for details on commands. - """ - def _getStarted(self, args): - command, options = self.__parseArguments(args) - sys.argv = options - - if command is None: - if args and args[0] in ["-h","--help","help"]: - usage() - sys.exit(0) - # try to default to "start" command - command = "_runAppli" - - try: - res = getattr(self, command)(options) # run appropriate method - return res or (None, None) - except SystemExit, exc: - if exc==0: - sys.exit(0) #catch sys.exit(0) happy end no warning - if exc==1: - self.getLogger().warning("SystemExit 1 in method %s.", command) - sys.exit(1) - except StandardError: - self.getLogger().error("Unexpected error:") - import traceback - traceback.print_exc() - sys.exit(1) - except SalomeRunnerException, e: - self.getLogger().error(e) - sys.exit(1) - # - - def __setEnvironmentFromConfigFile(self, filename, reserved=[]): - unsetVars, configVars, reservedDict = parseConfigFile(filename, reserved) - - # unset variables - for var in unsetVars: - self.unsetEnviron(var) - - # set environment - for reserved in reservedDict: - a = filter(None, reservedDict[reserved]) # remove empty elements - reformattedVals = ':'.join(a) - self.addToEnviron(reserved, reformattedVals) - pass - - for key,val in configVars: - self.setEnviron(key, val, overwrite=True) - pass - - sys.path[:0] = os.getenv('PYTHONPATH','').split(':') - # - - def _runAppli(self, args=[]): - # Initialize SALOME environment - sys.argv = ['runSalome'] + args - import setenv - setenv.main(True) - - import runSalome - runSalome.runSalome() - # - - def _runSession(self, args=[]): - sys.argv = ['runSession'] + args - import runSession - runSession.configureSession(args) - - import setenv - setenv.main(True) - - scriptArgs = getScriptsAndArgs(args) - command = formatScriptsAndArgs(scriptArgs) - if command: - proc = subprocess.Popen(command, shell=True, close_fds=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - return proc.communicate() - else: - absoluteAppliPath = os.getenv('ABSOLUTE_APPLI_PATH','') - cmd = ["/bin/bash", "--rcfile", absoluteAppliPath + "/.bashrc" ] - proc = subprocess.Popen(cmd, shell=False, close_fds=True) - proc.wait() - # - - def _runConsole(self, args=[]): - # Initialize SALOME environment - sys.argv = ['runConsole'] + args - import setenv - setenv.main(True) - - import runConsole - runConsole.connect() - # - - def _killAll(self, args=[]): - from killSalome import killAllPorts - killAllPorts() - # - - def _showInfo(self, args=[]): - print "Running with python", platform.python_version() - self._runAppli(["--version"]) - # - - def _usage(self, unused=[]): - usage() - # - - def _makeCoffee(self, args=[]): - print " (" - print " ) (" - print " ___...(-------)-....___" - print " .-\"\" ) ( \"\"-." - print " .-\'``\'|-._ ) _.-|" - print " / .--.| `\"\"---...........---\"\"` |" - print " / / | |" - print " | | | |" - print " \\ \\ | |" - print " `\\ `\\ | |" - print " `\\ `| |" - print " _/ /\\ /" - print " (__/ \\ /" - print " _..---\"\"` \\ /`\"\"---.._" - print " .-\' \\ / \'-." - print " : `-.__ __.-\' :" - print " : ) \"\"---...---\"\" ( :" - print " \'._ `\"--...___...--\"` _.\'" - print " \\\"\"--..__ __..--\"\"/" - print " \'._ \"\"\"----.....______.....----\"\"\" _.\'" - print " `\"\"--..,,_____ _____,,..--\"\"`" - print " `\"\"\"----\"\"\"`" - sys.exit(0) - # - - # Add the following two methods since logger is not pickable - # Ref: http://stackoverflow.com/questions/2999638/how-to-stop-attributes-from-being-pickled-in-python - def __getstate__(self): - d = dict(self.__dict__) - if hasattr(self, '_logger'): - del d['_logger'] - return d - # - def __setstate__(self, d): - self.__dict__.update(d) # I *think* this is a safe way to do it - # - # Excluding self._logger from pickle operation imply using the following method to access logger - def getLogger(self): - if not hasattr(self, '_logger'): - self._logger = logging.getLogger(__name__) - #self._logger.setLevel(logging.DEBUG) - self._logger.setLevel(logging.ERROR) - return self._logger; - # - -### -import pickle -if __name__ == "__main__": - if len(sys.argv) == 3: - runner = pickle.loads(sys.argv[1]) - args = pickle.loads(sys.argv[2]) - (out, err) = runner._getStarted(args) - if out: - sys.stdout.write(out) - if err: - sys.stderr.write(err) - else: - usage() -# +import os +import sys +import logging +import ConfigParser + +from parseConfigFile import parseConfigFile +from parseConfigFile import convertEnvFileToConfigFile + +import tempfile +import pickle +import subprocess +import platform + +from salomeLauncherUtils import SalomeRunnerException +from salomeLauncherUtils import getScriptsAndArgs, formatScriptsAndArgs + +def usage(): + #exeName = os.path.splitext(os.path.basename(__file__))[0] + + msg = '''\ +Usage: salome [command] [options] [--config=file1,...,filen] + +Commands: + start Launches SALOME virtual application [DEFAULT] + shell Executes a script under SALOME application environment + connect Connects a Python console to the active SALOME session + killall Kill all SALOME running sessions + info Display some information about SALOME + help Show this message + coffee Yes! SALOME can also make coffee!!" + +Use salome start --help or salome shell --help +to show help on start and shell commands. +''' + + print msg +# + +""" +The SalomeRunner class in an API to configure SALOME environment then +start SALOME using a single python command. + +""" +class SalomeRunner: + """ + Initialize environment from a list of configuration files + identified by their names. + These files should be in appropriate (new .cfg) format. + However you can give old .sh environment files; in this case, + the SalomeRunner class will try to automatically convert them + to .cfg format before setting the environment. + """ + def __init__(self, configFileNames=[]): + #it could be None explicitely (if user use multiples setEnviron...for standalone) + if configFileNames==None: + return + + if len(configFileNames) == 0: + raise SalomeRunnerException("No configuration files given") + + reserved=['PATH', 'LD_LIBRARY_PATH', 'PYTHONPATH', 'MANPATH', 'PV_PLUGIN_PATH'] + for filename in configFileNames: + basename, extension = os.path.splitext(filename) + if extension == ".cfg": + self.__setEnvironmentFromConfigFile(filename, reserved) + elif extension == ".sh": + #new convert procedures, temporary could be use not to be automatically deleted + #temp = tempfile.NamedTemporaryFile(suffix='.cfg', delete=False) + temp = tempfile.NamedTemporaryFile(suffix='.cfg') + try: + convertEnvFileToConfigFile(filename, temp.name, reserved) + self.__setEnvironmentFromConfigFile(temp.name, reserved) + except ConfigParser.ParsingError, e: + self.getLogger().warning("Invalid token found when parsing file: %s\n"%(filename)) + print e + print '\n' + finally: + # Automatically cleans up the file + temp.close() + else: + self.getLogger().warning("Unrecognized extension for configuration file: %s", filename) + # + + def go(self, args): + # Run this module as a script, in order to use appropriate Python interpreter + # according to current path (initialized from environment files). + absoluteAppliPath = os.getenv('ABSOLUTE_APPLI_PATH','') + proc = subprocess.Popen(['python', os.path.join(absoluteAppliPath,"bin","salome","salomeRunner.py"), pickle.dumps(self), pickle.dumps(args)], shell=False, close_fds=True) + proc.wait() + # + + """Append value to PATH environment variable""" + def addToPath(self, value): + self.addToEnviron('PATH', value) + # + + """Append value to LD_LIBRARY_PATH environment variable""" + def addToLdLibraryPath(self, value): + self.addToEnviron('LD_LIBRARY_PATH', value) + # + + """Append value to PYTHONPATH environment variable""" + def addToPythonPath(self, value): + self.addToEnviron('PYTHONPATH', value) + # + + """Set environment variable to value""" + def setEnviron(self, name, value, overwrite=False): + env = os.getenv(name, '') + if env and not overwrite: + self.getLogger().warning("Environment variable already existing (and not overwritten): %s=%s", name, value) + return + + if env: + self.getLogger().warning("Overwriting environment variable: %s=%s", name, value) + + value = os.path.expandvars(value) # expand environment variables + self.getLogger().debug("Set environment variable: %s=%s", name, value) + os.environ[name] = value + # + + """Unset environment variable""" + def unsetEnviron(self, name): + if os.environ.has_key(name): + del os.environ[name] + # + + """Append value to environment variable""" + def addToEnviron(self, name, value, separator=os.pathsep): + if value == '': + return + + value = os.path.expandvars(value) # expand environment variables + self.getLogger().debug("Add to %s: %s", name, value) + env = os.getenv(name, None) + if env is None: + os.environ[name] = value + else: + os.environ[name] = value + separator + env + # + + ################################### + # This begins the private section # + ################################### + + def __parseArguments(self, args): + if len(args) == 0 or args[0].startswith("-"): + return None, args + + command = args[0] + options = args[1:] + + availableCommands = { + 'start' : '_runAppli', + 'shell' : '_runSession', + 'connect' : '_runConsole', + 'killall': '_killAll', + 'info': '_showInfo', + 'help': '_usage', + 'coffee' : '_makeCoffee' + } + + if not command in availableCommands.keys(): + command = "start" + options = args + + return availableCommands[command], options + # + + """ + Run SALOME! + Args consist in a mandatory command followed by optionnal parameters. + See usage for details on commands. + """ + def _getStarted(self, args): + command, options = self.__parseArguments(args) + sys.argv = options + + if command is None: + if args and args[0] in ["-h","--help","help"]: + usage() + sys.exit(0) + # try to default to "start" command + command = "_runAppli" + + try: + res = getattr(self, command)(options) # run appropriate method + return res or (None, None) + except SystemExit, exc: + if exc==0: + sys.exit(0) #catch sys.exit(0) happy end no warning + if exc==1: + self.getLogger().warning("SystemExit 1 in method %s.", command) + sys.exit(1) + except StandardError: + self.getLogger().error("Unexpected error:") + import traceback + traceback.print_exc() + sys.exit(1) + except SalomeRunnerException, e: + self.getLogger().error(e) + sys.exit(1) + # + + def __setEnvironmentFromConfigFile(self, filename, reserved=[]): + unsetVars, configVars, reservedDict = parseConfigFile(filename, reserved) + + # unset variables + for var in unsetVars: + self.unsetEnviron(var) + + # set environment + for reserved in reservedDict: + a = filter(None, reservedDict[reserved]) # remove empty elements + reformattedVals = ':'.join(a) + self.addToEnviron(reserved, reformattedVals) + pass + + for key,val in configVars: + self.setEnviron(key, val, overwrite=True) + pass + + sys.path[:0] = os.getenv('PYTHONPATH','').split(':') + # + + def _runAppli(self, args=[]): + # Initialize SALOME environment + sys.argv = ['runSalome'] + args + import setenv + setenv.main(True) + + import runSalome + runSalome.runSalome() + # + + def _runSession(self, args=[]): + sys.argv = ['runSession'] + args + import runSession + runSession.configureSession(args) + + import setenv + setenv.main(True) + + scriptArgs = getScriptsAndArgs(args) + command = formatScriptsAndArgs(scriptArgs) + if command: + proc = subprocess.Popen(command, shell=True, close_fds=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + return proc.communicate() + else: + absoluteAppliPath = os.getenv('ABSOLUTE_APPLI_PATH','') + cmd = ["/bin/bash", "--rcfile", absoluteAppliPath + "/.bashrc" ] + proc = subprocess.Popen(cmd, shell=False, close_fds=True) + proc.wait() + # + + def _runConsole(self, args=[]): + # Initialize SALOME environment + sys.argv = ['runConsole'] + args + import setenv + setenv.main(True) + + import runConsole + runConsole.connect() + # + + def _killAll(self, args=[]): + from killSalome import killAllPorts + killAllPorts() + # + + def _showInfo(self, args=[]): + print "Running with python", platform.python_version() + self._runAppli(["--version"]) + # + + def _usage(self, unused=[]): + usage() + # + + def _makeCoffee(self, args=[]): + print " (" + print " ) (" + print " ___...(-------)-....___" + print " .-\"\" ) ( \"\"-." + print " .-\'``\'|-._ ) _.-|" + print " / .--.| `\"\"---...........---\"\"` |" + print " / / | |" + print " | | | |" + print " \\ \\ | |" + print " `\\ `\\ | |" + print " `\\ `| |" + print " _/ /\\ /" + print " (__/ \\ /" + print " _..---\"\"` \\ /`\"\"---.._" + print " .-\' \\ / \'-." + print " : `-.__ __.-\' :" + print " : ) \"\"---...---\"\" ( :" + print " \'._ `\"--...___...--\"` _.\'" + print " \\\"\"--..__ __..--\"\"/" + print " \'._ \"\"\"----.....______.....----\"\"\" _.\'" + print " `\"\"--..,,_____ _____,,..--\"\"`" + print " `\"\"\"----\"\"\"`" + sys.exit(0) + # + + # Add the following two methods since logger is not pickable + # Ref: http://stackoverflow.com/questions/2999638/how-to-stop-attributes-from-being-pickled-in-python + def __getstate__(self): + d = dict(self.__dict__) + if hasattr(self, '_logger'): + del d['_logger'] + return d + # + def __setstate__(self, d): + self.__dict__.update(d) # I *think* this is a safe way to do it + # + # Excluding self._logger from pickle operation imply using the following method to access logger + def getLogger(self): + if not hasattr(self, '_logger'): + self._logger = logging.getLogger(__name__) + #self._logger.setLevel(logging.DEBUG) + self._logger.setLevel(logging.ERROR) + return self._logger; + # + +### +import pickle +if __name__ == "__main__": + if len(sys.argv) == 3: + runner = pickle.loads(sys.argv[1]) + args = pickle.loads(sys.argv[2]) + (out, err) = runner._getStarted(args) + if out: + sys.stdout.write(out) + if err: + sys.stderr.write(err) + else: + usage() +# diff --git a/bin/searchFreePort.py b/bin/searchFreePort.py index 07540095d..83e528734 100644 --- a/bin/searchFreePort.py +++ b/bin/searchFreePort.py @@ -22,17 +22,52 @@ # See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com # -def searchFreePort(args={}, save_config=1, use_port=None): - """ - Search free port for SALOME session. - Returns first found free port number. - """ - import sys, os, re, shutil +import os +import sys +def __setup_config(nsport, args, save_config): + # + from salome_utils import generateFileName, getHostName + hostname = getHostName() + # + omniorbUserPath = os.getenv("OMNIORB_USER_PATH") + kwargs={} + if omniorbUserPath is not None: + kwargs["with_username"]=True + # + from ORBConfigFile import writeORBConfigFile + omniorb_config, giopsize = writeORBConfigFile(omniorbUserPath, hostname, nsport, kwargs) + args['port'] = os.environ['NSPORT'] + # + if save_config: + last_running_config = generateFileName(omniorbUserPath, prefix="omniORB", + suffix="last", + extension="cfg", + hidden=True, + **kwargs) + os.environ['LAST_RUNNING_CONFIG'] = last_running_config + try: + if sys.platform == "win32": + import shutil + shutil.copyfile(omniorb_config, last_running_config) + else: + try: + if os.access(last_running_config, os.F_OK): + os.remove(last_running_config) + except OSError: + pass + os.symlink(omniorb_config, last_running_config) + pass + pass + except: + pass + # +# + +def searchFreePort_withoutPortManager(args={}, save_config=1, use_port=None): # :NOTE: Under windows: # netstat options -l and -t are unavailable # grep command is unavailable - from subprocess import Popen, PIPE (stdout, stderr) = Popen(['netstat','-an'], stdout=PIPE).communicate() import StringIO @@ -41,6 +76,7 @@ def searchFreePort(args={}, save_config=1, use_port=None): # def portIsUsed(port, data): + import re regObj = re.compile( ".*tcp.*:([0-9]+).*:.*listen", re.IGNORECASE ); for item in data: try: @@ -53,49 +89,11 @@ def searchFreePort(args={}, save_config=1, use_port=None): return False # - def setup_config(nsport): - # - from salome_utils import generateFileName, getHostName - hostname = getHostName() - # - omniorbUserPath = os.getenv("OMNIORB_USER_PATH") - kwargs={} - if omniorbUserPath is not None: - kwargs["with_username"]=True - # - from ORBConfigFile import writeORBConfigFile - omniorb_config, giopsize = writeORBConfigFile(omniorbUserPath, hostname, nsport, kwargs) - args['port'] = os.environ['NSPORT'] - # - if save_config: - last_running_config = generateFileName(omniorbUserPath, prefix="omniORB", - suffix="last", - extension="cfg", - hidden=True, - **kwargs) - os.environ['LAST_RUNNING_CONFIG'] = last_running_config - try: - if sys.platform == "win32": - import shutil - shutil.copyfile(omniorb_config, last_running_config) - else: - try: - if os.access(last_running_config, os.F_OK): - os.remove(last_running_config) - except OSError: - pass - os.symlink(omniorb_config, last_running_config) - pass - pass - except: - pass - # - if use_port: print "Check if port can be used: %d" % use_port, if not portIsUsed(use_port, ports): print "- OK" - setup_config(use_port) + __setup_config(use_port, args, save_config) return else: print "- KO: port is busy" @@ -112,7 +110,7 @@ def searchFreePort(args={}, save_config=1, use_port=None): while 1: if not portIsUsed(NSPORT, ports): print "%s - OK"%(NSPORT) - setup_config(NSPORT) + __setup_config(NSPORT, args, save_config) break print "%s"%(NSPORT), if NSPORT == limit: @@ -123,5 +121,41 @@ def searchFreePort(args={}, save_config=1, use_port=None): NSPORT=NSPORT+1 pass # +# + +def searchFreePort_withPortManager(args={}, save_config=1, use_port=None): + from PortManager import getPort + port = getPort(use_port) - return + if use_port: + print "Check if port can be used: %d" % use_port, + if port == use_port and port != -1: + print "- OK" + __setup_config(use_port, args, save_config) + return + else: + print "- KO: port is busy" + pass + # + print "Searching for a free port for naming service:", + if port == -1: # try again + port = getPort(use_port) + + if port != -1: + print "%s - OK"%(port) + __setup_config(port, args, save_config) + else: + print "Unable to obtain port" +# + +def searchFreePort(args={}, save_config=1, use_port=None): + """ + Search free port for SALOME session. + Returns first found free port number. + """ + try: + import PortManager + searchFreePort_withPortManager(args, save_config, use_port) + except ImportError: + searchFreePort_withoutPortManager(args, save_config, use_port) +# diff --git a/src/LifeCycleCORBA/SALOME_LifeCycleCORBA.cxx b/src/LifeCycleCORBA/SALOME_LifeCycleCORBA.cxx index 1ae97eef7..13634f498 100644 --- a/src/LifeCycleCORBA/SALOME_LifeCycleCORBA.cxx +++ b/src/LifeCycleCORBA/SALOME_LifeCycleCORBA.cxx @@ -620,6 +620,18 @@ void SALOME_LifeCycleCORBA::killOmniNames() MESSAGE(cmd); system( cmd.c_str() ); } + +#ifdef WITH_PORTMANAGER + // shutdown portmanager + if ( !portNumber.empty() ) + { + std::string cmd = ("from PortManager import releasePort; "); + cmd += std::string("releasePort(") + portNumber + "); "; + cmd = std::string("python -c \"") + cmd +"\" > /dev/null 2> /dev/null"; + MESSAGE(cmd); + system( cmd.c_str() ); + } +#endif } //============================================================================= -- 2.39.2