From ba295c1247ebf303c50d73a8cad00ad86348459c Mon Sep 17 00:00:00 2001 From: admin Date: Thu, 17 Jul 2008 11:09:28 +0000 Subject: [PATCH] This commit was generated by cvs2git to create tag 'V4_1_0rc1'. Sprout from BR_Dev_For_4_0 2007-11-08 11:20:28 UTC mkr 'IMP NPAL13547: Checkbox to kill SALOME completely.' Cherrypick from V4_1_0_maintainance 2008-07-17 11:09:26 UTC jfa 'Bug 0019869: Compile KERNEL in Werror mode. Integrating KERNEL_SRC_20080710.patch.': bin/salome_utilities.py doc/salome/salome_application.dox src/Batch/Batch_BatchManager_eClient.cxx src/Batch/Batch_BatchManager_eClient.hxx src/Batch/Batch_BatchManager_eLSF.cxx src/Batch/Batch_BatchManager_eLSF.hxx src/Batch/Batch_BatchManager_ePBS.cxx src/Batch/Batch_BatchManager_ePBS.hxx src/Batch/Batch_FactBatchManager_eClient.cxx src/Batch/Batch_FactBatchManager_eClient.hxx src/Batch/Batch_FactBatchManager_eLSF.cxx src/Batch/Batch_FactBatchManager_eLSF.hxx src/Batch/Batch_FactBatchManager_ePBS.cxx src/Batch/Batch_FactBatchManager_ePBS.hxx src/Batch/Batch_JobInfo_eLSF.cxx src/Batch/Batch_JobInfo_eLSF.hxx src/Batch/Batch_JobInfo_ePBS.cxx src/Batch/Batch_JobInfo_ePBS.hxx src/DSC/DSC_User/Datastream/Calcium/CalciumCxxInterface.hxx src/DSC/DSC_User/Datastream/Calcium/calciumE.h src/Launcher/BatchTest.cxx src/Launcher/BatchTest.hxx src/Launcher/Launcher.cxx src/Launcher/Launcher.hxx src/ResourcesManager/ResourcesManager.cxx src/ResourcesManager/ResourcesManager.hxx Cherrypick from master 2008-03-07 07:43:41 UTC jfa 'Join modifications from BR_Dev_For_4_0 tag V4_1_1.': bin/appliskel/getAppliPath.py bin/waitNS.sh doc/salome/batch.dox doc/salome/install.dox doc/salome/kernel_resources.dox doc/salome/kernel_services.dox doc/salome/main.dox doc/salome/salome_file.dox doc/salome/tui/KERNEL/sources/kernel_about_4.png doc/salome/unittests.dox src/DSC/DSC.dox src/ParallelContainer/SALOME_ParallelContainerProxy_i.cxx src/ParallelContainer/SALOME_ParallelContainerProxy_i.hxx --- bin/appliskel/getAppliPath.py | 35 + bin/salome_utilities.py | 346 +++++++++ bin/waitNS.sh | 9 + doc/salome/batch.dox | 10 + doc/salome/install.dox | 378 ++++++++++ doc/salome/kernel_resources.dox | 559 ++++++++++++++ doc/salome/kernel_services.dox | 236 ++++++ doc/salome/main.dox | 82 +++ doc/salome/salome_application.dox | 377 ++++++++++ doc/salome/salome_file.dox | 123 ++++ .../tui/KERNEL/sources/kernel_about_4.png | Bin 0 -> 134730 bytes doc/salome/unittests.dox | 207 ++++++ src/Batch/Batch_BatchManager_eClient.cxx | 235 ++++++ src/Batch/Batch_BatchManager_eClient.hxx | 73 ++ src/Batch/Batch_BatchManager_eLSF.cxx | 310 ++++++++ src/Batch/Batch_BatchManager_eLSF.hxx | 93 +++ src/Batch/Batch_BatchManager_ePBS.cxx | 296 ++++++++ src/Batch/Batch_BatchManager_ePBS.hxx | 91 +++ src/Batch/Batch_FactBatchManager_eClient.cxx | 48 ++ src/Batch/Batch_FactBatchManager_eClient.hxx | 58 ++ src/Batch/Batch_FactBatchManager_eLSF.cxx | 63 ++ src/Batch/Batch_FactBatchManager_eLSF.hxx | 60 ++ src/Batch/Batch_FactBatchManager_ePBS.cxx | 64 ++ src/Batch/Batch_FactBatchManager_ePBS.hxx | 60 ++ src/Batch/Batch_JobInfo_eLSF.cxx | 103 +++ src/Batch/Batch_JobInfo_eLSF.hxx | 69 ++ src/Batch/Batch_JobInfo_ePBS.cxx | 114 +++ src/Batch/Batch_JobInfo_ePBS.hxx | 69 ++ src/DSC/DSC.dox | 49 ++ .../Calcium/CalciumCxxInterface.hxx | 532 ++++++++++++++ .../DSC_User/Datastream/Calcium/calciumE.h | 401 ++++++++++ src/Launcher/BatchTest.cxx | 689 ++++++++++++++++++ src/Launcher/BatchTest.hxx | 60 ++ src/Launcher/Launcher.cxx | 628 ++++++++++++++++ src/Launcher/Launcher.hxx | 79 ++ .../SALOME_ParallelContainerProxy_i.cxx | 43 ++ .../SALOME_ParallelContainerProxy_i.hxx | 44 ++ src/ResourcesManager/ResourcesManager.cxx | 488 +++++++++++++ src/ResourcesManager/ResourcesManager.hxx | 116 +++ 39 files changed, 7297 insertions(+) create mode 100755 bin/appliskel/getAppliPath.py create mode 100644 bin/salome_utilities.py create mode 100755 bin/waitNS.sh create mode 100644 doc/salome/batch.dox create mode 100644 doc/salome/install.dox create mode 100644 doc/salome/kernel_resources.dox create mode 100644 doc/salome/kernel_services.dox create mode 100644 doc/salome/main.dox create mode 100644 doc/salome/salome_application.dox create mode 100644 doc/salome/salome_file.dox create mode 100644 doc/salome/tui/KERNEL/sources/kernel_about_4.png create mode 100644 doc/salome/unittests.dox create mode 100644 src/Batch/Batch_BatchManager_eClient.cxx create mode 100644 src/Batch/Batch_BatchManager_eClient.hxx create mode 100644 src/Batch/Batch_BatchManager_eLSF.cxx create mode 100644 src/Batch/Batch_BatchManager_eLSF.hxx create mode 100644 src/Batch/Batch_BatchManager_ePBS.cxx create mode 100644 src/Batch/Batch_BatchManager_ePBS.hxx create mode 100644 src/Batch/Batch_FactBatchManager_eClient.cxx create mode 100644 src/Batch/Batch_FactBatchManager_eClient.hxx create mode 100644 src/Batch/Batch_FactBatchManager_eLSF.cxx create mode 100644 src/Batch/Batch_FactBatchManager_eLSF.hxx create mode 100644 src/Batch/Batch_FactBatchManager_ePBS.cxx create mode 100644 src/Batch/Batch_FactBatchManager_ePBS.hxx create mode 100644 src/Batch/Batch_JobInfo_eLSF.cxx create mode 100644 src/Batch/Batch_JobInfo_eLSF.hxx create mode 100644 src/Batch/Batch_JobInfo_ePBS.cxx create mode 100644 src/Batch/Batch_JobInfo_ePBS.hxx create mode 100644 src/DSC/DSC.dox create mode 100644 src/DSC/DSC_User/Datastream/Calcium/CalciumCxxInterface.hxx create mode 100644 src/DSC/DSC_User/Datastream/Calcium/calciumE.h create mode 100644 src/Launcher/BatchTest.cxx create mode 100644 src/Launcher/BatchTest.hxx create mode 100644 src/Launcher/Launcher.cxx create mode 100644 src/Launcher/Launcher.hxx create mode 100644 src/ParallelContainer/SALOME_ParallelContainerProxy_i.cxx create mode 100644 src/ParallelContainer/SALOME_ParallelContainerProxy_i.hxx create mode 100644 src/ResourcesManager/ResourcesManager.cxx create mode 100644 src/ResourcesManager/ResourcesManager.hxx diff --git a/bin/appliskel/getAppliPath.py b/bin/appliskel/getAppliPath.py new file mode 100755 index 000000000..bac311da7 --- /dev/null +++ b/bin/appliskel/getAppliPath.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python + +import os + +def relpath(target, base): + """ Find relative path from base to target + if target== "/local/chris/appli" and base== "/local/chris" the result is appli + if target== /tmp/appli and base /local/chris the result is ../../tmp/appli + """ + target=target.split(os.path.sep) + base=base.split(os.path.sep) + for i in xrange(len(base)): + if base[i] != target[i]: + i=i-1 + #not in base + break + p=['..']*(len(base)-i-1)+target[i+1:] + if p == []: + return '.' + return os.path.join( *p ) + +def set_var(VAR, strpath): + """Set VAR environment variable """ + value = "%r" % strpath + shell = os.getenv('SHELL') + if shell and shell.endswith('csh'): + return "setenv %s %s" % (VAR, value) + else: + return "export %s=%s" % (VAR, value) + + +applipath=relpath(os.path.abspath(os.path.dirname(__file__)),os.path.abspath(os.getenv('HOME'))) + +#print set_var('APPLI', applipath) +print applipath diff --git a/bin/salome_utilities.py b/bin/salome_utilities.py new file mode 100644 index 000000000..b4349cd20 --- /dev/null +++ b/bin/salome_utilities.py @@ -0,0 +1,346 @@ +# Copyright (C) 2005 OPEN CASCADE, CEA, EDF R&D, LEG +# PRINCIPIA R&D, EADS CCR, Lip6, BV, CEDRAT +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License. +# +# This library is distributed in the hope that it will be useful +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# +# --- +# +# File : salome_utilities.py +# Author : Vadim SANDLER, Open CASCADE S.A.S. (vadim.sandler@opencascade.com) +# +# --- + +""" +Set of utility functions used by SALOME python scripts. +""" + +# +# Exported functions +# +__all__ = [ + 'getORBcfgInfo', + 'getHostFromORBcfg', + 'getPortFromORBcfg', + 'getUserName', + 'getHostName', + 'getShortHostName', + 'getAppName', + 'getPortNumber', + 'getTmpDir', + 'generateFileName', + ] + +# --- + +def _try_bool( arg ): + """ + Check if specified parameter represents boolean value and returns its value. + String values like 'True', 'TRUE', 'YES', 'Yes', 'y', 'NO', 'false', 'n', etc + are supported. + If does not represent a boolean, an exception is raised. + """ + import types + if type( arg ) == types.BooleanType : + return arg + elif type( arg ) == types.StringType : + v = str( arg ).lower() + if v in [ "yes", "y", "true" ]: return True + elif v in [ "no", "n", "false" ]: return False + pass + raise Exception("Not boolean value") + +# --- + +def getORBcfgInfo(): + """ + Get omniORB current configuration. + Returns a list of three values: [ orb_version, host_name, port_number ]. + + The information is retrieved from the omniORB configuration file defined + by the OMNIORB_CONFIG environment variable. + If omniORB configuration file can not be accessed, a list of three empty + strings is returned. + """ + import os, re + ret = [ "", "", "" ] + try: + f = open( os.getenv( "OMNIORB_CONFIG" ) ) + lines = f.readlines() + f.close() + regvar = re.compile( "(ORB)?InitRef.*corbaname::(.*):(\d+)\s*$" ) + for l in lines: + try: + m = regvar.match( l ) + if m: + if m.group(1) is None: + ret[0] = "4" + else: + ret[0] = "3" + pass + ret[1] = m.group(2) + ret[2] = m.group(3) + break + pass + except: + pass + pass + pass + except: + pass + return ret + +# --- + +def getHostFromORBcfg(): + """ + Get current omniORB host. + """ + return getORBcfgInfo()[1] +# --- + +def getPortFromORBcfg(): + """ + Get current omniORB port. + """ + return getORBcfgInfo()[2] + +# --- + +def getUserName(): + """ + Get user name: + 1. try USER environment variable + 2. if fails, return 'unknown' as default user name + """ + import os + return os.getenv( "USER", "unknown" ) # 'unknown' is default user name + +# --- + +def getHostName(): + """ + Get host name: + 1. try socket python module gethostname() function + 2. if fails, try HOSTNAME environment variable + 3. if fails, try HOST environment variable + 4. if fails, return 'unknown' as default host name + """ + import os + try: + import socket + host = socket.gethostname() + except: + host = None + pass + if not host: host = os.getenv("HOSTNAME") + if not host: host = os.getenv("HOST") + if not host: host = "unknown" # 'unknown' is default host name + return host + +# --- + +def getShortHostName(): + """ + Get short host name: + 1. try socket python module gethostname() function + 2. if fails, try HOSTNAME environment variable + 3. if fails, try HOST environment variable + 4. if fails, return 'unknown' as default host name + """ + try: + return getHostName().split('.')[0] + except: + pass + return "unknown" # 'unknown' is default host name + +# --- + +def getAppName(): + """ + Get application name: + 1. try APPNAME environment variable + 2. if fails, return 'SALOME' as default application name + """ + import os + return os.getenv( "APPNAME", "SALOME" ) # 'SALOME' is default user name + +# --- + +def getPortNumber(): + """ + Get current naming server port number: + 1. try NSPORT environment variable + 1. if fails, try to parse config file defined by OMNIORB_CONFIG environment variable + 2. if fails, return 2809 as default port number + """ + import os + try: + return int( os.getenv( "NSPORT" ) ) + except: + pass + port = getPortFromORBcfg() + if port is not None: return port + return 2809 # '2809' is default port number + +# --- + +def getTmpDir(): + """ + Get directory to be used for the temporary files. + """ + import os, sys + if sys.platform == "win32": + # for Windows: temporarily using home directory for tmp files; + # to be replaced with TEMP environment variable later... + dir = os.getenv("HOME") + else: + # for Linux: use /tmp/logs/{user} folder + dir = os.path.join( '/tmp', 'logs', getUserName() ) + pass + return dir + +# --- + +def generateFileName( dir, prefix = None, suffix = None, extension = None, + unique = False, separator = "_", hidden = False, **kwargs ): + """ + Generate file name by sepecified parameters. If necessary, file name + can be generated to be unique. + + Parameters: + - dir : directory path + - prefix : file prefix (not added by default) + - suffix : file suffix (not added by default) + - extension : file extension (not added by default) + - unique : if this parameter is True, the unique file name is generated: + in this case, if the file with the generated name already exists + in the directory, an integer suffix is added to the end of the + file name. This parameter is False by default. + - separator : separator of the words ('_' by default) + - hidden : if this parameter is True, the file name is prepended by . (dot) + symbol. This parameter is False by default. + + Other keyword parameters are: + - with_username : 'add user name' flag/option: + * boolean value can be passed to determine user name automatically + * string value to be used as user name + - with_hostname : 'add host name' flag/option: + * boolean value can be passed to determine host name automatically + * string value to be used as host name + - with_port : 'add port number' flag/option: + * boolean value can be passed to determine port number automatically + * string value to be used as port number + - with_app : 'add application name' flag/option: + * boolean value can be passed to determine application name automatically + * string value to be used as application name + All parameters are optional. + """ + supported = [ 'with_username', 'with_hostname', 'with_port', 'with_app' ] + from launchConfigureParser import verbose + filename = [] + # separator + if separator is None: + separator = "" + pass + else: + separator = str( separator ) + pass + # prefix (if specified) + if prefix is not None: + filename.append( str( prefix ) ) + pass + # additional keywords + ### check unsupported parameters + for kw in kwargs: + if kw not in supported and verbose(): + print 'Warning! salome_utilitie.py: generateFileName(): parameter %s is not supported' % kw + pass + pass + ### process supported keywords + for kw in supported: + if kw not in kwargs: continue + ### user name + if kw == 'with_username': + try: + # auto user name ? + if _try_bool( kwargs[kw] ): filename.append( getUserName() ) + pass + except: + # user name given as parameter + filename.append( kwargs[kw] ) + pass + pass + ### host name + elif kw == 'with_hostname': + try: + # auto host name ? + if _try_bool( kwargs[kw] ): filename.append( getShortHostName() ) + pass + except: + # host name given as parameter + filename.append( kwargs[kw] ) + pass + pass + ### port number + elif kw == 'with_port': + try: + # auto port number ? + if _try_bool( kwargs[kw] ): filename.append( str( getPortNumber() ) ) + pass + except: + # port number given as parameter + filename.append( str( kwargs[kw] ) ) + pass + pass + ### application name + elif kw == 'with_app': + try: + # auto application name ? + if _try_bool( kwargs[kw] ): filename.append( getAppName() ) + pass + except: + # application name given as parameter + filename.append( kwargs[kw] ) + pass + pass + pass + # suffix (if specified) + if suffix is not None: + filename.append( str( suffix ) ) + pass + # raise an exception if file name is empty + if not filename: + raise Exception("Empty file name") + # + if extension is not None and extension.startswith("."): extension = extension[1:] + # + import os + name = separator.join( filename ) + if hidden: name = "." + name # add dot for hidden files + if extension: name = name + "." + str( extension ) # add extension if defined + name = os.path.join( dir, name ) + if unique: + # create unique file name + index = 0 + while os.path.exists( name ): + index = index + 1 + name = separator.join( filename ) + separator + str( index ) + if hidden: name = "." + name # add dot for hidden files + if extension: name = name + "." + str( extension ) # add extension if defined + name = os.path.join( dir, name ) + pass + pass + return name diff --git a/bin/waitNS.sh b/bin/waitNS.sh new file mode 100755 index 000000000..3d875f992 --- /dev/null +++ b/bin/waitNS.sh @@ -0,0 +1,9 @@ +#! /bin/sh +status=1 +while [ $status -ne 0 ]; do + ls $HOME/$APPLI/.omniORB_last.cfg >& /dev/null + status=$? + sleep 1 + echo -n "#" +done +./runSession waitNS.py \ No newline at end of file diff --git a/doc/salome/batch.dox b/doc/salome/batch.dox new file mode 100644 index 000000000..7e0e4e29b --- /dev/null +++ b/doc/salome/batch.dox @@ -0,0 +1,10 @@ +/*! + +\page batch_page Batch + + Batch documentation + +*/ + + + diff --git a/doc/salome/install.dox b/doc/salome/install.dox new file mode 100644 index 000000000..8090ff92f --- /dev/null +++ b/doc/salome/install.dox @@ -0,0 +1,378 @@ +/*! + \page INSTALL Installation instructions + +NOT UP TO DATE %SALOME 4 +WORK in PROGRESS, INCOMPLETE DOCUMENT + +You'll find here generic instructions for installing the SALOME2 platform. + +\section Summary + +
    +
  1. \ref S1_install
  2. +
  3. \ref S2_install
  4. +
  5. \ref S3_install
  6. +
  7. \ref S4_install
  8. +
  9. \ref S5_install
  10. +
  11. \ref S6_install
  12. +
  13. \ref S7_install
  14. +
  15. \ref S8_install
  16. +
+ +\section S1_install Quick Overview + +First of all, you have to check (or install if needed) the dependant +software programs on your system. These programs are: + +- common development tools as gcc, automake, autoconf and libtools. +- third party softwares used in SALOME building or runtime process + (python, OCC, VTK, ...) + +Further details can be found in sections [2] and [3]. + +If the dependencies are installed on your system, then you have to set +your shell environment to get access to the software components +(cf. [4]. "Preparing the shell environment"). + +The next step is to install the KERNEL (cf. [5] "Installing KERNEL"): + +\code +$ mkdir +$ mkdir +$ cd +$ ./build_configure +$ cd +$ /configure --prefix= +$ make +$ make install +\endcode + +Then, the %SALOME components GEOM, MED, VISU, ... can be installed +with a similar procedure (cf. [6]). + +Eventually, the platform ccodean be run by executing the shell script +runSalome (cf. [7]). Here, somme additionnal variables have to be set +to describe the %SALOME runtime configuration (_ROOT_DIR, +OMNIORB_CONFIG) + +The following provides you with specific instructions for each step. + + +\section S2_install System configuration + +%SALOME is compiled and tested on differents platforms with native packages: +- Debian sarge +- Mandrake 10.1 +- ... + +If you have another platform, we suggest the following configuration +for building process: + +- gcc-3.3.x or 3.4.x +- automake-1.7 or more (only aclocal is used) +- autoconf-2.59 +- libtool-1.5.6 + +remarks: + +- This is the minimum level of automake, autoconf and libtool, if you need + to compile all the third party softwares (included OpenCascade 5.2.x). + +\section S3_install Third-party dependencies + +The %SALOME platform relies on a set of third-party softwares. The +current version depends on the following list +(versions given here are from Debian Sarge, except OpenCascade, VTK and MED, +which are not Debian packages): + +- CAS-5.2.4 OpenCascade (try binaries,a source patch is needed) +- VTK-4.2.6 VTK 3D-viewer +- PyQt-3.13 Python-Qt Wrapper +- Python-2.3.5 Python interpreter +- SWIG-1.3.24 SWIG library +- boost-1_32_0 C++ library (only include templates are used) +- hdf5-1.6.2 Files Database library +- med-2.2.2 MED Data Format support for file records +- omniORB-4.0.5 ORB used in %SALOME +- qt-x11-free-3.3.3 Qt library +- qwt-4.2 Graph components for Qt +- sip4-4.1.1 langage binding software + +And, in order to build the documentation: + +- doxygen-1.4.2 +- graphviz-2.2.1 + + +Additionnal software may be installed for optional features: + +- netgen4.3 + patch +- tix8.1.4 +- openpbs-2.3.16 +- lsf-??? + +To Do + +- Instructions for installing these software programs can be found in a + special note doc/configuration_examples/install-prerequis. +- Installation shell scripts are also provided. + These scripts have to be adapted to your own configuration. + +- See doc/configuration_examples/* + +In the following, we assume that all the third-party softwares are +installed in the same root directory, named /prerequis. +Then, your file system should probably look like:: + +\code +/prerequis/Python-2.2.2 +/prerequis/omniORB-3.0.5 +/prerequis/qt-x11-free-3.0.5 +... +\endcode + +\section S4_install Preparing the shell environment + +Some variables have to be set to get acces to third-party software +components (include files, executable, library, ...) during building +process and runtime. + +The shell file prerequis.sh, embedded in the KERNEL source package, +provides a template for setting those variables. In this example, all the +softwares are supposed to be installed in the same root directory, +named here INSTALLROOT. + +Copy the prerequis.sh in a working directory and adjust the settings +to your own configuration. To get the shell prepared, just +execute the following command in the building shell: + +\code +$ source prerequis.sh +\endcode + +(we assume here a ksh or bash mode) + + +\section S5_install Installing the KERNEL component + +We use here the notation to specify the source directory +of the KERNEL component. The shell environment is supposed to have +been set (cf. 4). + +Installing the KERNEL from a source package needs three directories: + +- the source directory, denoted here by . + +- the build directory, denoted by in the following. This + directory can't be the same directory as . + +- the install directory, denoted by in the following. This + directory can't be the same directory as or + . + +The installing process is: + +STEP 1: + preparing directories + + create the and the directories: + + \code +$ mkdir +$ mkdir +\endcode + +STEP 2: + build configure script + + go to directory and generate the "configure" script: + + \code +$ cd +$ ./build_configure + \endcode + + If it doesn't work, check your system automake tools as specified in + section [2]. + +STEP 3: + configure the building process + go to the build directory and execute the configuration process:: + + \code +$ cd +$ /configure --prefix= + \endcode + + Note that must be an absolute path. + + When the configure process is complete, check the status of + third-party softwares detection. You should have a status like:: + + \code + --------------------------------------------- + Summary + --------------------------------------------- + Configure + cc : yes + boost : yes + lex_yacc : yes + python : yes + swig : yes + threads : yes + OpenGL : yes + qt : yes + vtk : yes + hdf5 : yes + med2 : yes + omniORB : yes + occ : yes + sip : yes + pyqt : yes + qwt : yes + doxygen : yes + graphviz : no + openpbs : no + lsf : no + Default ORB : omniORB + ---------------------------------------------- + \endcode + +If a software get a status "no", then it's not "seen" in the system: + +- the software is not installed, or +- the shell environment is not set correctly. + +In this example, the software programs graphviz, openpbs and lsf are not +installed (optional for most usages). + + +STEP 4 : + Building the binary files + + Execute make in the directory:: + + \code +$ make + \endcode + +STEP 5: + Installing binary files, scripts and documentation + + Execute install target in the directory:: + + \code +$ make install + \endcode + +\section S6_install Installing the SALOME components + +TInstalling a component is done by following the same +instructions as given for the KERNEL, replacing KERNEL by + (build_configure, configure, make, make install). + +You just have to be aware of the dependencies between components: + +- MED depends on KERNEL +- GEOM depends on KERNEL +- SMESH depends on KERNEL, MED, GEOM +- VISU depends on KERNEL, MED +- SUPERV depends on KERNEL + +For example, installing the component SMESH needs the previous +installation of the KERNEL component, and then the GEOM and MED components. + +The building process uses the variables _ROOT_DIR to +localize the dependant components. The variables must be set to the +install path directory of the components (ex: +KERNEL_ROOT_DIR=). + +In the above example, the three variables KERNEL_ROOT_DIR, +GEOM_ROOT_DIR and MED_ROOT_DIR have to be set before configuring the +building process of the SMESH component (STEP 3). + + +\section S7_install Runtime + +See SALOME_Application to define your own configuration of %SALOME and run it +on one or several computers. This is the recommended way of configuration. + +The following explains the general principles. + +To run the %SALOME platform, the procedure is: + +- set the shell environment to get acces to third-party softwares: + +\code +$ source prerequis.sh +\endcode + +- define the %SALOME configuration by setting the whole set of + variables _ROOT_DIR. Here, you just have to set the + kernel and the components you need:: + + \code +$ export KERNEL_ROOT_DIR= +$ export MED_ROOT_DIR= +$ ... + \endcode + +- define the CORBA configuration file by setting the variable + OMNIORB_CONFIG. This variable must be set to a writable file + path. The file may be arbitrary chosen and doesn't need to exist + before running. We suggest:: + + \code +$ export OMNIORB_CONFIG=$HOME/.omniORB.cfg + \endcode + +- run the %SALOME platform by executing the script runSalome: + + \code +$KERNEL_ROOT_DIR/bin/salome/runSalome + \endcode + +\section S8_install Suggestions and advices + +For convenience or customization, we suggest the following organisation: + +- chose and create a root directory for the %SALOME platform, say + . + +- install the third-party softwares in a sub-directory "prerequis" + +- install the %SALOME components in a sub-directory "SALOME2" + +- make personnal copies of the files prerequis.sh and runSalome in + : + + \code +$ cp /prerequis.sh /. +$ cp /bin/salome/runSalome /. + \endcode + + Edit the file prerequis.sh and adjust it to your own configuration. + +- define the SALOME2 configuration + + This step consists in setting the KERNEL_ROOT_DIR, the whole set of + variables _ROOT_DIR you need, and the OMNIORB_CONFIG + variable. + + We suggest to create a shell file envSalome.sh containing those + settings. Then the configuration consists in loading envSalome.sh in + the runtime shell: + +\code +$ source envSalome.sh +\endcode + +- When installed with this file organisation, running %SALOME is done + with the following shell commands:: + + \code + $ source /prerequis.sh + $ source /envSalome.sh + $ ./runSalome + \endcode +*/ diff --git a/doc/salome/kernel_resources.dox b/doc/salome/kernel_resources.dox new file mode 100644 index 000000000..6401e942b --- /dev/null +++ b/doc/salome/kernel_resources.dox @@ -0,0 +1,559 @@ +/*! + +\page kernel_resources SALOME Kernel resources for developer + +WORK in PROGRESS, INCOMPLETE DOCUMENT + + +\section S1_kernel_res Abstract + +This document describes the development environment for +C++ and Python. Makefiles generation and usage are +introduced in another document: "using the %SALOME +configuration and building system environment". +Development environment is intended here as: trace and +debug macros usage; %SALOME exceptions usage, in C++ and +Python; user CORBA exceptions usage, in C++ and Python, +with and without Graphical User Interface; some general +purpose services such as singleton, used for CORBA +connection and disconnection. + +\section S2_kernel_res Trace and debug Utilities + +During the development process, an execution log is +useful to identify problems. This log contains +messages, variables values, source files names and line +numbers. It is recommended to verify assertions on +variables values and if necessary, to stop the +execution at debug time, in order to validate all parts +of code. + +
    +
  1. +Two modes: debug and release + +The goal of debug mode is to check as many features as +possible during the early stages of the development +process. The purpose of the utilities provided in +%SALOME is to help the developer to add detailed traces +and check variables values, without writing a lot of code. + +When the code is assumed to be valid, the release mode +optimizes execution, in terms of speed, memory, and +display only user level messages. + +But, some informations must always be displayed in both +modes: especially messages concerning environment or +internal errors, with version identification. When an +end user is confronted to such a message, he may refer +to a configuration documentation or send the message to +the people in charge of %SALOME installation, or to the +development team, following the kind of error. +
  2. +
  3. +C++ Macros for trace and debug + +%SALOME provides C++ macros for trace and debug. These +macros are in: + +\code +KERNEL_SRC/src/SALOMELocalTrace/utilities.h +\endcode + +This file must be included in C++ source. Some +macros are activated only in debug mode, others are +always activated. To activate the debug mode, ``_DEBUG_`` +must be defined, which is the case when %SALOME +Makefiles are generated from configure, without +options. When ``_DEBUG_`` is undefined (release mode: +``configure --disable-debug --enable-production``), the +debug mode macros are defined empty (they do nothing). +So, when switching from debug to release, it is +possible (and recommended) to let the macro calls +unchanged in the source. + +All the macros generate trace messages, stored in a +circular buffer pool. %A separate %thread reads the +messages in the buffer pool, and, depending on options +given at %SALOME start, writes the messages on the +standard output, a file, or send them via CORBA, in +case of a multi machine configuration. + +Three informations are systematically added in front of +the information displayed: + +- the %thread number from which the message come from; + +- the name of the source file in which the macros is set; + +- the line number of the source file at which the macro + is set. + +
      +
    1. +Macros defined in debug and release modes +\n +INFOS_COMPILATION + + The C++ macro INFOS_COMPILATION writes on the trace + buffer pool informations about the compiling process: + + - the name of the compiler : g++, KCC, CC, pgCC; + + - the date and the time of the compiling processing process. + + This macro INFOS_COMPILATION does not have any + argument. Moreover, it is defined in both compiling + mode : _DEBUG_ and _RELEASE_. + + Example: + + \code +#include "utilities.h" +int main(int argc , char **argv) +{ + INFOS_COMPILATION; + ... +} +INFOS(str) + \endcode +\n +INFOS + + In both compiling mode _DEBUG_ and _RELEASE_, The C++ + macro INFOS writes on the trace buffer pool %the string + which has been passed in argument by the user. + + Example: + + \code +#include "utilities.h" +int main(int argc , char **argv) +{ + ... + INFOS("NORMAL END OF THE PROCESS"); + return 0; +} + \endcode + + Displays: + + \code +main.cxx [5] : NORMAL END OF THE PROCESS + \endcode +\n +INTERRUPTION(str) + + In both compiling mode _DEBUG_ and _RELEASE_, The C++ + macro INTERRUPTION writes on the trace buffer pool the + %string, with a special ABORT type. When the %thread in + charge of collecting messages finds this message, it + terminates the application, after message treatment. + +IMMEDIATE_ABORT(str) + + In both compiling mode _DEBUG_ and _RELEASE_, The C++ + macro IMMEDIATE_ABORT writes the message str immediately on + standard error and exits the application. Remaining + messages not treated by the message collector %thread + are lost. + +
    2. +
    3. +Macros defined only in debug mode +\n +MESSAGE(str) + + In _DEBUG_ compiling mode only, the C++ macro MESSAGE + writes on the trace buffer pool the %string which has + been passed in argument by the user. In _RELEASE_ + compiling mode, this macro is blank. + + Example: + + \code +#include "utilities.h" +#include + +using namespace std; + +int main(int argc , char **argv) +{ + ... + const char *str = "Salome"; + MESSAGE(str); + ... const string st; + st = "Aster"; + MESSAGE(c_str(st+" and CASTEM")); + return 0; +} + + \endcode + + Displays: + + \code +- Trace main.cxx [8] : Salome +- Trace main.cxx [12] : Aster and CASTEM + \endcode + +\n +BEGIN_OF(func_name) + + In _DEBUG_ compiling mode, The C++ macro BEGIN_OF + appends the %string "Begin of " to the one passed in + argument by the user and displays the result on the + trace buffer pool. In _RELEASE_ compiling mode, this + macro is blank. + + Example: + + \code +#include "utilities.h" +int main(int argc , char **argv) +{ + BEGIN_OF(argv[0]); + return 0; +} + \endcode + + Displays: + + \code + - Trace main.cxx [3] : Begin of a.out + \endcode +\n +END_OF(func_name) + + In _DEBUG_ compiling mode, The C++ macro END_OF appends + the %string "Normal end of " to the one passed in + argument by the user and displays the result on the + trace buffer pool. In _RELEASE_ compiling mode, this + macro is blank. + + Example: + + \code +#include "utilities.h" +int main(int argc , char **argv) +{ + END_OF(argv[0]); + return 0; +} + \endcode + + Displays: + + \code +- Trace main.cxx [4] : Normal end of a.out + \endcode +\n +SCRUTE(var) + + In _DEBUG_ compiling mode, The C++ macro SCRUTE + displays its argument which is an application variable + followed by the value of the variable. In _RELEASE_ + compiling mode, this macro is blank. + + Example: + + \code +#include "utilities.h" +int main(int argc , char **argv) +{ + const int i=999; + if( i > 0 ) SCRUTE(i) ; i=i+1; + return 0; +} + \endcode + + Displays: + + \code +- Trace main.cxx [5] : i=999 + \endcode +\n +ASSERT(condition) + + In _DEBUG_ compiling mode only, The C++ macro ASSERT + checks the expression passed in argument to be not + NULL. If it is NULL the condition is written with the + macro INTERRUPTION (see above). The process exits after + trace of this last message. In _RELEASE_ compiling + mode, this macro is blank. N.B. : if ASSERT is already + defined, this macro is ignored. + + Example: + + \code +#include "utilities.h" +... +const char *ptrS = fonc(); +ASSERT(ptrS!=NULL); +cout << strlen(ptrS); +float table[10]; +int k; +... +ASSERT(k<10); +cout << table[k]; + \endcode + +
    4. +
    +
  4. +
+ +\section S3_kernel_res Exceptions + +
    +
  1. +C++ exceptions: class SALOME_Exception + +
      +
    1. +definition + +The class SALOME_Exception provides a generic method to +send a message, with optional source file name and line +number. This class is intended to serve as a base class +for all kinds of exceptions %SALOME code. All the +exceptions derived from SALOME_Exception could be +handled in a single catch, in which the message +associated to the exception is displayed, or sent to a +log file. + +The class SALOME_Exception inherits its behavior from +the STL class exception. +
    2. +
    3. +usage + +The header %SALOME/src/utils/utils_SALOME_Exception.hxx +must be included in the C++ source, when raised or trapped: + +\code +#include "utils_SALOME_Exception.hxx" +\endcode + +The SALOME_Exception constructor is: + +\code +SALOME_Exception( const char *text, + const char *fileName=0, + const unsigned int lineNumber=0 ); +\endcode + +The exception is raised like this: + +\code +throw SALOME_Exception("my pertinent message"); +\endcode + +or like this: + +\code +throw SALOME_Exception(LOCALIZED("my pertinent message")); +\endcode + +where LOCALIZED is a macro provided with +``utils_SALOME_Exception.hxx`` which gives file name and +line number. + +The exception is handled like this: + +\code + try +{ + ... +} +catch (const SALOME_Exception &ex) +{ + cerr << ex.what() < +
    +
  2. +
  3. +CORBA exceptions + +
      +
    1. +definition + +The idl SALOME_Exception provides a generic CORBA +exception for %SALOME, with an attribute that gives an +exception type,a message, plus optional source file +name and line number. + +This idl is intended to serve for all user CORBA +exceptions raised in %SALOME code, as IDL specification +does not support exception inheritance. So, all the +user CORBA exceptions from %SALOME could be handled in a +single catch. + +The exception types defined in idl are: + + - COMM CORBA communication problem, + + - BAD_PARAM Bad User parameters, + + - INTERNAL_ERROR application level problem (often irrecoverable). + +CORBA system and user exceptions already defined in the +packages used within %SALOME, such as OmniORB +exceptions, must be handled separately. + +
    2. +
    3. +usage +
        +
      1. +CORBA servant, C++ + + The CORBA Server header for SALOME_Exception and a + macro to throw the exception are provided with the + header ``KERNEL_SRC/src/Utils/Utils_CorbaException.hxx``: + + \code +#include "Utils_CorbaException.hxx" + \endcode + + The exception is raised with a macro which appends file + name and line number: + + \code +if (myStudyName.size() == 0) + THROW_SALOME_CORBA_EXCEPTION("No Study Name given", + SALOME::BAD_PARAM); + \endcode + +
      2. +
      3. +CORBA Client, GUI Qt C++ + + NO MORE AVAILABLE in %SALOME 3.x + + The CORBA Client header for SALOME_Exception and a Qt + function header that displays a message box are + provided in: + + ``KERNEL_SRC/src/SALOMEGUI/SALOMEGUI_QtCatchCorbaException.hxx`` + + \code +#include "SALOMEGUI_QtCatchCorbaException.hxx" + \endcode + + %A typical exchange with a CORBA Servant will be: + + \code +try +{ + ... // one ore more CORBA calls +} + +catch (const SALOME::SALOME_Exception & S_ex) +{ + QtCatchCorbaException(S_ex); +} + \endcode + +
      4. +
      5. +CORBA Client, C++, without GUI + + Nothing specific has been provided to the developer + yet. See the idl or the Qt function + SALOMEGUI_QtCatchCorbaException.hxx to see how to get + the information given by the exception %object. + +
      6. +
      +
    4. +
    +
+ +\section S4_kernel_res Miscellaneous tools + +
    +
  1. +Singleton +
      +
    1. +Definition + +%A singleton is an application data which is created and +deleted only once at the end of the application +process. The C++ compiler allows the user to create a +static singleton data before the first executable +statement. They are deleted after the last statement execution. + +The ``SINGLETON_`` template class deals with dynamic +singleton. It is useful for functor objects. For +example, an %object that connects the application to a +system at creation and disconnects the application at deletion. + +
    2. +
    3. +Usage + +To create a single instance of a POINT %object: + +\code +# include "Utils_SINGLETON.hxx" +... +POINT *ptrPoint=SINGLETON_::Instance() ; +assert(ptrPoint!=NULL) ; +\endcode + +No need to delete ptrPoint. Deletion is achieved +automatically at exit. If the user tries to create more +than one singleton by using the class method +SINGLETON_::Instance(), the pointer is returned +with the same value even if this is done in different +functions (threads ?): + +\code +POINT *p1=SINGLETON_::Instance() ; +... +POINT *p2=SINGLETON_::Instance() ; + +assert(p1==p2) +\endcode + +
    4. +
    5. +Design description + +Here are the principles features of the singleton +design: + +- the user creates an %object of class TYPE by using the + class method ``SINGLETON_::Instance()`` which + returns a pointer to the single %object ; + +- to create an %object, ``SINGLETON_::Instance()`` + uses the default constructor of class TYPE ; + +- at the same time, this class method creates a + destructor %object which is added to the generic list + of destructor objects to be executed at the end of + the application (atexit) ; + +- at the end of the application process all the + deletions are performed by the ``Nettoyage()`` C function + which executes the destruction objects end then + deletes the destructions objects themselves ; + +- the ``Nettoyage()`` C function using ``atexit()`` C function + is embedded in a static single %object ``ATEXIT_()``. + +
    6. +
    +
  2. +
+ +*/ diff --git a/doc/salome/kernel_services.dox b/doc/salome/kernel_services.dox new file mode 100644 index 000000000..d363c84cf --- /dev/null +++ b/doc/salome/kernel_services.dox @@ -0,0 +1,236 @@ +/*! + \page KERNEL_Services KERNEL Services for end user (Python interface) + +WORK in PROGRESS, INCOMPLETE DOCUMENT + +In a %SALOME application, distributed components, servers and clients use +the CORBA middleware for comunication. CORBA interfaces are defined via idl +files. All the different CORBA interfaces are available for users in Python, +see CORBA interfaces below. + +For some general purpose services, CORBA interfaces have been encapsulated +in order to provide a simple interface (encapsulation is generally done in +C++ classes, and a Python SWIG interface is also generated from C++, to +ensure a consistent behavior between C++ modules and Python modules or user +script). + +\section S1_kernel_ser General purpose services + +
    +
  1. +%SALOME services access from a Python shell + +See \ref SALOME_Application for detailed instructions to launch a Python +interpreter with full acces to the %SALOME environment and services. + +You can use the embedded Python interpreter in Grahic User Interface, or an +external interpreter, with: + +\code +./runSession +python +\endcode + +In either cases, %SALOME services access is done with: + +\code +import salome +salome.salome_init() +\endcode + +In the embedded interpreter, it is already done, but there is no problem to +do it several times, so it is preferable to add these instructions +systematically in your scripts, to allow them to work in all configurations. + +
  2. +
  3. +Container and component instanciation + +See LifeCycleCORBA for the C++ interface (Python interface obtained with SWIG +is very similar). + +In the following example, a test component provided in KERNEL is launched +in the local container, "FactoryServer", created when %SALOME starts: + +\code +import salome +salome.salome_init() + +import LifeCycleCORBA +lcc = LifeCycleCORBA.LifeCycleCORBA() +obj=lcc.FindOrLoad_Component("FactoryServer","SalomeTestComponent") + +import Engines +comp=obj._narrow(Engines.TestComponent) + +comp.Coucou(1) +\endcode + +The answer is something like: + +\code +'TestComponent_i : L = 1' +\endcode + +The _narrow() instruction is not always mandatory in Python, but sometimes +useful to be sure you have got the right type of %object. Here, Testcomponent +interface is defined in CORBA module Engines. With this example, it works also +without the _narrow() instruction: + +\code + obj.Coucou(1) +\endcode + +In the next example, a component instance is created in a specific Container +defined by it's computer hostname and it's name. Here we use the local +computer. Note that in Utils_Identity, getShortHostName() gives the short +hostname of the computer, without domain suffixes, which is used in %SALOME. +The container process is created here if it does not exists, and a new +component instance is created: + +\code +import salome +salome.salome_init() +import LifeCycleCORBA +lcc = LifeCycleCORBA.LifeCycleCORBA() + +import Utils_Identity +host = Utils_Identity.getShortHostName() + +import Engines +params={} +params['hostname']=host +params['container_name']='myContainer' +comp=lcc.LoadComponent(params,'SalomeTestComponent') +comp.Coucou(1) +\endcode + +If you want to get a list of containers and component instances, client %object +from orbmodule provides a list: + +\code +import orbmodule +clt=orbmodule.client() +clt.showNS() +\endcode + +The list looks like: + +\code +Logger. +ContainerManager.object +Containers.dir + cli70ac.dir + FactoryServerPy.object + SuperVisionContainer.object + FactoryServer.object + FactoryServer.dir + SalomeTestComponent_inst_1.object + myContainer.object + myContainer.dir + SalomeTestComponent_inst_1.object + SalomeTestComponent_inst_2.object +Registry.object +Kernel.dir + ModulCatalog.object + Session.object +Study.dir + Study2.object + extStudy_1.object + extStudy_2.object + extStudy_3.object +myStudyManager.object +SalomeAppEngine.object +\endcode + +
  4. +
  5. +File transfer service + +See SALOME_FileTransferCORBA for the C++ interface (Python interface obtained with +SWIG is very similar). + +The following example shows how to tranfer a file from a remote host to the +client computer. Remote hostname is 'cli76cc', we would like to copy +'tkcvs_8_0_3.tar.gz' from remote to local computer. %A full pathname is +required. %A container is created on remote computer if it does not exist, +to handle the file transfer: + +\code +import salome +salome.salome_init() + +import LifeCycleCORBA +remotefile="/home/prascle/tkcvs_8_0_3.tar.gz" +aFileTransfer=LifeCycleCORBA.SALOME_FileTransferCORBA('cli76cc',remotefile) +localFile=aFileTransfer.getLocalFile() +\endcode + +
  6. +
  7. +CORBA Naming service access + +See SALOME_NamingService for the C++ interface. The Python interface +SALOME_NamingServicePy is not yet derived from the C++ interface and offers +only the most useful functions. + +
  8. +
  9. +Batch services + +See \ref batch_page documentation (in french only). + +
  10. +
+ +\section S2_kernel_ser All IDL Interfaces + +
    +
  1. +Containers and component life cycle, File transfer service + +- Engines : engines CORBA module. +- Engines::Component : generic component interface. All %SALOME components inherit this interface. +- Engines::Container : host for C++ and Python components components instances +- Engines::fileTransfer : agent for file transfer created by a container copy a local file to a distent client +- Engines::fileRef : reference to a file, used by a container for file transfers +- Engines::ContainerManager : unique instance, in charge of container creation on remote computers +- Engines::MPIContainer : an exemple of parallel implementation for containers and components +- Engines::MPIObject + +
  2. +
  3. +Study management + +- SALOMEDS : SALOMEDS CORBA module +- SALOMEDS.idl +- SALOMEDS_Attributes.idl + +
  4. +
  5. +High speed transfer, object life cycle, exceptions, GUI interface... + +- SALOME : %SALOME CORBA module +- SALOME_Comm.idl +- SALOME_GenericObj.idl +- SALOME_Exception +- SALOME_Session.idl + +
  6. +
  7. +Miscelleanous + +- SALOME_ModuleCatalog +- SALOME_RessourcesCatalog +- SALOME_Registry.idl +- Logger.idl + +Other idl for test purposes +\n +- nstest.idl +- SALOME_TestComponent.idl +- SALOME_TestModuleCatalog.idl +- SALOME_TestMPIComponent.idl +- TestNotif.idl + +*/ diff --git a/doc/salome/main.dox b/doc/salome/main.dox new file mode 100644 index 000000000..7515c353c --- /dev/null +++ b/doc/salome/main.dox @@ -0,0 +1,82 @@ +/*! \mainpage SALOME KERNEL Reference Documentation + \image html kernel_about_4.png + + \section S1_main Introduction + + Welcome to the %SALOME KERNEL documentation ! + + Following your kind of usage of %SALOME, you will find some specific + introductory documentation, listed below. + + \section S2_main End user + +
      +
    1. + How to configure a %SALOME application + \n The end user may have to configure his own %SALOME application by selection of a + subset of availables %SALOME modules. He also may want to install his + application on several computers. + See \subpage SALOME_Application to define your own configuration of %SALOME and run it + on one or several computers. This is the recommended way of configuration. +
    2. +
    3. + How to launch %SALOME in a %SALOME application + \n See \ref SALOME_Application. +
    4. +
    5. + How to use KERNEL services in Python scripts + \n The %SALOME KERNEL offers a list of services available in Python. See \subpage KERNEL_Services. +
    6. +
    + + \section S3_main Application Integrator + + Applications integrators are in charge of configuration and installation of + specific %SALOME applications over a local network. Application Integrators + built %SALOME modules binaries from sources tarballs. + +
      +
    1. + How to install %SALOME + \n See \subpage INSTALL for general information on required configuration and + prerequisites, compilation procedure, setting environment principles. +
    2. +
    3. + How to configure a %SALOME application + \n See \ref SALOME_Application to define your own configuration of %SALOME and run it + on one or several computers. This is the recommended way of configuration. +
    4. +
    + + \section S4_main Module maintainer + + Module maintainers are in charge of the development and debug of the %SALOME + modules. Each %SALOME module is stored in a CVS base. CVS bases are organised + in separate branches for developments and debug. All official or development + releases are identified by a CVS tag. + +
      +
    1. + Source code structuration and Unit Tests + \n See \subpage UnitTests for general information on code directories structure, + unit tests associated to the different kind of classes, and how to run + the unit tests. +
    2. +
    3. + Some development utilities + \n See \subpage kernel_resources for information on basic utilities for C++ and Python + development, like trace and debug, exceptions, singleton. +
    4. +
    + + \section S5_main SALOME programming model + + You will find in the next pages informations about + specific points of %SALOME Kernel : + + - \subpage dsc_page : DSC documentation page. + - \subpage salome_file_page : Salome_file documentation page. + - \subpage batch_page : BATCH documentation page. + +*/ + diff --git a/doc/salome/salome_application.dox b/doc/salome/salome_application.dox new file mode 100644 index 000000000..d970338a3 --- /dev/null +++ b/doc/salome/salome_application.dox @@ -0,0 +1,377 @@ +/*! + \page SALOME_Application SALOME Application Concept + + Configuration for one or more computers + + + **WORK in PROGRESS, INCOMPLETE DOCUMENT** + +The following explains how to configure your own application with your list of +modules, how to define and run this application on one or more computers. + +\section S1_sal_appl General principles + +%A %SALOME application is defined by a set of modules (GEOM, SMESH, ASTER...). + +%A %SALOME User can define several %SALOME Applications. These applications are +runnable from the same user account. These applications may share the same +KERNEL and modules. Thus, the application configuration is independant of +KERNEL and must not be put in KERNEL_ROOT_DIR. + +Furthermore, prerequisites may not be the same on all the applications. + +%A %SALOME Session can run on a several computers. + +Binary modules and prerequisites are installed on the different computers. +There is no need to have all the modules on each computer (the minimum is +KERNEL). + +There is no need of standardization or centralised information on the details +of configuration on each computer (PATH, LD_LIBRARY_PATH, environment +variables) provided the application modules are version - compatible. Details +of configuration stay private to the computer, and are held by scripts on each +computer. + +There is no hierarchy between the computers (for example only one master +computer used to launch application). + +The %SALOME user has an account on all the computers. Access between +account@computer is via rsh or ssh and must be configured for use without +password (key exchange for ssh). Account may be different on each +computer. + +\section S2_sal_appl Application Directory + +There are two ways for creation of an application directory, the recommended way is +the second, easier to configure. + +
      +
    1. + First way - references to different module directories + +The script createAppli.sh in ${KERNEL_ROOT_DIR}/bin/salome creates an +application directory with the given path in parameter. The path given, ${APPLI}, is +relative to ${HOME}. + +The directory is only a skeleton, the user has to edit several files to +configure his own application. These files are described after, the list is: + +- env.d/atFirst.sh +- env.d/envProducts.sh +- env.d/envSALOME.sh +- CatalogResources.xml +- SALOMEApp.xml + +
    2. +
    3. + Second and easiest way - one single virtual install directory + +The user must create a %SALOME application configuration file by modifying a +copy of ${KERNEL_ROOT_DIR}/bin/salome/config_appli.xml. +The file describes the list of %SALOME modules used in the application, with +their respective installation path. The configuration file also defines the +path of an existing script which sets the %SALOME prerequisites, +and optionnaly, the path of samples directory (SAMPLES_SRC). +The following command:: + +\code +python /bin/salome/appli_gen.py --prefix= --config= +\endcode + +creates a virtual installation of %SALOME in the application directory ${APPLI} +(bin, lib, doc, share...), with, for each file (executable, script, data, +library, resources...), symbolic links to the actual file. +Note: it is recommended to set the environment for %SALOME prerequisites +before invoking the above command, in order to use the same python as SALOME, +otherwise installation may be wrong + +Providing an existing script for %SALOME prerequisites (the same one +used for modules compilation, or given with the modules installation), the +installation works without further modification for a single computer (unless +some modules needs a special environment not defined in the above script). +For a distributed application (several computers), one must copy and adapt +CatalogResources.xml from ${KERNEL_ROOT_DIR}/bin/salome/appliskel (see below). +
    4. +
    + +\section S3_sal_appl General rules + +Directory ${APPLI} must be created on each computer of the application. +The easiest way is to use the same relative path (to ${HOME}) on each computer. +(Sometimes it is not possible to use the same path everywhere, for instance +when ${HOME} is shared with NFS, so it is possible to define different path +following the computers). + +The ${APPLI} directory contains scripts for environment and runs. Environment +scripts must be configured (by the user) on each computer. All the environment +scripts are in the ${APPLI}/env.d directory. + +The script ${APPLI}/envd sources **all** the files (\*.sh) in ${APPLI}/env.d +in alphanumeric order (after edition, think to remove backup files). the envd +script is used by run scripts. + +
      +
    1. +env.d scripts + +With the first way of installation, each user **must define** his own +configuration for these scripts, following the above rules. +With the virtual installation (second way, above), env.d +scripts are built automatically. + + **The following is only an example proposed by createAppli.sh, (first way of installation) not working as it is**. + +- atFirst.sh + Sets the computer configuration not directly related to %SALOME, + like useful tools, default PATH. + +- envProducts.sh + Sets the %SALOME prerequisites. + +- envSALOME.sh + Sets all the MODULE_ROOT_DIR that can be used in the %SALOME application. + + SALOMEAppConfig is also defined by: + +\code +export SALOMEAppConfig=${HOME}/${APPLI} +\endcode + + where SALOMEAppConfig designates the directory containing SALOMEApp.xml. + Note that ${APPLI} is already defined by the calling scripts when + env.d/envSALOME.sh is sourced. +
    2. +
    3. +User run scripts + +The %SALOME user can use 4 scripts: + +- runAppli + Launches a %SALOME Session + (similar to ${KERNEL_ROOT_DIR}/bin/salome/runSalome but with a different + name to avoid confusions). See parameters below. + +- runSession + Launches a shell script in the %SALOME application environment, with access + to the current (last launched) %SALOME session (naming service), if any. + Without arguments, the script is interactive. With arguments, the script + executes the command in the %SALOME application environment. + +- runConsole + Gives a python console connected to the current %SALOME Session. + It is also possible to use runSession, then python. + +- runTests + Similar to runSession, used for unit testing, but runSession tries to use an + already existing naming service definition from a running session (hostname + and port number), and runTests defines a new configuration for naming service + (new port number). +
    4. +
    5. +%SALOME internal run scripts + +- envd + Sets %SALOME application environment, envd is sourced by other scripts. + +For remote calls, %SALOME uses one script. + +- runRemote.sh + This script is mainly used to launch containers. The first 3 arguments + define the hostname and port userd for naming service, plus a working directory, the remaining + arguments define the command to execute. +
    6. +
    7. +Other configuration files + +- SALOMEApp.xml + This file is similar to the default given + in ${GUI_ROOT_DIR}/share/SALOME/resources/gui + + +- CatalogRessources.xml + This files describes all the computers the application can use. The given + example is minimal and suppose ${APPLI} is the same relative path + to ${HOME}, on all the computers. %A different directory can be set on a + particular computer with a line: + +\code +appliPath="my/specific/path/on/this/computer" +\endcode + +
    8. +
    + +\section S4_sal_appl Examples of use + +
      +
    1. +Launch a %SALOME session with a GUI interface + +Launch is done with a command like:: + +\code +./runAppli --logger +\endcode + +The --logger option means here : collect all the traces from the all the +distributed process, via CORBA, in a single file : logger.log. + +There are a lot of options, a complete list is given by:: + +\code +./runAppli --help +\endcode + +Note that, without argument, runAppli is a non interactive Python application, +and, with arguments, runAppli is an interactive Python interpreter. + +Several options are already defined by default in SALOMEApp.xml files. Optional +arguments given in the command override the SALOMEApp.xml configuration. + +Several sessions can run simultaneously, each session use a different port for +CORBA naming service, so the sessions are totally separated from each other. + +When the GUI is closed, the different %SALOME servers are still running. +
    2. +
    3. +Close a %SALOME session, kill all the servers + +Inside the interactive python interpreter you get when you use runAppli +with arguments, you can kill all the servers of your session with:: + +\code +>>> killLocalPort() +\endcode + +or the servers of all the sessions with:: + +\code +>>> killAllPorts() +\endcode + +If you have no active Python interpreter connected to your session, you can +kill all the %SALOME servers of **all the sessions** on a given computer:: + +\code +./runSession killSalome.py +\endcode + +Remember! it's the same idea in Windows (R) operating system (Microsoft and Windows are either registered trademarks or trademarks of + Microsoft Corporation in the United States and/or other countries) : +use the start menu to stop... + +When you use only one session at a time, you don't need more. + +To kill a given session (when several session are running), one needs +the naming service port number:: + +\code +./runSession killSalomeWithPort 2810 +\endcode + +Note that the port number of the last launched session can be found on Linux, +in the prompt, within a runSession shell (see below). + +It is also possible to get the Naming Service host and port number of +the last launched session with:: + +\code +./runSession NSparam.py +\endcode + +
    4. +
    5. +Launch a %SALOME session without GUI interface + +This is used to launch a %SALOME Python script without GUI +(no GUI %server = SALOME_session_server) + +Example of script (test_session_geom.py): + +\code +import salome_session +salome_session.startSession(modules=["GEOM"]) +import GEOM_usinggeom +raw_input("Press a key and the servers will be killed ...") +\endcode + +This script is run in a non interactive way with:: + +\code +./runSession python test_session_geom.py +\endcode + +All the process are automatically killed when Python is closed +(with SALOME_session delete). +
    6. +
    7. +Add an external Python interpretor to a running session + +It's often easier to develop and try Python scripts outside the GUI embedded +Python interpreter. Imagine, for instance, you are writing a script involving +geometry and mesh modules. +first, launch a %SALOME session with gui, then, on another terminal:: + +\code +./runSession +python +\endcode + +Import %SALOME module. salome_init() without arguments creates a new study +in the running session (note: SALOME_init(n) attachs to a running session whose +studyId is n):: + +\code +import salome +salome.salome_init() +\endcode + +An example of script given with SMESH:: + +\code +import ex01_cube2build +\endcode + +It is possible to connect the GUI interface to the study created in the above +script with the file/connect menu, then browse study and display objects. +Further modifications on study can be done either with GUI or external script +(use refresh popup in GUI %object browser to see study modifications generated +by the external script). **AVOID modifications with GUI when a Python script +is running**. Not all the modules are protected against concurrent actions... +
    8. +
    9. +Different uses of the runSession shell interpreter + +runSession invoked without arguments gives an interactive shell with the full +environment of %SALOME (PATH, LD_LIBRARY_PATH, PYTHONPATH, other variables). +If there are running sessions of the same %SALOME application, runSession +connects to the last launched session (i.e. gets the naming service references +of the session: hostname and port) + +On Linux, the shell prompt (bash) gives information on naming service +references, hostname and port:: + +\code +[NS=cli76cc:2811]prascle@cli76cc:~/SALOME2/Run/Virtual$ +\endcode + +If there is no running session, prompt looks like:: + +\code +[NS=:]prascle@cli76cc:~/SALOME2/Run/Virtual$ +\endcode + +runSession is useful to launch any script or program which needs the complete +%SALOME environment, with or without a session already running. +For instance, to launch the ddd debugger interface on the gui %server, first +launch a %SALOME session with gui, then, on another terminal:: + +\code +./runSession ddd +\endcode + +Then attach to the running SALOME_Session_Server process. +
    10. +
    + +*/ diff --git a/doc/salome/salome_file.dox b/doc/salome/salome_file.dox new file mode 100644 index 000000000..dffb378b9 --- /dev/null +++ b/doc/salome/salome_file.dox @@ -0,0 +1,123 @@ +/*! + +\page salome_file_page Salome_file + +This page introduces the Salome_file feature. Salome_file is based on the +SALOME_FileTransfer. It extends it to enable a higher model for managing files into +%SALOME applications. + +\section S1_Salome_file Principles + +Salome_file is a CORBA %object. It's role is to managed many system files. When a Salome_file +is created, no files are managed. Then, files are added using Salome_file interface. %A file is represented +by a name and a path. + +There is two different cases when a file is added : + +- Local file : the file added exists or it will be created by the user with the path and the name used in +its registration. +- Distributed file : the file added exists into a distributed localization. + +To be able to get a distributed file, the Salome_file has to be connected with an another Salome_file that +managed this file. This distributed Salome_file could be located into a distributed resource. + +\section S2_Salome_file Simple example + +This section shows a simple example of the use of Salome_file. The objective is to create +two Salome_file; one is managing a local file, the other is managing a distributed file. +Then, these Salome_files are connected to enable the copy of the real file gbetween the two Salome_files. + +Firstly, two Salome_files are created : + +\code +#include "Salome_file_i.hxx" + +int main (int argc, char * argv[]) +{ + Salome_file_i file_source; + Salome_file_i file_dest; + +\endcode + +Secondly, the real files are registered into the Salome_files. + +\code + file_source.setLocalFile("/bin/cat"); + file_dest.setDistributedFile("/tmp/cat_copy"); +\endcode + +Thirdly, we connect the destination file with the source file : + +\code + file_dest.connect(file_source); +\endcode + +Finally, the file is sended using Salome_file interface. + +\code + file_dest.recvFiles(); + // Status check + state = file_dest.getSalome_fileState(); + print_state(state); // You have to implement this function. +}; +\endcode + +\section S3_Salome_file Advanced example + +This advanced example illustrates a part of the Salome_file API dedicated +for situations where multiple files are managed. + +This is the situation : + +\code + +#include "Salome_file_i.hxx" + +int main (int argc, char * argv[]) +{ + Salome_file_i file_source_a; + Salome_file_i file_source_b; + Salome_file_i file_dest; + + file_source_a.setLocalFile("/bin/cat"); + file_source_a.setLocalFile("/bin/ls"); + + file_source_b.setLocalFile("/bin/echo"); + file_source_b.setLocalFile("/bin/cp"); + + file_dest.setDistributedFile("/tmp/cat_copy"); + file_dest.setDistributedFile("/tmp/echo_copy"); +\endcode + +There is two problems in this case. + +The first problem is in the file_dest Salome_file, there is two files. If +the method connect is used, the Salome_file cannot know if the reference is for cat_copy or +echo_copy. Indeed echo_copy could be provided by another Salome_file that for cat_copy. + +The second problem comes from the two files of file_source_a Salome_file. Indeed when connect is used, +there is no information about the choice of the source file into the source Salome_file. For +cat_copy, did the used want cat or echo ? + +To avoid these cases, Salome_file API provides advanced methods : + +\code + file_dest.connectDistributedFile("cat_copy", file_source_a); + file_dest.setDistributedSourceFile("cat_copy", "cat"); + + file_dest.connectDistributedFile("cat_echo", file_source_b); + file_dest.setDistributedSourceFile("cat_echo", "echo"); + + file_dest.recvFiles(); + // Status check + state = file_dest.getSalome_fileState(); + print_state(state); // You have to implement this function. +}; +\endcode + +\section S3_Salome_file Using Salome_file into %SALOME services + +Currently you can't use Salome_file into YACS schema. In the next version of %SALOME, +files ports will be available to connect output files to input files. + +*/ diff --git a/doc/salome/tui/KERNEL/sources/kernel_about_4.png b/doc/salome/tui/KERNEL/sources/kernel_about_4.png new file mode 100644 index 0000000000000000000000000000000000000000..5c9c09a795758f733b327c4e39d9d7d80ad6943b GIT binary patch literal 134730 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

    KVHX9C@uxw!Yt;J6lmZ@Z@ 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

    eTAqSYsScu0q%7_n~s*`{vm1A?zx(1UW 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 in PROGRESS, INCOMPLETE DOCUMENT + +You will find here general information on code directories structure, +unit tests associated to the different kind of classes, and how to run +the unit tests. + +\section S1_unit SALOME KERNEL source code structuration + +

      +
    1. General structure of KERNEL_SRC + +- KERNEL_SRC : + Some README files and configuration tools for build + +- KERNEL_SRC/adm_local : + Part of the configuration files, other modules have a directory with the + same name. Not used in KERNEL. + +- KERNEL_SRC/bin : + Python and shell scripts used at run time. + Kit to install a %SALOME Application. + +- KERNEL_SRC/doc : + Kit for KERNEL end user documentation production: + public interfaces, Python, CORBA. + Integrator and Developper documentation. + +- KERNEL_SRC/idl : + All CORBA interfaces from KERNEL are regrouped here. + +- KERNEL_SRC/resources : + Configuration files for servers (examples). + Interfaces definitions for KERNEL test components. + +- KERNEL_SRC/salome_adm : + Configuration files used by autotools (M4 macros & co.) + +- KERNEL_SRC/src : + The source code (C++ and Python) + +
    2. +
    3. +Directory src: C++ and Python source code + +
        +
      1. +Basic services non related to CORBA + +- Basics + %A set of general purpose C++ services, not related to CORBA. + Some general purpose services that are in Utils directory (CORBA related), + are progressivley moved here, as they are not related to CORBA. + + +- SALOMELocalTrace + %A multithread trace system that allows message tracing on standard error + or a file. + +- CASCatch + Exceptions and signal handler. + +- HDFPersist + %A C++ interface to HDF. + +
      2. + +
      3. +Basic CORBA services + +- Logger : + %A CORBA %server that collects the trace messages from differents CORBA + process. + +- SALOMETraceCollector : + %A multithread trace system derived from SALOMELocalTrace, that sends messages + to Logger %server via CORBA. + +- Utils : + %A set of general purpose services related to CORBA, such as basic CORBA + exception system. See also Basics directory above. + +- NamingService : + C++ and Python interfaces to name, store and retrieve CORBA objects + +- GenericObj : + %A generic CORBA interface for CORBA objects, to count distributed references, + and to allow destruction by client. + +
      4. +
      5. +Miscellaneous CORBA servers + +- %Registry : + Implements SALOME_registry.idl. + Provides a CORBA %server library and a separate %server program. + +- ModuleCatalog : + Implements SALOME_moduleCatalog.idl. + Provide a CORBA %server library and separate %server and client programs. + +- ModuleGenerator : + Tool to generate a module catalog from CORBA idl + +- ResourcesManager : + library included in container %server + +- Notification : + library included in differents servers (container) + +- NOTIFICATION_SWIG + +
      6. + +
      7. +CORBA Containers for %SALOME Modules + +- Container + +- TestContainer + +- LifeCycleCORBA + +- LifeCycleCORBA_SWIG + +
      8. + +
      9. +STUDY %server and related interfaces and tools + +- SALOMEDSClient + +- TOOLSDS + +- SALOMEDSImpl + +- SALOMEDS + +
      10. +
      11. +Python interface to %SALOME + +- KERNEL_PY + +
      12. +
      13. +Efficient CORBA transfer services + +- Communication + +- Communication_SWIG + +
      14. +
      15. +%A Parallel container with MPI + +- MPIContainer + +- TestMPIContainer + +
      16. +
      17. +Batch interface library + +- Batch + +- Batch_SWIG + +
      18. +
      19. +Unit tests + +- UnitTests + +
      20. +
      +
    4. +
    + +\section S2_unit Tools and principles used for Unit testing + +**TO BE COMPLETED** + +Unit Testing rely on cppunit package for C++ testing, and on unittest module +for Python. See these products for general principles of unit testing. + +The cppunit package is optional. When the prerequisite is detected, the unit +tests are compiled. + +Unit Tests sources are in directories Test under the src/directories +containing the classes to test. + +Test are ordered following the order of directories given above. + +Tests can be run as a whole, or for a particular directory. In this case, only +a partial test is run (the classes to test, and the classes used, i.e. the +preceding test directories). + + +Today, only some tests are written as an example. There are not yet python +scripts in KERNEL_SRC, but it's a matter of days, there are working scripts +to test LifeCycleCORBA_SWIG interface. + +*/ diff --git a/src/Batch/Batch_BatchManager_eClient.cxx b/src/Batch/Batch_BatchManager_eClient.cxx new file mode 100644 index 000000000..41ce47c2e --- /dev/null +++ b/src/Batch/Batch_BatchManager_eClient.cxx @@ -0,0 +1,235 @@ +// Copyright (C) 2005 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN, +// CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License. +// +// This library is distributed in the hope that it will be useful +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// +/* + * BatchManager_eLSF.cxx : emulation of LSF client + * + * Auteur : Bernard SECHER - CEA DEN + * Mail : mailto:bernard.secher@cea.fr + * Date : Thu Apr 24 10:17:22 2008 + * Projet : PAL Salome + * + */ + +#include +#include +#include +#include +#include "Batch_BatchManager_eClient.hxx" + +namespace Batch { + + BatchManager_eClient::BatchManager_eClient(const Batch::FactBatchManager * parent, const char* host, const char* protocol, const char* mpiImpl) : BatchManager(parent, host), _protocol(protocol), _username("") + { + // instanciation of mpi implementation needed to launch executable in batch script + _mpiImpl = FactoryMpiImpl(mpiImpl); + } + + // Destructeur + BatchManager_eClient::~BatchManager_eClient() + { + // Nothing to do + delete _mpiImpl; + } + + void BatchManager_eClient::exportInputFiles(const Job& job) throw(EmulationException) + { + int status; + Parametre params = job.getParametre(); + Versatile V = params[INFILE]; + Versatile::iterator Vit; + string command; + string copy_command; + _username = string(params[USER]); + + // Test protocol + if( _protocol == "rsh" ) + copy_command = "rcp "; + else if( _protocol == "ssh" ) + copy_command = "scp "; + else + throw EmulationException("Unknown protocol : only rsh and ssh are known !"); + + // First step : creating batch tmp files directory + command = _protocol; + command += " "; + if(_username != ""){ + command += _username; + command += "@"; + } + command += _hostname; + command += " \"mkdir -p "; + command += string(params[TMPDIR]); + command += "\"" ; + cerr << command.c_str() << endl; + status = system(command.c_str()); + if(status) { + std::ostringstream oss; + oss << status; + std::string ex_mess("Error of connection on remote host ! status = "); + ex_mess += oss.str(); + throw EmulationException(ex_mess.c_str()); + } + + // Second step : copy fileToExecute into + // batch tmp files directory + command = copy_command; + command += string(params[EXECUTABLE]); + command += " "; + if(_username != ""){ + command += _username; + command += "@"; + } + command += _hostname; + command += ":"; + command += string(params[TMPDIR]); + cerr << command.c_str() << endl; + status = system(command.c_str()); + if(status) { + std::ostringstream oss; + oss << status; + std::string ex_mess("Error of connection on remote host ! status = "); + ex_mess += oss.str(); + throw EmulationException(ex_mess.c_str()); + } + + // Third step : copy filesToExportList into + // batch tmp files directory + for(Vit=V.begin(); Vit!=V.end(); Vit++) { + CoupleType cpt = *static_cast< CoupleType * >(*Vit); + Couple inputFile = cpt; + command = copy_command; + command += inputFile.getLocal(); + command += " "; + if(_username != ""){ + command += _username; + command += "@"; + } + command += _hostname; + command += ":"; + command += inputFile.getRemote(); + cerr << command.c_str() << endl; + status = system(command.c_str()); + if(status) { + std::ostringstream oss; + oss << status; + std::string ex_mess("Error of connection on remote host ! status = "); + ex_mess += oss.str(); + throw EmulationException(ex_mess.c_str()); + } + } + + } + + void BatchManager_eClient::importOutputFiles( const Job & job, const string directory ) throw(EmulationException) + { + string command; + int status; + + Parametre params = job.getParametre(); + Versatile V = params[OUTFILE]; + Versatile::iterator Vit; + + for(Vit=V.begin(); Vit!=V.end(); Vit++) { + CoupleType cpt = *static_cast< CoupleType * >(*Vit); + Couple outputFile = cpt; + if( _protocol == "rsh" ) + command = "rcp "; + else if( _protocol == "ssh" ) + command = "scp "; + else + throw EmulationException("Unknown protocol"); + + if (_username != ""){ + command += _username; + command += "@"; + } + command += _hostname; + command += ":"; + command += outputFile.getRemote(); + command += " "; + command += directory; + cerr << command.c_str() << endl; + status = system(command.c_str()); + if(status) + { + // Try to get what we can (logs files) + // throw BatchException("Error of connection on remote host"); + std::string mess("Copy command failed ! status is :"); + ostringstream status_str; + status_str << status; + mess += status_str.str(); + cerr << mess << endl; + } + } + + } + + MpiImpl *BatchManager_eClient::FactoryMpiImpl(string mpiImpl) throw(EmulationException) + { + if(mpiImpl == "lam") + return new MpiImpl_LAM(); + else if(mpiImpl == "mpich1") + return new MpiImpl_MPICH1(); + else if(mpiImpl == "mpich2") + return new MpiImpl_MPICH2(); + else if(mpiImpl == "openmpi") + return new MpiImpl_OPENMPI(); + else if(mpiImpl == "slurm") + return new MpiImpl_SLURM(); + else{ + ostringstream oss; + oss << mpiImpl << " : not yet implemented"; + throw EmulationException(oss.str().c_str()); + } + } + + string BatchManager_eClient::BuildTemporaryFileName() const + { + //build more complex file name to support multiple salome session + char *temp = new char[19]; + strcpy(temp, "/tmp/command"); + strcat(temp, "XXXXXX"); +#ifndef WNT + mkstemp(temp); +#else + char aPID[80]; + itoa(getpid(), aPID, 10); + strcat(temp, aPID); +#endif + + string command(temp); + delete [] temp; + command += ".sh"; + return command; + } + + void BatchManager_eClient::RmTmpFile(std::string & TemporaryFileName) + { + string command = "rm "; + command += TemporaryFileName; + char *temp = strdup(command.c_str()); + int lgthTemp = strlen(temp); + temp[lgthTemp - 3] = '*'; + temp[lgthTemp - 2] = '\0'; + system(temp); + free(temp); + } + +} diff --git a/src/Batch/Batch_BatchManager_eClient.hxx b/src/Batch/Batch_BatchManager_eClient.hxx new file mode 100644 index 000000000..717eae6a3 --- /dev/null +++ b/src/Batch/Batch_BatchManager_eClient.hxx @@ -0,0 +1,73 @@ +// Copyright (C) 2005 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN, +// CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License. +// +// This library is distributed in the hope that it will be useful +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// +/* + * BatchManager_eLSF.hxx : emulation of client + * + * Auteur : Bernard SECHER - CEA DEN + * Mail : mailto:bernard.secher@cea.fr + * Date : Thu Apr 24 10:17:22 2008 + * Projet : PAL Salome + * + */ + +#ifndef _BATCHMANAGER_eClient_H_ +#define _BATCHMANAGER_eClient_H_ + + +#include "MpiImpl.hxx" +#include "Batch_BatchManager.hxx" + +namespace Batch { + + class Job; + + class EmulationException + { + public: + const std::string msg; + + EmulationException(const std::string m) : msg(m) {} + }; + + class BatchManager_eClient : public BatchManager + { + public: + // Constructeur et destructeur + BatchManager_eClient(const Batch::FactBatchManager * parent, const char* host="localhost", const char* protocol="ssh", const char* mpiImpl="indif"); + virtual ~BatchManager_eClient(); + void importOutputFiles( const Job & job, const std::string directory ) throw(EmulationException); + + protected: + std::string _protocol; // protocol to access _hostname + std::string _username; // username to access _hostname + MpiImpl *_mpiImpl; // Mpi implementation to launch executable in batch script + + std::string BuildTemporaryFileName() const; + void RmTmpFile(std::string & TemporaryFileName); + MpiImpl* FactoryMpiImpl(string mpiImpl) throw(EmulationException); + void exportInputFiles(const Job & job) throw(EmulationException); + + private: + + }; + +} + +#endif diff --git a/src/Batch/Batch_BatchManager_eLSF.cxx b/src/Batch/Batch_BatchManager_eLSF.cxx new file mode 100644 index 000000000..d1c44e60d --- /dev/null +++ b/src/Batch/Batch_BatchManager_eLSF.cxx @@ -0,0 +1,310 @@ +// Copyright (C) 2005 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN, +// CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License. +// +// This library is distributed in the hope that it will be useful +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// +/* + * BatchManager_eLSF.cxx : emulation of LSF client + * + * Auteur : Bernard SECHER - CEA DEN + * Mail : mailto:bernard.secher@cea.fr + * Date : Thu Apr 24 10:17:22 2008 + * Projet : PAL Salome + * + */ + +#include +#include +#include +#include +#include "Batch_BatchManager_eLSF.hxx" + +namespace Batch { + + BatchManager_eLSF::BatchManager_eLSF(const FactBatchManager * parent, const char * host, const char * protocol, const char * mpiImpl) throw(InvalidArgumentException,ConnexionFailureException) : BatchManager_eClient(parent,host,protocol,mpiImpl) + { + // Nothing to do + } + + // Destructeur + BatchManager_eLSF::~BatchManager_eLSF() + { + // Nothing to do + } + + // Methode pour le controle des jobs : soumet un job au gestionnaire + const JobId BatchManager_eLSF::submitJob(const Job & job) + { + int status; + Parametre params = job.getParametre(); + const std::string dirForTmpFiles = params[TMPDIR]; + const string fileToExecute = params[EXECUTABLE]; + string::size_type p1 = fileToExecute.find_last_of("/"); + string::size_type p2 = fileToExecute.find_last_of("."); + std::string fileNameToExecute = fileToExecute.substr(p1+1,p2-p1-1); + + // export input files on cluster + exportInputFiles(job); + + // build batch script for job + buildBatchScript(job); + + // define name of log file + string logFile="/tmp/logs/"; + logFile += getenv("USER"); + logFile += "/batchSalome_"; + srand ( time(NULL) ); + int ir = rand(); + ostringstream oss; + oss << ir; + logFile += oss.str(); + logFile += ".log"; + + string command; + + // define command to submit batch + command = _protocol; + command += " "; + + if(_username != ""){ + command += _username; + command += "@"; + } + + command += _hostname; + command += " \"cd " ; + command += dirForTmpFiles ; + command += "; bsub < " ; + command += fileNameToExecute ; + command += "_Batch.sh\" > "; + command += logFile; + cerr << command.c_str() << endl; + status = system(command.c_str()); + if(status) + throw EmulationException("Error of connection on remote host"); + + // read id of submitted job in log file + char line[128]; + FILE *fp = fopen(logFile.c_str(),"r"); + fgets( line, 128, fp); + fclose(fp); + + string sline(line); + int p10 = sline.find("<"); + int p20 = sline.find(">"); + string strjob = sline.substr(p10+1,p20-p10-1); + + JobId id(this, strjob); + return id; + } + + // Methode pour le controle des jobs : retire un job du gestionnaire + void BatchManager_eLSF::deleteJob(const JobId & jobid) + { + int status; + int ref; + istringstream iss(jobid.getReference()); + iss >> ref; + + // define command to submit batch + string command; + command = _protocol; + command += " "; + + if (_username != ""){ + command += _username; + command += "@"; + } + + command += _hostname; + command += " \"bkill " ; + command += iss.str(); + command += "\""; + cerr << command.c_str() << endl; + status = system(command.c_str()); + if(status) + throw EmulationException("Error of connection on remote host"); + + cerr << "jobId = " << ref << "killed" << endl; + } + + // Methode pour le controle des jobs : suspend un job en file d'attente + void BatchManager_eLSF::holdJob(const JobId & jobid) + { + throw EmulationException("Not yet implemented"); + } + + // Methode pour le controle des jobs : relache un job suspendu + void BatchManager_eLSF::releaseJob(const JobId & jobid) + { + throw EmulationException("Not yet implemented"); + } + + + // Methode pour le controle des jobs : modifie un job en file d'attente + void BatchManager_eLSF::alterJob(const JobId & jobid, const Parametre & param, const Environnement & env) + { + throw EmulationException("Not yet implemented"); + } + + // Methode pour le controle des jobs : modifie un job en file d'attente + void BatchManager_eLSF::alterJob(const JobId & jobid, const Parametre & param) + { + alterJob(jobid, param, Environnement()); + } + + // Methode pour le controle des jobs : modifie un job en file d'attente + void BatchManager_eLSF::alterJob(const JobId & jobid, const Environnement & env) + { + alterJob(jobid, Parametre(), env); + } + + // Methode pour le controle des jobs : renvoie l'etat du job + JobInfo BatchManager_eLSF::queryJob(const JobId & jobid) + { + int id; + istringstream iss(jobid.getReference()); + iss >> id; + + // define name of log file + string logFile="/tmp/logs/"; + logFile += getenv("USER"); + logFile += "/batchSalome_"; + + srand ( time(NULL) ); + int ir = rand(); + ostringstream oss; + oss << ir; + logFile += oss.str(); + logFile += ".log"; + + string command; + int status; + + // define command to submit batch + command = _protocol; + command += " "; + + if (_username != ""){ + command += _username; + command += "@"; + } + + command += _hostname; + command += " \"bjobs " ; + command += iss.str(); + command += "\" > "; + command += logFile; + cerr << command.c_str() << endl; + status = system(command.c_str()); + if(status) + throw EmulationException("Error of connection on remote host"); + + JobInfo_eLSF ji = JobInfo_eLSF(id,logFile); + return ji; + } + + + + // Methode pour le controle des jobs : teste si un job est present en machine + bool BatchManager_eLSF::isRunning(const JobId & jobid) + { + throw EmulationException("Not yet implemented"); + } + + void BatchManager_eLSF::buildBatchScript(const Job & job) throw(EmulationException) + { + int status; + Parametre params = job.getParametre(); + const int nbproc = params[NBPROC]; + const long edt = params[MAXWALLTIME]; + const long mem = params[MAXRAMSIZE]; + const string workDir = params[WORKDIR]; + const std::string dirForTmpFiles = params[TMPDIR]; + const string fileToExecute = params[EXECUTABLE]; + string::size_type p1 = fileToExecute.find_last_of("/"); + string::size_type p2 = fileToExecute.find_last_of("."); + std::string rootNameToExecute = fileToExecute.substr(p1+1,p2-p1-1); + std::string fileNameToExecute = "~/" + dirForTmpFiles + "/" + string(basename(fileToExecute.c_str())); + + int idx = dirForTmpFiles.find("Batch/"); + std::string filelogtemp = dirForTmpFiles.substr(idx+6, dirForTmpFiles.length()); + + std::string TmpFileName = BuildTemporaryFileName(); + ofstream tempOutputFile; + tempOutputFile.open(TmpFileName.c_str(), ofstream::out ); + + tempOutputFile << "#! /bin/sh -f" << endl ; + if( edt > 0 ) + tempOutputFile << "#BSUB -W " << getWallTime(edt) << endl ; + if( mem > 0 ) + tempOutputFile << "#BSUB -M " << mem*1024 << endl ; + tempOutputFile << "#BSUB -n " << nbproc << endl ; + tempOutputFile << "#BSUB -o runSalome.output.log." << filelogtemp << endl ; + tempOutputFile << "#BSUB -e runSalome.error.log." << filelogtemp << endl ; + if( workDir.size() > 0 ) + tempOutputFile << "cd " << workDir << endl ; + tempOutputFile << _mpiImpl->boot("",nbproc); + tempOutputFile << _mpiImpl->run("",nbproc,fileNameToExecute); + tempOutputFile << _mpiImpl->halt(); + tempOutputFile.flush(); + tempOutputFile.close(); + chmod(TmpFileName.c_str(), 0x1ED); + cerr << TmpFileName.c_str() << endl; + + string command; + if( _protocol == "rsh" ) + command = "rcp "; + else if( _protocol == "ssh" ) + command = "scp "; + else + throw EmulationException("Unknown protocol"); + command += TmpFileName; + command += " "; + if(_username != ""){ + command += _username; + command += "@"; + } + command += _hostname; + command += ":"; + command += dirForTmpFiles ; + command += "/" ; + command += rootNameToExecute ; + command += "_Batch.sh" ; + cerr << command.c_str() << endl; + status = system(command.c_str()); + if(status) + throw EmulationException("Error of connection on remote host"); + + RmTmpFile(TmpFileName); + + } + + std::string BatchManager_eLSF::getWallTime(const long edt) + { + long h, m; + h = edt / 60; + m = edt - h*60; + ostringstream oss; + if( m >= 10 ) + oss << h << ":" << m; + else + oss << h << ":0" << m; + return oss.str(); + } + +} diff --git a/src/Batch/Batch_BatchManager_eLSF.hxx b/src/Batch/Batch_BatchManager_eLSF.hxx new file mode 100644 index 000000000..00d79a4fa --- /dev/null +++ b/src/Batch/Batch_BatchManager_eLSF.hxx @@ -0,0 +1,93 @@ +// Copyright (C) 2005 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN, +// CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License. +// +// This library is distributed in the hope that it will be useful +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// +/* + * BatchManager_eLSF.hxx : emulation of LSF client + * + * Auteur : Bernard SECHER - CEA DEN + * Mail : mailto:bernard.secher@cea.fr + * Date : Thu Apr 24 10:17:22 2008 + * Projet : PAL Salome + * + */ + +#ifndef _BATCHMANAGER_eLSF_H_ +#define _BATCHMANAGER_eLSF_H_ + + +#include "Batch_JobId.hxx" +#include "Batch_JobInfo.hxx" +#include "Batch_JobInfo_eLSF.hxx" +#include "Batch_InvalidArgumentException.hxx" +#include "Batch_ConnexionFailureException.hxx" +#include "Batch_APIInternalFailureException.hxx" +#include "Batch_NotYetImplementedException.hxx" +#include "Batch_BatchManager.hxx" +#include "Batch_BatchManager_eClient.hxx" + +namespace Batch { + + class Job; + class JobId; + class JobInfo; + class FactBatchManager; + + class BatchManager_eLSF : public BatchManager_eClient + { + public: + // Constructeur et destructeur + BatchManager_eLSF(const FactBatchManager * parent, const char * host="localhost", const char * protocol="ssh", const char * mpiImpl="indif") throw(InvalidArgumentException,ConnexionFailureException); // connexion a la machine host + virtual ~BatchManager_eLSF(); + + // Recupere le nom du serveur par defaut + // static string BatchManager_LSF::getDefaultServer(); + + // Methodes pour le controle des jobs + virtual const JobId submitJob(const Job & job); // soumet un job au gestionnaire + virtual void deleteJob(const JobId & jobid); // retire un job du gestionnaire + virtual void holdJob(const JobId & jobid); // suspend un job en file d'attente + virtual void releaseJob(const JobId & jobid); // relache un job suspendu + virtual void alterJob(const JobId & jobid, const Parametre & param, const Environnement & env); // modifie un job en file d'attente + virtual void alterJob(const JobId & jobid, const Parametre & param); // modifie un job en file d'attente + virtual void alterJob(const JobId & jobid, const Environnement & env); // modifie un job en file d'attente + virtual JobInfo queryJob(const JobId & jobid); // renvoie l'etat du job + virtual bool isRunning(const JobId & jobid); // teste si un job est present en machine + + virtual void setParametre(const JobId & jobid, const Parametre & param) { return alterJob(jobid, param); } // modifie un job en file d'attente + virtual void setEnvironnement(const JobId & jobid, const Environnement & env) { return alterJob(jobid, env); } // modifie un job en file d'attente + + + protected: + void buildBatchScript(const Job & job) throw(EmulationException); + std::string getWallTime(const long edt); + + private: + +#ifdef SWIG + public: + // Recupere le l'identifiant d'un job deja soumis au BatchManager + //virtual const JobId getJobIdByReference(const string & ref) { return BatchManager::getJobIdByReference(ref); } + virtual const JobId getJobIdByReference(const char * ref) { return BatchManager::getJobIdByReference(ref); } +#endif + + }; + +} + +#endif diff --git a/src/Batch/Batch_BatchManager_ePBS.cxx b/src/Batch/Batch_BatchManager_ePBS.cxx new file mode 100644 index 000000000..999025cfb --- /dev/null +++ b/src/Batch/Batch_BatchManager_ePBS.cxx @@ -0,0 +1,296 @@ +// Copyright (C) 2005 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN, +// CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License. +// +// This library is distributed in the hope that it will be useful +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// +/* + * BatchManager_ePBS.cxx : emulation of PBS client + * + * Auteur : Bernard SECHER - CEA DEN + * Mail : mailto:bernard.secher@cea.fr + * Date : Thu Apr 24 10:17:22 2008 + * Projet : PAL Salome + * + */ + +#include +#include +#include +#include +#include "Batch_BatchManager_ePBS.hxx" + +namespace Batch { + + BatchManager_ePBS::BatchManager_ePBS(const FactBatchManager * parent, const char * host, const char * protocol, const char * mpiImpl) throw(InvalidArgumentException,ConnexionFailureException) : BatchManager_eClient(parent,host,protocol,mpiImpl) + { + // Nothing to do + } + + // Destructeur + BatchManager_ePBS::~BatchManager_ePBS() + { + // Nothing to do + } + + // Methode pour le controle des jobs : soumet un job au gestionnaire + const JobId BatchManager_ePBS::submitJob(const Job & job) + { + int status; + Parametre params = job.getParametre(); + const std::string dirForTmpFiles = params[TMPDIR]; + const string fileToExecute = params[EXECUTABLE]; + string::size_type p1 = fileToExecute.find_last_of("/"); + string::size_type p2 = fileToExecute.find_last_of("."); + std::string fileNameToExecute = fileToExecute.substr(p1+1,p2-p1-1); + + // export input files on cluster + exportInputFiles(job); + + // build batch script for job + buildBatchScript(job); + + // define name of log file + string logFile="/tmp/logs/"; + logFile += getenv("USER"); + logFile += "/batchSalome_"; + srand ( time(NULL) ); + int ir = rand(); + ostringstream oss; + oss << ir; + logFile += oss.str(); + logFile += ".log"; + + string command; + + // define command to submit batch + command = _protocol; + command += " "; + + if(_username != ""){ + command += _username; + command += "@"; + } + + command += _hostname; + command += " \"cd " ; + command += dirForTmpFiles ; + command += "; qsub " ; + command += fileNameToExecute ; + command += "_Batch.sh\" > "; + command += logFile; + cerr << command.c_str() << endl; + status = system(command.c_str()); + if(status) + throw EmulationException("Error of connection on remote host"); + + // read id of submitted job in log file + char line[128]; + FILE *fp = fopen(logFile.c_str(),"r"); + fgets( line, 128, fp); + fclose(fp); + + string sline(line); + int pos = sline.find("."); + string strjob; + if(pos == string::npos) + strjob = sline; + else + strjob = sline.substr(0,pos); + + JobId id(this, strjob); + return id; + } + + // Methode pour le controle des jobs : retire un job du gestionnaire + void BatchManager_ePBS::deleteJob(const JobId & jobid) + { + int status; + int ref; + istringstream iss(jobid.getReference()); + iss >> ref; + + // define command to submit batch + string command; + command = _protocol; + command += " "; + + if (_username != ""){ + command += _username; + command += "@"; + } + + command += _hostname; + command += " \"qdel " ; + command += iss.str(); + command += "\""; + cerr << command.c_str() << endl; + status = system(command.c_str()); + if(status) + throw EmulationException("Error of connection on remote host"); + + cerr << "jobId = " << ref << "killed" << endl; + } + + // Methode pour le controle des jobs : suspend un job en file d'attente + void BatchManager_ePBS::holdJob(const JobId & jobid) + { + throw EmulationException("Not yet implemented"); + } + + // Methode pour le controle des jobs : relache un job suspendu + void BatchManager_ePBS::releaseJob(const JobId & jobid) + { + throw EmulationException("Not yet implemented"); + } + + + // Methode pour le controle des jobs : modifie un job en file d'attente + void BatchManager_ePBS::alterJob(const JobId & jobid, const Parametre & param, const Environnement & env) + { + throw EmulationException("Not yet implemented"); + } + + // Methode pour le controle des jobs : modifie un job en file d'attente + void BatchManager_ePBS::alterJob(const JobId & jobid, const Parametre & param) + { + alterJob(jobid, param, Environnement()); + } + + // Methode pour le controle des jobs : modifie un job en file d'attente + void BatchManager_ePBS::alterJob(const JobId & jobid, const Environnement & env) + { + alterJob(jobid, Parametre(), env); + } + + // Methode pour le controle des jobs : renvoie l'etat du job + JobInfo BatchManager_ePBS::queryJob(const JobId & jobid) + { + int id; + istringstream iss(jobid.getReference()); + iss >> id; + + // define name of log file + string logFile="/tmp/logs/"; + logFile += getenv("USER"); + logFile += "/batchSalome_"; + + ostringstream oss; + oss << this << "_" << id; + logFile += oss.str(); + logFile += ".log"; + + string command; + int status; + + // define command to submit batch + command = _protocol; + command += " "; + + if (_username != ""){ + command += _username; + command += "@"; + } + + command += _hostname; + command += " \"qstat -f " ; + command += iss.str(); + command += "\" > "; + command += logFile; + cerr << command.c_str() << endl; + status = system(command.c_str()); + if(status && status != 153 && status != 256*153) + throw EmulationException("Error of connection on remote host"); + + JobInfo_ePBS ji = JobInfo_ePBS(id,logFile); + return ji; + } + + // Methode pour le controle des jobs : teste si un job est present en machine + bool BatchManager_ePBS::isRunning(const JobId & jobid) + { + throw EmulationException("Not yet implemented"); + } + + void BatchManager_ePBS::buildBatchScript(const Job & job) throw(EmulationException) + { + int status; + Parametre params = job.getParametre(); + const long nbproc = params[NBPROC]; + const long edt = params[MAXWALLTIME]; + const long mem = params[MAXRAMSIZE]; + const string workDir = params[WORKDIR]; + const std::string dirForTmpFiles = params[TMPDIR]; + const string fileToExecute = params[EXECUTABLE]; + const string home = params[HOMEDIR]; + string::size_type p1 = fileToExecute.find_last_of("/"); + string::size_type p2 = fileToExecute.find_last_of("."); + std::string rootNameToExecute = fileToExecute.substr(p1+1,p2-p1-1); + std::string fileNameToExecute = "~/" + dirForTmpFiles + "/" + string(basename(fileToExecute.c_str())); + + int idx = dirForTmpFiles.find("Batch/"); + std::string filelogtemp = dirForTmpFiles.substr(idx+6, dirForTmpFiles.length()); + + std::string TmpFileName = BuildTemporaryFileName(); + ofstream tempOutputFile; + tempOutputFile.open(TmpFileName.c_str(), ofstream::out ); + + tempOutputFile << "#! /bin/sh -f" << endl; + if( edt > 0 ) + tempOutputFile << "#PBS -l walltime=" << edt*60 << endl ; + if( mem > 0 ) + tempOutputFile << "#PBS -l mem=" << mem << "mb" << endl ; + tempOutputFile << "#PBS -o " << home << "/" << dirForTmpFiles << "/runSalome.output.log." << filelogtemp << endl ; + tempOutputFile << "#PBS -e " << home << "/" << dirForTmpFiles << "/runSalome.error.log." << filelogtemp << endl ; + if( workDir.size() > 0 ) + tempOutputFile << "cd " << workDir << endl ; + tempOutputFile << _mpiImpl->boot("${PBS_NODEFILE}",nbproc); + tempOutputFile << _mpiImpl->run("${PBS_NODEFILE}",nbproc,fileNameToExecute); + tempOutputFile << _mpiImpl->halt(); + tempOutputFile.flush(); + tempOutputFile.close(); + chmod(TmpFileName.c_str(), 0x1ED); + cerr << TmpFileName.c_str() << endl; + + string command; + if( _protocol == "rsh" ) + command = "rcp "; + else if( _protocol == "ssh" ) + command = "scp "; + else + throw EmulationException("Unknown protocol"); + command += TmpFileName; + command += " "; + if(_username != ""){ + command += _username; + command += "@"; + } + command += _hostname; + command += ":"; + command += dirForTmpFiles ; + command += "/" ; + command += rootNameToExecute ; + command += "_Batch.sh" ; + cerr << command.c_str() << endl; + status = system(command.c_str()); + if(status) + throw EmulationException("Error of connection on remote host"); + + RmTmpFile(TmpFileName); + + } + +} diff --git a/src/Batch/Batch_BatchManager_ePBS.hxx b/src/Batch/Batch_BatchManager_ePBS.hxx new file mode 100644 index 000000000..42f4b1b69 --- /dev/null +++ b/src/Batch/Batch_BatchManager_ePBS.hxx @@ -0,0 +1,91 @@ +// Copyright (C) 2005 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN, +// CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License. +// +// This library is distributed in the hope that it will be useful +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// +/* + * BatchManager_ePBS.hxx : emulation of PBS client + * + * Auteur : Bernard SECHER - CEA DEN + * Mail : mailto:bernard.secher@cea.fr + * Date : Thu Apr 24 10:17:22 2008 + * Projet : PAL Salome + * + */ + +#ifndef _BATCHMANAGER_eLSF_H_ +#define _BATCHMANAGER_eLSF_H_ + +#include "Batch_JobId.hxx" +#include "Batch_JobInfo.hxx" +#include "Batch_JobInfo_ePBS.hxx" +#include "Batch_InvalidArgumentException.hxx" +#include "Batch_ConnexionFailureException.hxx" +#include "Batch_APIInternalFailureException.hxx" +#include "Batch_NotYetImplementedException.hxx" +#include "Batch_BatchManager.hxx" +#include "Batch_BatchManager_eClient.hxx" + +namespace Batch { + + class Job; + class JobId; + class JobInfo; + class FactBatchManager; + + class BatchManager_ePBS : public BatchManager_eClient + { + public: + // Constructeur et destructeur + BatchManager_ePBS(const FactBatchManager * parent, const char * host="localhost", const char * protocol="ssh", const char * mpiImpl="indif") throw(InvalidArgumentException,ConnexionFailureException); // connexion a la machine host + virtual ~BatchManager_ePBS(); + + // Recupere le nom du serveur par defaut + // static string BatchManager_LSF::getDefaultServer(); + + // Methodes pour le controle des jobs + virtual const JobId submitJob(const Job & job); // soumet un job au gestionnaire + virtual void deleteJob(const JobId & jobid); // retire un job du gestionnaire + virtual void holdJob(const JobId & jobid); // suspend un job en file d'attente + virtual void releaseJob(const JobId & jobid); // relache un job suspendu + virtual void alterJob(const JobId & jobid, const Parametre & param, const Environnement & env); // modifie un job en file d'attente + virtual void alterJob(const JobId & jobid, const Parametre & param); // modifie un job en file d'attente + virtual void alterJob(const JobId & jobid, const Environnement & env); // modifie un job en file d'attente + virtual JobInfo queryJob(const JobId & jobid); // renvoie l'etat du job + virtual bool isRunning(const JobId & jobid); // teste si un job est present en machine + + virtual void setParametre(const JobId & jobid, const Parametre & param) { return alterJob(jobid, param); } // modifie un job en file d'attente + virtual void setEnvironnement(const JobId & jobid, const Environnement & env) { return alterJob(jobid, env); } // modifie un job en file d'attente + + + protected: + void buildBatchScript(const Job & job) throw(EmulationException); + + private: + +#ifdef SWIG + public: + // Recupere le l'identifiant d'un job deja soumis au BatchManager + //virtual const JobId getJobIdByReference(const string & ref) { return BatchManager::getJobIdByReference(ref); } + virtual const JobId getJobIdByReference(const char * ref) { return BatchManager::getJobIdByReference(ref); } +#endif + + }; + +} + +#endif diff --git a/src/Batch/Batch_FactBatchManager_eClient.cxx b/src/Batch/Batch_FactBatchManager_eClient.cxx new file mode 100644 index 000000000..6673879de --- /dev/null +++ b/src/Batch/Batch_FactBatchManager_eClient.cxx @@ -0,0 +1,48 @@ +// Copyright (C) 2005 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN, +// CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License. +// +// This library is distributed in the hope that it will be useful +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// +/* + * FactBatchManager_eClient.cxx : emulation of client + * + * Auteur : Bernard SECHER - CEA DEN + * Mail : mailto:bernard.secher@cea.fr + * Date : Thu Apr 24 10:17:22 2008 + * Projet : PAL Salome + * + */ + +#include +#include +#include "Batch_FactBatchManager_eClient.hxx" +using namespace std; + +namespace Batch { + + // Constructeur + FactBatchManager_eClient::FactBatchManager_eClient(const string & _t) : FactBatchManager(_t) + { + } + + // Destructeur + FactBatchManager_eClient::~FactBatchManager_eClient() + { + // Nothing to do + } + +} diff --git a/src/Batch/Batch_FactBatchManager_eClient.hxx b/src/Batch/Batch_FactBatchManager_eClient.hxx new file mode 100644 index 000000000..616a6626d --- /dev/null +++ b/src/Batch/Batch_FactBatchManager_eClient.hxx @@ -0,0 +1,58 @@ +// Copyright (C) 2005 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN, +// CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License. +// +// This library is distributed in the hope that it will be useful +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// +/* + * FactBatchManager_eClient.hxx : emulation of client + * + * Auteur : Bernard SECHER - CEA DEN + * Mail : mailto:bernard.secher@cea.fr + * Date : Thu Apr 24 10:17:22 2008 + * Projet : PAL Salome + * + */ + +#ifndef _FACTBATCHMANAGER_eClient_H_ +#define _FACTBATCHMANAGER_eClient_H_ + +#include +#include +#include "Batch_FactBatchManager.hxx" + +namespace Batch { + + class BatchManager_eClient; + + class FactBatchManager_eClient : public FactBatchManager + { + public: + // Constructeur et destructeur + FactBatchManager_eClient(const std::string & type); + virtual ~FactBatchManager_eClient(); + + virtual Batch::BatchManager_eClient * operator() (const char * hostname,const char * protocol, const char * mpi) const = 0; + + protected: + + private: + + }; + +} + +#endif diff --git a/src/Batch/Batch_FactBatchManager_eLSF.cxx b/src/Batch/Batch_FactBatchManager_eLSF.cxx new file mode 100644 index 000000000..227bffa32 --- /dev/null +++ b/src/Batch/Batch_FactBatchManager_eLSF.cxx @@ -0,0 +1,63 @@ +// Copyright (C) 2005 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN, +// CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License. +// +// This library is distributed in the hope that it will be useful +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// +/* + * FactBatchManager_eLSF.cxx : + * + * Auteur : Bernard SECHER - CEA DEN + * Date : Avril 2008 + * Projet : PAL Salome + * + */ + +#include +#include "Batch_BatchManager_eLSF.hxx" +#include "Batch_FactBatchManager_eLSF.hxx" +//#include "utilities.h" + +namespace Batch { + + static FactBatchManager_eLSF sFBM_eLSF; + + // Constructeur + FactBatchManager_eLSF::FactBatchManager_eLSF() : FactBatchManager_eClient("eLSF") + { + // Nothing to do + } + + // Destructeur + FactBatchManager_eLSF::~FactBatchManager_eLSF() + { + // Nothing to do + } + + // Functor + BatchManager * FactBatchManager_eLSF::operator() (const char * hostname) const + { + // MESSAGE("Building new BatchManager_LSF on host '" << hostname << "'"); + return new BatchManager_eLSF(this, hostname); + } + + BatchManager_eClient * FactBatchManager_eLSF::operator() (const char * hostname, const char * protocol, const char * mpiImpl) const + { + // MESSAGE("Building new BatchManager_LSF on host '" << hostname << "'"); + return new BatchManager_eLSF(this, hostname, protocol, mpiImpl); + } + +} diff --git a/src/Batch/Batch_FactBatchManager_eLSF.hxx b/src/Batch/Batch_FactBatchManager_eLSF.hxx new file mode 100644 index 000000000..e1660aaaa --- /dev/null +++ b/src/Batch/Batch_FactBatchManager_eLSF.hxx @@ -0,0 +1,60 @@ +// Copyright (C) 2005 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN, +// CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License. +// +// This library is distributed in the hope that it will be useful +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// +/* + * FactBatchManager_eLSF.hxx : + * + * Auteur : Bernard SECHER : CEA DEN + * Date : Avril 2008 + * Projet : PAL Salome + * + */ + +#ifndef _FACTBATCHMANAGER_eLSF_H_ +#define _FACTBATCHMANAGER_eLSF_H_ + +using namespace std; +#include +#include +#include "Batch_BatchManager_eClient.hxx" +#include "Batch_FactBatchManager_eClient.hxx" + +namespace Batch { + + class BatchManager_eLSF; + + class FactBatchManager_eLSF : public FactBatchManager_eClient + { + public: + // Constructeur et destructeur + FactBatchManager_eLSF(); + virtual ~FactBatchManager_eLSF(); + + virtual BatchManager * operator() (const char * hostname) const; + virtual BatchManager_eClient * operator() (const char * hostname, const char * protocol, const char * mpiImpl) const; + + protected: + + private: + + }; + +} + +#endif diff --git a/src/Batch/Batch_FactBatchManager_ePBS.cxx b/src/Batch/Batch_FactBatchManager_ePBS.cxx new file mode 100644 index 000000000..3bcbda530 --- /dev/null +++ b/src/Batch/Batch_FactBatchManager_ePBS.cxx @@ -0,0 +1,64 @@ +// Copyright (C) 2005 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN, +// CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License. +// +// This library is distributed in the hope that it will be useful +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// +/* + * FactBatchManager_ePBS.cxx : + * + * Auteur : Bernard SECHER - CEA DEN + * Date : Avril 2008 + * Projet : PAL Salome + * + */ + +#include +#include "Batch_BatchManager_ePBS.hxx" +#include "Batch_FactBatchManager_ePBS.hxx" +//#include "utilities.h" + +namespace Batch { + + static FactBatchManager_ePBS sFBM_ePBS; + + // Constructeur + FactBatchManager_ePBS::FactBatchManager_ePBS() : FactBatchManager_eClient("ePBS") + { + // Nothing to do + } + + // Destructeur + FactBatchManager_ePBS::~FactBatchManager_ePBS() + { + // Nothing to do + } + + // Functor + BatchManager * FactBatchManager_ePBS::operator() (const char * hostname) const + { + // MESSAGE("Building new BatchManager_PBS on host '" << hostname << "'"); + return new BatchManager_ePBS(this, hostname); + } + + BatchManager_eClient * FactBatchManager_ePBS::operator() (const char * hostname, const char * protocol, const char * mpiImpl) const + { + // MESSAGE("Building new BatchManager_PBS on host '" << hostname << "'"); + return new BatchManager_ePBS(this, hostname, protocol, mpiImpl); + } + + +} diff --git a/src/Batch/Batch_FactBatchManager_ePBS.hxx b/src/Batch/Batch_FactBatchManager_ePBS.hxx new file mode 100644 index 000000000..69fdf322a --- /dev/null +++ b/src/Batch/Batch_FactBatchManager_ePBS.hxx @@ -0,0 +1,60 @@ +// Copyright (C) 2005 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN, +// CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License. +// +// This library is distributed in the hope that it will be useful +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// +/* + * FactBatchManager_ePBS.hxx : + * + * Auteur : Bernard SECHER : CEA DEN + * Date : Avril 2008 + * Projet : PAL Salome + * + */ + +#ifndef _FACTBATCHMANAGER_ePBS_H_ +#define _FACTBATCHMANAGER_ePBS_H_ + +using namespace std; +#include +#include +#include "Batch_BatchManager_eClient.hxx" +#include "Batch_FactBatchManager_eClient.hxx" + +namespace Batch { + + class BatchManager_ePBS; + + class FactBatchManager_ePBS : public FactBatchManager_eClient + { + public: + // Constructeur et destructeur + FactBatchManager_ePBS(); + virtual ~FactBatchManager_ePBS(); + + virtual BatchManager * operator() (const char * hostname) const; + virtual BatchManager_eClient * operator() (const char * hostname, const char * protocol, const char * mpiImpl) const; + + protected: + + private: + + }; + +} + +#endif diff --git a/src/Batch/Batch_JobInfo_eLSF.cxx b/src/Batch/Batch_JobInfo_eLSF.cxx new file mode 100644 index 000000000..177f2eb06 --- /dev/null +++ b/src/Batch/Batch_JobInfo_eLSF.cxx @@ -0,0 +1,103 @@ +// Copyright (C) 2005 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN, +// CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License. +// +// This library is distributed in the hope that it will be useful +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// +/* + * JobInfo_eLSF.cxx : emulation of LSF client + * + * Auteur : Bernard SECHER - CEA DEN + * Mail : mailto:bernard.secher@cea.fr + * Date : Thu Apr 24 10:17:22 2008 + * Projet : PAL Salome + * + */ + +#include +#include +#include +#include +#include "Batch_Parametre.hxx" +#include "Batch_Environnement.hxx" +#include "Batch_RunTimeException.hxx" +#include "Batch_APIInternalFailureException.hxx" +#include "Batch_JobInfo_eLSF.hxx" + +namespace Batch { + + + + // Constructeurs + JobInfo_eLSF::JobInfo_eLSF(int id, string logFile) : JobInfo() + { + // On remplit les membres _param et _env + ostringstream oss; + oss << id; + _param[ID] = oss.str(); + + // read status of job in log file + char line[128]; + ifstream fp(logFile.c_str(),ios::in); + fp.getline(line,80,'\n'); + + string sjobid, username, status; + fp >> sjobid; + fp >> username; + fp >> status; + + _param[STATE] = status; + + if( status.find("RUN") != string::npos) + _running = true; + + } + + // Teste si un job est present en machine + bool JobInfo_eLSF::isRunning() const + { + return _running; + } + + + // Destructeur + JobInfo_eLSF::~JobInfo_eLSF() + { + // Nothing to do + } + + // Convertit une date HH:MM:SS en secondes + long JobInfo_eLSF::HMStoLong(const string & s) + { + long hour, min, sec; + + sscanf( s.c_str(), "%ld:%ld:%ld", &hour, &min, &sec); + return ( ( ( hour * 60L ) + min ) * 60L ) + sec; + } + + // Methode pour l'interfacage avec Python (SWIG) : affichage en Python + string JobInfo_eLSF::__str__() const + { + ostringstream sst; + sst << " +#include +#include +#include +#include "Batch_Parametre.hxx" +#include "Batch_Environnement.hxx" +#include "Batch_RunTimeException.hxx" +#include "Batch_APIInternalFailureException.hxx" +#include "Batch_JobInfo_ePBS.hxx" + +namespace Batch { + + + + // Constructeurs + JobInfo_ePBS::JobInfo_ePBS(int id, string logFile) : JobInfo() + { + // On remplit les membres _param et _env + ostringstream oss; + oss << id; + _param[ID] = oss.str(); + + // read of log file + char line[128]; + ifstream fp(logFile.c_str(),ios::in); + + string status; + string sline; + int pos = string::npos; + while( (pos == string::npos) && fp.getline(line,80,'\n') ){ + sline = string(line); + pos = sline.find("job_state"); + }; + + if(pos!=string::npos){ + istringstream iss(sline); + iss >> status; + iss >> status; + iss >> status; + } + else + status = "U"; + + _param[STATE] = status; + + if( status.find("R") != string::npos) + _running = true; + + } + + // Teste si un job est present en machine + bool JobInfo_ePBS::isRunning() const + { + return _running; + } + + + // Destructeur + JobInfo_ePBS::~JobInfo_ePBS() + { + // Nothing to do + } + + // Convertit une date HH:MM:SS en secondes + long JobInfo_ePBS::HMStoLong(const string & s) + { + long hour, min, sec; + + sscanf( s.c_str(), "%ld:%ld:%ld", &hour, &min, &sec); + return ( ( ( hour * 60L ) + min ) * 60L ) + sec; + } + + // Methode pour l'interfacage avec Python (SWIG) : affichage en Python + string JobInfo_ePBS::__str__() const + { + ostringstream sst; + sst << " + +
  8. +BASIC datastream ports + + + +
    Port name Data type Idl Name Idl File
    BASIC_short short Data_Short_Port SALOME_Ports.idl
    +
  9. + +
  10. +CALCIUM datastream ports + + + + + + + + +
    Port name Data type Idl Name Idl File
    CALCIUM_integer sequence of long Calcium_Integer_Port Calcium_Ports.idl
    CALCIUM_real sequence of float Calcium_Real_Port Calcium_Ports.idl
    CALCIUM_double sequence of double Calcium_Double_Port Calcium_Ports.idl
    CALCIUM_string sequence of %string Calcium_String_Port Calcium_Ports.idl
    CALCIUM_logical sequence of boolean Calcium_Logical_Port Calcium_Ports.idl
    CALCIUM_complex sequence of float Calcium_Complex_Port Calcium_Ports.idl
    +
  11. + +
  12. +PALM datastream ports + + + + +
    Port name Data type Idl Name Idl File
    PALM_short short Palm_Data_Short_Port Palm_Ports.idl
    PALM_seq_short sequence of short Palm_Data_Seq_Short_Port Palm_Ports.idl
    +
  13. + +
+ +*/ + + diff --git a/src/DSC/DSC_User/Datastream/Calcium/CalciumCxxInterface.hxx b/src/DSC/DSC_User/Datastream/Calcium/CalciumCxxInterface.hxx new file mode 100644 index 000000000..969b510e8 --- /dev/null +++ b/src/DSC/DSC_User/Datastream/Calcium/CalciumCxxInterface.hxx @@ -0,0 +1,532 @@ +// Copyright (C) 2007 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN, +// CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// +// +// +// File : CalciumCxxInterface.hxx +// Author : Eric Fayolle (EDF) +// Module : KERNEL +// Modified by : $LastChangedBy$ +// Date : $LastChangedDate: 2007-03-01 13:27:58 +0100 (jeu, 01 mar 2007) $ +// Id : $Id$ + +#ifndef _CALCIUM_CXXINTERFACE_HXX_ +#define _CALCIUM_CXXINTERFACE_HXX_ + +#include +#include +#include +#include "Superv_Component_i.hxx" +#include "CalciumException.hxx" +#include "CalciumTypes.hxx" +#include "CalciumGenericUsesPort.hxx" +#include "Copy2UserSpace.hxx" +#include "Copy2CorbaSpace.hxx" +#include "CalciumPortTraits.hxx" + +#include + +//#define _DEBUG_ + +template +struct IsSameType { + static const bool value = false; +}; +template +struct IsSameType { + static const bool value = true; +}; + + +#include + +namespace CalciumInterface { + + /********************* INTERFACE DE DECONNEXION *****************/ + + static inline void + ecp_cd (Superv_Component_i & component, std::string & instanceName) + { + /* TODO : Trouver le nom de l'instance SALOME*/ + if (instanceName.empty()) instanceName="UNDEFINED"; + + } + + static void + ecp_fin (Superv_Component_i & component, bool provideLastGivenValue) + { + std::vector usesPortNames; + std::vector::const_iterator it; + component.get_uses_port_names(usesPortNames); + + //Récupérer le type de réel du port est un peu difficile + //car l'interface ne donne aucune indication + + // uses_port *myUsesPort; + calcium_uses_port* myCalciumUsesPort; + + for (it=usesPortNames.begin(); it != usesPortNames.end(); ++it) { + try { + + myCalciumUsesPort= + component.Superv_Component_i::get_port< calcium_uses_port >((*it).c_str()); + +// component.Superv_Component_i::get_port(myUsesPort,(*it).c_str()); +// calcium_uses_port* myCalciumUsesPort= +// dynamic_cast(myUsesPort); + +#ifdef _DEBUG_ + std::cerr << "-------- CalciumInterface(ecp_fin) MARK 1 -|"<< *it <<"|----"<< + // typeid(myUsesPort).name() <<"-------------" << + typeid(myCalciumUsesPort).name() <<"-------------" << std::endl; +#endif + +// if ( !myCalciumUsesPort ) +// throw Superv_Component_i::BadCast(LOC(OSS()<<"Impossible de convertir le port " +// << *it << " en port de type calcium_uses_port." )); + + myCalciumUsesPort->disconnect(provideLastGivenValue); + + } catch ( const Superv_Component_i::BadCast & ex) { +#ifdef _DEBUG_ + std::cerr << ex.what() << std::endl; +#endif + throw (CalciumException(CalciumTypes::CPTPVR,ex)); + } catch ( const DSC_Exception & ex) { +#ifdef _DEBUG_ + std::cerr << ex.what() << std::endl; +#endif + // Exception venant de SupervComponent : + // PortNotDefined(CPNMVR), PortNotConnected(CPLIEN) + // ou du port uses : Dsc_Exception + // On continue à traiter la deconnexion des autres ports uses + } catch (...) { + throw (CalciumException(CalciumTypes::CPATAL,"Exception innatendue")); + // En fonction du mode de gestion des erreurs throw; + } + } + } + + + /********************* INTERFACES DE DESALLOCATION *****************/ + + // Uniquement appelé par l'utilisateur s'il utilise la 0 copie + // ( pointeur de données data==NULL à l'appel de ecp_lecture ) + // Une désallocation aura lieu uniquement si un buffer intermédiaire + // était necessaire (type utilisateur et corba diffférent) + // La propriété du buffer est rendue à CORBA sinon + template static void + ecp_free ( T1 * dataPtr ) + { + typedef typename ProvidesPortTraits::PortType PortType; + typedef typename PortType::DataManipulator DataManipulator; + typedef typename DataManipulator::Type DataType; // Attention != T1 + typedef typename DataManipulator::InnerType InnerType; + + DeleteTraits::value, DataManipulator >::apply(dataPtr); + } + + template static void + ecp_free ( T1 * dataPtr ) + { + ecp_free ( dataPtr ); + } + + + /********************* INTERFACES DE LECTURE *****************/ + + + // T1 est le type de données + // T2 est un de type Calcium permettant de sélectionner le port CORBA correspondant + // T1 et T2 sont dissociés pour discriminer le cas des nombres complexes + // -> Les données des nombres complexes sont de type float mais + // le port à utiliser est le port cplx + template static void + ecp_lecture ( Superv_Component_i & component, + int const & dependencyType, + double & ti, + double const & tf, + long & i, + const string & nomVar, + size_t bufferLength, + size_t & nRead, + T1 * &data ) + { + + assert(&component); + + typedef typename ProvidesPortTraits::PortType PortType; + typedef typename PortType::DataManipulator DataManipulator; + typedef typename DataManipulator::Type CorbaDataType; // Attention != T1 + typedef typename DataManipulator::InnerType InnerType; + CalciumTypes::DependencyType _dependencyType= + static_cast(dependencyType); + + CorbaDataType corbaData; + +#ifdef _DEBUG_ + std::cerr << "-------- CalciumInterface(ecp_lecture) MARK 1 ------------------" << std::endl; +#endif + + if (nomVar.empty()) + throw CalciumException(CalciumTypes::CPNMVR, + LOC("Le nom de la variable est ")); + PortType * port; +#ifdef _DEBUG_ + std::cout << "-------- CalciumInterface(ecp_lecture) MARK 2 ------------------" << std::endl; +#endif + + try { + port = component.Superv_Component_i::get_port< PortType > (nomVar.c_str()); +#ifdef _DEBUG_ + std::cout << "-------- CalciumInterface(ecp_lecture) MARK 3 ------------------" << std::endl; +#endif + } catch ( const Superv_Component_i::PortNotDefined & ex) { +#ifdef _DEBUG_ + std::cerr << ex.what() << std::endl; +#endif + throw (CalciumException(CalciumTypes::CPNMVR,ex)); + } catch ( const Superv_Component_i::PortNotConnected & ex) { +#ifdef _DEBUG_ + std::cerr << ex.what() << std::endl;; +#endif + throw (CalciumException(CalciumTypes::CPLIEN,ex)); + // VERIFIER LES CAS DES CODES : CPINARRET, CPSTOPSEQ, CPCTVR, CPLIEN + } catch ( const Superv_Component_i::BadCast & ex) { +#ifdef _DEBUG_ + std::cerr << ex.what() << std::endl; +#endif + throw (CalciumException(CalciumTypes::CPTPVR,ex)); + } + + // mode == mode du port + CalciumTypes::DependencyType portDependencyType = port->getDependencyType(); + + if ( portDependencyType == CalciumTypes::UNDEFINED_DEPENDENCY ) + throw CalciumException(CalciumTypes::CPIT, + LOC(OSS()<<"Le mode de dépendance de la variable " + << nomVar << " est indéfini.")); + + if ( ( portDependencyType != _dependencyType ) && + ( _dependencyType != CalciumTypes::SEQUENCE_DEPENDENCY ) ) + throw CalciumException(CalciumTypes::CPITVR, + LOC(OSS()<<"Le mode de dépendance de la variable " + << nomVar << ": " << portDependencyType + << " ne correspond pas au mode demandé.")); + + + if ( _dependencyType == CalciumTypes::TIME_DEPENDENCY ) { + corbaData = port->get(ti,tf, 0); +#ifdef _DEBUG_ + std::cout << "-------- CalciumInterface(ecp_lecture) MARK 5 ------------------" << std::endl; +#endif + } + else if ( _dependencyType == CalciumTypes::ITERATION_DEPENDENCY ) { + corbaData = port->get(0, i); +#ifdef _DEBUG_ + std::cout << "-------- CalciumInterface(ecp_lecture) MARK 6 ------------------" << std::endl; +#endif + } else { + // Lecture en séquence +#ifdef _DEBUG_ + std::cout << "-------- CalciumInterface(ecp_lecture) MARK 7 ------------------" << std::endl; +#endif + corbaData = port->next(ti,i); + } + +#ifdef _DEBUG_ + std::cout << "-------- CalciumInterface(ecp_lecture) MARK 8 ------------------" << std::endl; +#endif + size_t corbaDataSize = DataManipulator::size(corbaData); +#ifdef _DEBUG_ + std::cout << "-------- CalciumInterface(ecp_lecture) corbaDataSize : " << corbaDataSize << std::endl; +#endif + + // Vérifie si l'utilisateur demande du 0 copie + if ( data == NULL ) { + if ( bufferLength != 0 ) { + MESSAGE("bufferLength devrait valoir 0 pour l'utilisation du mode sans copie (data==NULL)"); + } + nRead = corbaDataSize; + // Si les types T1 et InnerType sont différents, il faudra effectuer tout de même une recopie + if (!IsSameType::value) data = new T1[nRead]; +#ifdef _DEBUG_ + std::cout << "-------- CalciumInterface(ecp_lecture) MARK 9 ------------------" << std::endl; +#endif + // On essaye de faire du 0 copy si les types T1 et InnerType sont les mêmes. + // Copy2UserSpace : + // La raison d'être du foncteur Copy2UserSpace est que le compilateur n'acceptera + // pas une expresion d'affectation sur des types incompatibles même + // si cette expression se trouve dans une branche non exécuté d'un test + // sur la compatibilité des types. + // En utilisant le foncteur Copy2UserSpace, seul la spécialisation en adéquation + // avec la compatibilité des types sera compilée + Copy2UserSpace< IsSameType::value, DataManipulator >::apply(data,corbaData,nRead); +#ifdef _DEBUG_ + std::cout << "-------- CalciumInterface(ecp_lecture) MARK 10 ------------------" << std::endl; +#endif + // Attention : Seul CalciumCouplingPolicy via eraseDataId doit décider de supprimer ou non + // la donnée corba associée à un DataId ! Ne pas effectuer la desallocation suivante : + // DataManipulator::delete_data(corbaData); + // ni DataManipulator::getPointer(corbaData,true); qui détruit la sequence lorsque l'on + // prend la propriété du buffer + // old : Dans les deux cas la structure CORBA n'est plus utile + // old : Si !IsSameType::value l'objet CORBA est détruit avec son contenu + // old : Dans l'autre cas seul la coquille CORBA est détruite + // L'utilisateur devra appeler ecp_free qui déterminera s'il est necessaire + // de désallouer un buffer intermédiaire ( types différents) ou de rendre la propriété + } else { + nRead = std::min < size_t > (corbaDataSize,bufferLength); +#ifdef _DEBUG_ + std::cout << "-------- CalciumInterface(ecp_lecture) MARK 11 ------------------" << std::endl; +#endif + Copy2UserSpace::apply(data,corbaData,nRead); + DataManipulator::copy(corbaData,data,nRead); + +#ifdef _DEBUG_ + std::cout << "-------- CalciumInterface(ecp_lecture) MARK 12 ------------------" << std::endl; +#endif + // Attention : Seul CalciumCouplingPolicy via eraseDataId doit décider de supprimer ou non + // la donnée corba associée à un DataId ! Ne pas effectuer la desallocation suivante : + // DataManipulator::delete_data(corbaData); + } +#ifdef _DEBUG_ + std::cout << "-------- CalciumInterface(ecp_lecture), Valeur de data : " << std::endl; + std::copy(data,data+nRead,std::ostream_iterator(std::cout," ")); + std::cout << "Ptr :" << data << std::endl; + + std::cout << "-------- CalciumInterface(ecp_lecture) MARK 13 ------------------" << std::endl; +#endif + + + return; + } + + // T1 est le type de données + template static void + ecp_lecture ( Superv_Component_i & component, + int const & dependencyType, + double & ti, + double const & tf, + long & i, + const string & nomVar, + size_t bufferLength, + size_t & nRead, + T1 * &data ) + { + ecp_lecture (component,dependencyType,ti,tf, + i,nomVar,bufferLength,nRead,data); + + } + + /********************* INTERFACES D'ECRITURE *****************/ + + // T1 : DataType + // T2 : PortType + template static void + ecp_ecriture ( Superv_Component_i & component, + int const & dependencyType, + double const & t, + long const & i, + const string & nomVar, + size_t bufferLength, + T1 const & data ) + { + + assert(&component); + + //typedef typename StarTrait::NonStarType T; + typedef typename boost::remove_all_extents< T2 >::type T2_without_extent; + typedef typename boost::remove_all_extents< T1 >::type T1_without_extent; + + typedef typename UsesPortTraits ::PortType UsesPortType; + typedef typename ProvidesPortTraits::PortType ProvidesPortType;// pour obtenir un manipulateur de données + typedef typename ProvidesPortType::DataManipulator DataManipulator; + // Verifier que l'on peut définir UsesPortType::DataManipulator + // typedef typename PortType::DataManipulator DataManipulator; + typedef typename DataManipulator::Type CorbaDataType; // Attention != T1 + typedef typename DataManipulator::InnerType InnerType; + + T1_without_extent const & _data = data; + + CalciumTypes::DependencyType _dependencyType= + static_cast(dependencyType); + +#ifdef _DEBUG_ + std::cerr << "-------- CalciumInterface(ecriture) MARK 1 ------------------" << std::endl; +#endif + if ( nomVar.empty() ) throw CalciumException(CalciumTypes::CPNMVR, + LOC("Le nom de la variable est ")); + UsesPortType * port; +#ifdef _DEBUG_ + std::cout << "-------- CalciumInterface(ecriture) MARK 2 ------------------" << std::endl; +#endif + + try { + port = component.Superv_Component_i::get_port< UsesPortType > (nomVar.c_str()); +#ifdef _DEBUG_ + std::cout << "-------- CalciumInterface(ecriture) MARK 3 ------------------" << std::endl; +#endif + } catch ( const Superv_Component_i::PortNotDefined & ex) { +#ifdef _DEBUG_ + std::cerr << ex.what() << std::endl; +#endif + throw (CalciumException(CalciumTypes::CPNMVR,ex)); + } catch ( const Superv_Component_i::PortNotConnected & ex) { +#ifdef _DEBUG_ + std::cerr << ex.what() << std::endl;; +#endif + throw (CalciumException(CalciumTypes::CPLIEN,ex)); + // VERIFIER LES CAS DES CODES : CPINARRET, CPSTOPSEQ, CPCTVR, CPLIEN + } catch ( const Superv_Component_i::BadCast & ex) { +#ifdef _DEBUG_ + std::cerr << ex.what() << std::endl; +#endif + throw (CalciumException(CalciumTypes::CPTPVR,ex)); + } + + // mode == mode du port + // On pourrait créer la méthode CORBA dans le mode de Couplage CALCIUM. + // et donc ajouter cette cette méthode uniquement dans l'IDL calcium ! + +// CalciumTypes::DependencyType portDependencyType; +// try { +// portDependencyType = port->getDependencyType(); +// std::cout << "-------- CalciumInterface(ecriture) MARK 4 ------------------" << std::endl; +// } catch ( const DSC_Exception & ex ) { +// std::cerr << ex.what() << std::endl;; +// throw (CalciumException(CalciumTypes::CPIT,ex)); +// } + + if ( _dependencyType == CalciumTypes::UNDEFINED_DEPENDENCY ) + throw CalciumException(CalciumTypes::CPIT, + LOC(OSS()<<"Le mode de dépendance demandé pour la variable " + << nomVar << " est indéfini.")); + + if ( _dependencyType == CalciumTypes::SEQUENCE_DEPENDENCY ) + throw CalciumException(CalciumTypes::CPIT, + LOC(OSS()<<"Le mode de dépendance SEQUENCE_DEPENDENCY pour la variable " + << nomVar << " est impossible en écriture.")); + + // Il faudrait que le port provides génère une exception si le mode donnée n'est pas + // le bon. La seule façon de le faire est d'envoyer -1 en temps si on n'est en itération + // et vice-versa pour informer les provides port du mode dans lequel on est. Sinon il faut + // modifier l'interface IDL pour y ajouter un mode de dépendance ! + // ----> +// if ( portDependencyType != _dependencyType ) +// throw CalciumException(CalciumTypes::CPITVR, +// LOC(OSS()<<"Le mode de dépendance de la variable " +// << nomVar << " ne correspond pas au mode demandé.")); + + + if ( bufferLength < 1 ) + throw CalciumException(CalciumTypes::CPNTNULL, + LOC(OSS()<<"Le buffer a envoyer est de taille nulle ")); + + +#ifdef _DEBUG_ + std::cout << "-------- CalciumInterface(ecriture) MARK 4 ------------------" << std::endl; +#endif + CorbaDataType corbaData; + + + // Si les types Utilisateurs et CORBA sont différents + // il faut effectuer une recopie sinon on utilise directement le + // buffer data pour constituer la séquence + // TODO : + // - Attention en mode asynchrone il faudra eventuellement + // faire une copie des données même si elles sont de même type. + // - OLD : En cas de collocalisation (du port provide et du port uses) + // OLD : il est necessaire d'effectuer une recopie du buffer car la + // OLD : séquence est envoyée au port provide par une référence sur + // OLD : la séquence locale. Or la méthode put récupère le buffer directement + // OLD : qui est alors le buffer utilisateur. Il pourrait alors arriver que : + // OLD : * Le recepteur efface le buffer emetteur + // OLD : * Le port lui-même efface le buffer de l'utilisateur ! + // OLD : Cette copie est effectuée dans GenericPortUses::put + // OLD : en fonction de la collocalisation ou non. + // - OLD :En cas de connection multiples d'un port uses distant vers plusieurs port provides + // OLD : collocalisés les ports provides partagent la même copie de la donnée ! + // OLD : Il faut effectuer une copie dans le port provides. + // OLD : Cette copie est effectuée dans GenericPortUses::put + // OLD : en fonction de la collocalisation ou non. + Copy2CorbaSpace::value, DataManipulator >::apply(corbaData,_data,bufferLength); + + //TODO : GERER LES EXCEPTIONS ICI : ex le port n'est pas connecté + if ( _dependencyType == CalciumTypes::TIME_DEPENDENCY ) { + try + { + port->put(*corbaData,t, -1); + } + catch ( const DSC_Exception & ex) + { + throw (CalciumException(CalciumTypes::CPATAL,ex.what())); + } + //Le -1 peut être traité par le cst DataIdContainer et transformé en 0 + //Etre obligé de mettre une étoile ds (*corbadata) va poser des pb pour les types <> seq +#ifdef _DEBUG_ + std::cout << "-------- CalciumInterface(ecriture) MARK 5 ------------------" << std::endl; +#endif + } + else if ( _dependencyType == CalciumTypes::ITERATION_DEPENDENCY ) { + try + { + port->put(*corbaData,-1, i); + } + catch ( const DSC_Exception & ex) + { + throw (CalciumException(CalciumTypes::CPATAL,ex.what())); + } +#ifdef _DEBUG_ + std::cout << "-------- CalciumInterface(ecriture) MARK 6 ------------------" << std::endl; +#endif + } + + +#ifdef _DEBUG_ + std::cout << "-------- CalciumInterface(ecriture), Valeur de corbaData : " << std::endl; + for (int i = 0; i < corbaData->length(); ++i) + cout << "-------- CalciumInterface(ecriture), corbaData[" << i << "] = " << (*corbaData)[i] << endl; +#endif + + // if ( !IsSameType::value ) delete corbaData; + // Supprime l'objet CORBA avec eventuellement les données qu'il contient (cas de la recopie) + delete corbaData; + +#ifdef _DEBUG_ + std::cout << "-------- CalciumInterface(ecriture) MARK 7 ------------------" << std::endl; +#endif + + return; + }; + + template static void + ecp_ecriture ( Superv_Component_i & component, + int const & dependencyType, + double const & t, + long const & i, + const string & nomVar, + size_t bufferLength, + T1 const & data ) { + ecp_ecriture (component,dependencyType,t,i,nomVar,bufferLength,data); + }; + +}; + +#endif diff --git a/src/DSC/DSC_User/Datastream/Calcium/calciumE.h b/src/DSC/DSC_User/Datastream/Calcium/calciumE.h new file mode 100644 index 000000000..49c9e57d8 --- /dev/null +++ b/src/DSC/DSC_User/Datastream/Calcium/calciumE.h @@ -0,0 +1,401 @@ +/* Copyright (C) 2007 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN, + * CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com + * + * + * + * File : calcium.h + * Author : Eric Fayolle (EDF) + * Module : KERNEL + */ + +/* Outils d'Aide au Couplage de Code de Calcul : $Id$ */ + +#ifndef __CALCIUM_E_H +#define __CALCIUM_E_H + +#include + +#if defined(__STDC__) || defined(__cplusplus) || defined(c_plusplus) +#define CPNeedPrototype 1 +#else +#define CPNeedPrototype 0 +#endif + + +#if defined(__cplusplus) || defined(c_plusplus) +extern "C" { +#endif + +/* */ +/* */ +/* Fonctions de connexion */ +/* */ +/* */ +extern int ecp_cd( +/* ----- */ +#if CPNeedPrototype + void * component /* Pointeur de type Superv_Component_i* sur le */ + /* composant SALOME Supervisable */, + char * /* S Nom de l instance appelante*/ +#endif +); + + +/* */ +/* */ +/* Fonction de deconnexion */ +/* */ +/* */ +extern int ecp_fin( +/* ------ */ +#if CPNeedPrototype + void * component /* Pointeur de type Superv_Component_i* sur le */ + /* composant SALOME Supervisable */, + int /* E Directive de continuation */ + /* CP_CONT ou CP_ARRET */ +#endif +); + +/* */ +/* */ +/* Fonctions de libération du buffer 0 copy */ +/* */ + + extern void ecp_len_free( +#if CPNeedPrototype + int * +#endif + ); + extern void ecp_lre_free( +#if CPNeedPrototype + float * +#endif + ); + extern void ecp_ldb_free( +#if CPNeedPrototype + double * +#endif + ); + extern void ecp_llo_free( +#if CPNeedPrototype + int * +#endif + ); + extern void ecp_lcp_free( +#if CPNeedPrototype + float * +#endif + ); + extern void ecp_lch_free( +#if CPNeedPrototype + char ** +#endif + ); + + + +/* */ +/* */ +/* Fonctions de lecture bloquante 0 copy */ +/* */ +/* */ +extern int ecp_len( +/* ------ */ +#if CPNeedPrototype + void * component /* Pointeur de type Superv_Component_i* sur le */ + /* composant SALOME Supervisable */, + int /* E Type de dependance ou de lecture */ + /* CP_TEMPS, CP_ITERATION, CP_SEQUENTIEL */, + float * /* E/S Borne inf de l'intervalle de lecture */ + /* Retourne le pas lu dans le cas de */ + /* lecture sequentielle */, + float * /* E Borne Sup de l'intervalle de lecture */, + int * /* E/S Pas d'iteration a lire */ + /* Retourne le pas lu dans le cas de */ + /* lecture sequentielle */, + char * /* E Nom de la variable a lire */, + int /* E Nombre max de valeurs a lire */, + int * /* S Nombre de valeurs rellement lues */, + int ** /* E/S Tableau d'entiers pour stocker les */ + /* valeurs lues */ +#endif +); + +extern int ecp_lre( +/* ------ */ +#if CPNeedPrototype + void * component /* Pointeur de type Superv_Component_i* sur le */ + /* composant SALOME Supervisable */, + int /* E Type de dependance ou de lecture */ + /* CP_TEMPS, CP_ITERATION, CP_SEQUENTIEL */, + float * /* E/S Borne inf de l'intervalle de lecture */ + /* Retourne le pas lu dans le cas de */ + /* lecture sequentielle */, + float * /* E Borne Sup de l'intervalle de lecture */, + int * /* E/S Pas d'iteration a lire */ + /* Retourne le pas lu dans le cas de */ + /* lecture sequentielle */, + char * /* E Nom de la variable a lire */, + int /* E Nombre max de valeurs a lire */, + int * /* S Nombre de valeurs rellement lues */, + float **/* E/S Tableau de flottants pour stocker les */ + /* valeurs lues */ +#endif +); + +extern int ecp_ldb( +/* ------ */ +#if CPNeedPrototype + void * component /* Pointeur de type Superv_Component_i* sur le */ + /* composant SALOME Supervisable */, + int /* E Type de dependance ou de lecture */ + /* CP_TEMPS, CP_ITERATION, CP_SEQUENTIEL */, + double* /* E/S Borne inf de l'intervalle de lecture */ + /* Retourne le pas lu dans le cas de */ + /* lecture sequentielle */, + double* /* E Borne Sup de l'intervalle de lecture */, + int * /* E/S Pas d'iteration a lire */ + /* Retourne le pas lu dans le cas de */ + /* lecture sequentielle */, + char * /* E Nom de la variable a lire */, + int /* E Nombre max de valeurs a lire */, + int * /* S Nombre de valeurs rellement lues */, + double**/* E/S Tableau de doubles pour stocker les */ + /* valeurs lues */ +#endif +); + +extern int ecp_lcp( +/* ------ */ +#if CPNeedPrototype + void * component /* Pointeur de type Superv_Component_i* sur le */ + /* composant SALOME Supervisable */, + int /* E Type de dependance ou de lecture */ + /* CP_TEMPS, CP_ITERATION, CP_SEQUENTIEL */, + float * /* E/S Borne inf de l'intervalle de lecture */ + /* Retourne le pas lu dans le cas de */ + /* lecture sequentielle */, + float * /* E Borne Sup de l'intervalle de lecture */, + int * /* E/S Pas d'iteration lire */ + /* Retourne le pas lu dans le cas de */ + /* lecture sequentielle */, + char * /* E Nom de la variable a lire */, + int /* E Nombre max de valeurs a lire */, + int * /* S Nombre de valeurs rellement lues */, + float **/* E/S Tableau de flottants pour stocker les */ + /* valeurs lues (dimension = 2 * le nombre */ + /* de valeurs lues) */ +#endif +); + +extern int ecp_llo( +/* ------ */ +#if CPNeedPrototype + void * component /* Pointeur de type Superv_Component_i* sur le */ + /* composant SALOME Supervisable */, + int /* E Type de dependance ou de lecture */ + /* CP_TEMPS, CP_ITERATION, CP_SEQUENTIEL */, + float * /* E/S Borne inf de l'intervalle de lecture */ + /* Retourne le pas lu dans le cas de */ + /* lecture sequentielle */, + float * /* E Borne Sup de l'intervalle de lecture */, + int * /* E/S Pas d'iteration a lire */ + /* Retourne le pas lu dans le cas de */ + /* lecture sequentielle */, + char * /* E Nom de la variable a lire */, + int /* E Nombre max de valeurs a lire */, + int * /* S Nombre de valeurs rellement lues */, + int ** /* E/S Tableau d 'entier pour stocker les */ + /* valeurs lues (remplace le logiques) */ +#endif +); + +extern int ecp_lch( +/* ------ */ +#if CPNeedPrototype + void * component /* Pointeur de type Superv_Component_i* sur le */ + /* composant SALOME Supervisable */, + int /* E Type de dependance ou de lecture */ + /* CP_TEMPS, CP_ITERATION, CP_SEQUENTIEL */, + float * /* E/S Borne inf de l'intervalle de lecture */ + /* Retourne le pas lu dans le cas de */ + /* lecture sequentielle */, + float * /* E Borne Sup de l'intervalle de lecture */, + int * /* E/S Pas d'iteration a lire */ + /* Retourne le pas lu dans le cas de */ + /* lecture sequentielle */, + char * /* E Nom de la variable a lire */, + int /* E Nombre max de valeurs a lire */, + int * /* S Nombre de valeurs rellement lues */, + char **[]/*E/S Tableau de chaines pour stocker les */ + /* valeurs lues (remplace le logiques) */, + int /* E Taille des chaines du tablaeu */ +#endif +); + + + +/* */ +/* */ +/* Fonctions de lecture non bloquantes */ +/* */ +/* */ +extern int ecp_nlen( +/* ------- */ +#if CPNeedPrototype + void * component /* Pointeur de type Superv_Component_i* sur le */ + /* composant SALOME Supervisable */, + int /* E Type de dependance ou de lecture */ + /* CP_TEMPS, CP_ITERATION, CP_SEQUENTIEL */, + float * /* E/S Borne inf de l'intervalle de lecture */ + /* Retourne le pas lu dans le cas de */ + /* lecture sequentielle */, + float * /* E Borne Sup de l'intervalle de lecture */, + int * /* E/S Pas d'iteration a lire */ + /* Retourne le pas lu dans le cas de */ + /* lecture sequentielle */, + char * /* E Nom de la variable a lire */, + int /* E Nombre max de valeurs a lire */, + int * /* S Nombre de valeurs rellement lues */, + int ** /* E/S Tableau d'entiers pour stocker les */ + /* valeurs lues */ +#endif +); + +extern int ecp_nlre( +/* ------- */ +#if CPNeedPrototype + void * component /* Pointeur de type Superv_Component_i* sur le */ + /* composant SALOME Supervisable */, + int /* E Type de dependance ou de lecture */ + /* CP_TEMPS, CP_ITERATION, CP_SEQUENTIEL */, + float * /* E/S Borne inf de l'intervalle de lecture */ + /* Retourne le pas lu dans le cas de */ + /* lecture sequentielle */, + float * /* E Borne Sup de l'intervalle de lecture */, + int * /* E/S Pas d'iteration a lire */ + /* Retourne le pas lu dans le cas de */ + /* lecture sequentielle */, + char * /* E Nom de la variable a lire */, + int /* E Nombre max de valeurs a lire */, + int * /* S Nombre de valeurs rellement lues */, + float **/* E/S Tableau de flottants pour stocker les */ + /* valeurs lues */ +#endif +); + +extern int ecp_nldb( +/* ------- */ + +#if CPNeedPrototype + void * component /* Pointeur de type Superv_Component_i* sur le */ + /* composant SALOME Supervisable */, + int /* E Type de dependance ou de lecture */ + /* CP_TEMPS, CP_ITERATION, CP_SEQUENTIEL */, + double */* E/S Borne inf de l'intervalle de lecture */ + /* Retourne le pas lu dans le cas de */ + /* lecture sequentielle */, + double */* E Borne Sup de l'intervalle de lecture */, + int * /* E/S Pas d'iteration a lire */ + /* Retourne le pas lu dans le cas de */ + /* lecture sequentielle */, + char * /* E Nom de la variable a lire */, + int /* E Nombre max de valeurs a lire */, + int * /* S Nombre de valeurs rellement lues */, + double**/* E/S Tableau de doubles pour stocker les */ + /* valeurs lues */ +#endif +); + +extern int ecp_nlcp( +/* ------- */ +#if CPNeedPrototype + void * component /* Pointeur de type Superv_Component_i* sur le */ + /* composant SALOME Supervisable */, + int /* E Type de dependance ou de lecture */ + /* CP_TEMPS, CP_ITERATION, CP_SEQUENTIEL */, + float * /* E/S Borne inf de l'intervalle de lecture */ + /* Retourne le pas lu dans le cas de */ + /* lecture sequentielle */, + float * /* E Borne Sup de l'intervalle de lecture */, + int * /* E/S Pas d'iteration lire */ + /* Retourne le pas lu dans le cas de */ + /* lecture sequentielle */, + char * /* E Nom de la variable a lire */, + int /* E Nombre max de valeurs a lire */, + int * /* S Nombre de valeurs rellement lues */, + float **/* E/S Tableau de flottants pour stocker les */ + /* valeurs lues (dimension = 2 * le nombre */ + /* de valeurs lues) */ +#endif +); + +extern int ecp_nllo( +/* ------- */ +#if CPNeedPrototype + void * component /* Pointeur de type Superv_Component_i* sur le */ + /* composant SALOME Supervisable */, + int /* E Type de dependance ou de lecture */ + /* CP_TEMPS, CP_ITERATION, CP_SEQUENTIEL */, + float * /* E/S Borne inf de l'intervalle de lecture */ + /* Retourne le pas lu dans le cas de */ + /* lecture sequentielle */, + float * /* E Borne Sup de l'intervalle de lecture */, + int * /* E/S Pas d'iteration a lire */ + /* Retourne le pas lu dans le cas de */ + /* lecture sequentielle */, + char * /* E Nom de la variable a lire */, + int /* E Nombre max de valeurs a lire */, + int * /* S Nombre de valeurs rellement lues */, + int **/* E/S Tableau d 'entier pour stocker les */ + /* valeurs lues (remplace le logiques) */ +#endif +); + +extern int ecp_nlch( +/* ------- */ +#if CPNeedPrototype + void * component /* Pointeur de type Superv_Component_i* sur le */ + /* composant SALOME Supervisable */, + int /* E Type de dependance ou de lecture */ + /* CP_TEMPS, CP_ITERATION, CP_SEQUENTIEL */, + float * /* E/S Borne inf de l'intervalle de lecture */ + /* Retourne le pas lu dans le cas de */ + /* lecture sequentielle */, + float * /* E Borne Sup de l'intervalle de lecture */, + int * /* E/S Pas d'iteration a lire */ + /* Retourne le pas lu dans le cas de */ + /* lecture sequentielle */, + char * /* E Nom de la variable a lire */, + int /* E Nombre max de valeurs a lire */, + int * /* S Nombre de valeurs rellement lues */, + char **[]/* E/S Tableau de chaines pour stocker les */ + /* valeurs lues (remplace le logiques) */, + int /* E Taille des chaines du tablaeu */ +#endif +); + + + +#if defined(__cplusplus) || defined(c_plusplus) +} +#endif + + +#endif diff --git a/src/Launcher/BatchTest.cxx b/src/Launcher/BatchTest.cxx new file mode 100644 index 000000000..bb44166f4 --- /dev/null +++ b/src/Launcher/BatchTest.cxx @@ -0,0 +1,689 @@ +#include "BatchTest.hxx" + +BatchTest::BatchTest(const Engines::MachineParameters& batch_descr) +{ + _batch_descr = batch_descr; + + // Getting date + Batch::Date date = Batch::Date(time(0)); + _date = date.str(); + int lend = _date.size() ; + int i = 0 ; + while (i < lend) + { + if (_date[i] == '/' || _date[i] == '-' || _date[i] == ':' ) + { + _date[i] = '_' ; + } + i++ ; + } + + // Creating test temporary file + _test_filename = "/tmp/"; + _test_filename += _date + "_test_cluster_file_"; + _test_filename += _batch_descr.alias.in(); + _base_filename = _date + "_test_cluster_file_" + _batch_descr.alias.in(); +} + +BatchTest::~BatchTest() {} + +bool +BatchTest::test() +{ + bool rtn = false; + INFOS(std::endl + << "--- Testing batch Machine :" << std::endl + << "--- Name : " << _batch_descr.hostname << std::endl + << "--- Alias : " << _batch_descr.alias << std::endl + << "--- Protocol : " << _batch_descr.protocol << std::endl + << "--- User Name : " << _batch_descr.username << std::endl + << "--- Batch Type : " << _batch_descr.batch << std::endl + << "--- MPI Impl : " << _batch_descr.mpiImpl << std::endl + << "--- Appli Path : " << _batch_descr.applipath << std::endl + ); + + std::string result_connection("Not Tested"); + std::string result_filecopy("Not Tested"); + std::string result_getresult("Not Tested"); + std::string result_jobsubmit_simple("Not Tested"); + std::string result_jobsubmit_mpi("Not Tested"); + std::string result_appli("Not Tested"); + + result_connection = test_connection(); + result_filecopy = test_filecopy(); + result_getresult = test_getresult(); + result_jobsubmit_simple = test_jobsubmit_simple(); + result_jobsubmit_mpi = test_jobsubmit_mpi(); + result_appli = test_appli(); + + INFOS(std::endl + << "--- Test results" << std::endl + << "--- Connection : " << result_connection << std::endl + << "--- File copy : " << result_filecopy << std::endl + << "--- Get results : " << result_getresult << std::endl + << "--- Submit simple job : " << result_jobsubmit_simple << std::endl + << "--- Submit mpi job : " << result_jobsubmit_mpi << std::endl + << "--- Application : " << result_appli << std::endl + ); + + if (result_connection == "OK" and + result_filecopy == "OK" and + result_getresult == "OK" and + result_jobsubmit_simple == "OK" and + result_jobsubmit_mpi == "OK" and + result_appli == "OK") + rtn = true; + + return rtn; +} + +// For this test we use : alias, protocol, username +std::string +BatchTest::test_connection() +{ + int status; + std::string command; + std::string result("Failed : "); + std::string alias = _batch_descr.alias.in(); + std::string username = _batch_descr.username.in(); + std::string protocol = _batch_descr.protocol.in(); + + // Basic tests + if(alias == "") + { + result += "alias is empty !"; + return result; + } + if(username == "") + { + result += "username is empty !"; + return result; + } + if( protocol != "rsh" and protocol != "ssh") + { + result += "protocol unknown ! (" + protocol + ")"; + return result; + } + + // Build command + command += protocol + + " " + + username + "@" + alias; + + // Test + status = system(command.c_str()); + if(status) { + std::ostringstream oss; + oss << status; + result += "Error of connection on remote host ! status = "; + result += oss.str(); + return result; + } + + result = "OK"; + return result; +} + +// For this test we use : alias, protocol, username +std::string +BatchTest::test_filecopy() +{ + int status; + std::string home; + std::string command; + std::string result("Failed : "); + std::string alias = _batch_descr.alias.in(); + std::string username = _batch_descr.username.in(); + std::string protocol = _batch_descr.protocol.in(); + + // Getting home directory + std::string rst = get_home(&home); + if(rst != "") { + result += rst; + return result; + } + + // Writing into the tempory file + command = "echo Hello > " + _test_filename; + status = system(command.c_str()); + if(status) { + std::ostringstream oss; + oss << status; + result += "Error in creating tempory file ! status = "; + result += oss.str(); + return result; + } + + // Build command + command = "scp"; + if(protocol == "rsh") + command = "rcp"; + command += " " + _test_filename + " " + + username + "@" + alias + ":" + home; + + // Test + status = system(command.c_str()); + if(status) { + std::ostringstream oss; + oss << status; + result += "Error in copy file on remote host ! status = "; + result += oss.str(); + return result; + } + + result = "OK"; + return result; +} + +// For this test we use : alias, protocol, username +std::string +BatchTest::test_getresult() +{ + int status; + std::string home; + std::string command; + std::string result("Failed : "); + std::string alias = _batch_descr.alias.in(); + std::string username = _batch_descr.username.in(); + std::string protocol = _batch_descr.protocol.in(); + + // Getting home directory + std::string rst = get_home(&home); + if(rst != "") { + result += rst; + return result; + } + + // Build command + command = "scp"; + if(protocol == "rsh") + command = "rcp"; + command += " " + username + "@" + alias + ":" + home + + "/" + _base_filename + " " + _test_filename + "_copy"; + + // Test + status = system(command.c_str()); + if(status) { + std::ostringstream oss; + oss << status; + result += "Error in copy file from remote host ! status = "; + result += oss.str(); + return result; + } + + // Compare files + std::ifstream src_file(_test_filename.c_str()); + if (!src_file) + { + result += "Error in reading temporary file ! filename = " + _test_filename; + return result; + } + std::string cp_filename = _test_filename + "_copy"; + std::ifstream cp_file(cp_filename.c_str()); + if (!cp_file) + { + result += "Error in reading temporary copy file ! filename = " + cp_filename; + return result; + } + std::string src_firstline; + std::string cp_firstline; + std::getline(src_file, src_firstline); + std::getline(cp_file, cp_firstline); + src_file.close(); + cp_file.close(); + if (src_firstline != cp_firstline) + { + result += "Error source file and copy file are not equa ! source = " + src_firstline + " copy = " + cp_firstline; + return result; + } + + result = "OK"; + return result; +} + +std::string +BatchTest::test_jobsubmit_simple() +{ + int status; + std::string home; + std::string command; + std::string result("Failed : "); + std::string alias = _batch_descr.alias.in(); + std::string username = _batch_descr.username.in(); + std::string protocol = _batch_descr.protocol.in(); + std::string batch_type = _batch_descr.batch.in(); + + // Basic test + if (batch_type == "slurm") + { + INFOS("test_jobsubmit_simple not yet implemented for slurm... return OK"); + result = "OK"; + return result; + } + if (batch_type != "pbs") + { + result += "Batch type unknown ! : " + batch_type; + return result; + } + + // Getting home directory + std::string rst = get_home(&home); + if(rst != "") { + result += rst; + return result; + } + + // PBS test + std::string _test_file_simple = _test_filename + "_simple"; + std::ofstream file; + file.open(_test_file_simple.c_str(), std::ofstream::out); + file << "#!/bin/bash\n" + << "#PBS -l nodes=1\n" + << "#PBS -l walltime=00:01:00\n" + << "#PBS -o " + home + "/" + _date + "_simple_output.log\n" + << "#PBS -e " + home + "/" + _date + "_simple_error.log\n" + << "echo Bonjour\n" + << "echo Error >&2\n"; + file.flush(); + file.close(); + + + // Build command for copy + command = "scp"; + if(protocol == "rsh") + command = "rcp"; + command += " " + _test_file_simple + " " + + username + "@" + alias + ":" + home; + status = system(command.c_str()); + if(status) { + std::ostringstream oss; + oss << status; + result += "Error in copy job file to remote host ! status = "; + result += oss.str(); + return result; + } + + // Build command for submit job + std::string file_job_name = _test_filename + "_jobid"; + command = protocol + " " + username + "@" + alias + " qsub " + _base_filename + "_simple > " + file_job_name; + status = system(command.c_str()); + if(status) { + std::ostringstream oss; + oss << status; + result += "Error in sending qsub to remote host ! status = "; + result += oss.str(); + return result; + } + std::string jobid; + std::ifstream file_job(file_job_name.c_str()); + if (!file_job) + { + result += "Error in reading temporary file ! filename = " + file_job_name; + return result; + } + std::getline(file_job, jobid); + file_job.close(); + + // Wait the end of the job + command = protocol + " " + username + "@" + alias + " qstat -f " + jobid + " > " + file_job_name; + bool stop = false; + while (!stop) + { + status = system(command.c_str()); + if(status && status != 153 && status != 256*153) + { + std::ostringstream oss; + oss << status; + result += "Error in sending qstat to remote host ! status = "; + result += oss.str(); + return result; + } + + if(status == 153 || status == 256*153 ) + stop = true; + sleep(1); + } + + // Build command for getting results + command = "scp"; + if(protocol == "rsh") + command = "rcp"; + command += " " + + username + "@" + alias + ":" + home + "/" + _date + "_simple* /tmp"; + status = system(command.c_str()); + if(status) { + std::ostringstream oss; + oss << status; + result += "error in getting file result of qsub simple to remote host ! status = "; + result += oss.str(); + return result; + } + + // Test results + std::string normal_input; + std::string file_normal_name = "/tmp/" + _date + "_simple_output.log"; + std::ifstream file_normal(file_normal_name.c_str()); + if (!file_normal) + { + result += "Error in reading temporary file ! filename = " + file_normal_name; + return result; + } + std::getline(file_normal, normal_input); + file_normal.close(); + if (normal_input != "Bonjour") + { + result += "error from simple ouput file ! waiting for Bonjour and get : " + normal_input; + return result; + } + std::string error_input; + std::string file_error_name = "/tmp/" + _date + "_simple_error.log"; + std::ifstream file_error(file_error_name.c_str()); + if (!file_error) + { + result += "Error in reading temporary file ! filename = " + file_error_name; + return result; + } + std::getline(file_error, error_input); + file_error.close(); + if (error_input != "Error") + { + result += "error from simple error file ! waiting for Error and get : " + error_input; + return result; + } + result = "OK"; + return result; +} + +std::string +BatchTest::test_jobsubmit_mpi() +{ + int status; + std::string home; + std::string command; + MpiImpl * mpiImpl; + std::string result("Failed : "); + std::string alias = _batch_descr.alias.in(); + std::string username = _batch_descr.username.in(); + std::string protocol = _batch_descr.protocol.in(); + std::string batch_type = _batch_descr.batch.in(); + std::string mpi_type = _batch_descr.mpiImpl.in(); + + // Basic test + if(mpi_type == "lam") + mpiImpl = new MpiImpl_LAM(); + else if(mpi_type == "mpich1") + mpiImpl = new MpiImpl_MPICH1(); + else if(mpi_type == "mpich2") + mpiImpl = new MpiImpl_MPICH2(); + else if(mpi_type == "openmpi") + mpiImpl = new MpiImpl_OPENMPI(); + else + { + result += "Error MPI impl not supported : " + mpi_type; + return result; + } + + // SLURM not yet implemented... + if (batch_type == "slurm") + { + INFOS("test_jobsubmit_simple not yet implemented for slurm... return OK"); + result = "OK"; + return result; + } + + // Getting home directory + std::string rst = get_home(&home); + if(rst != "") { + result += rst; + return result; + } + + // MPI test + std::string _test_file_script = _test_filename + "_script"; + std::ofstream file_script; + file_script.open(_test_file_script.c_str(), std::ofstream::out); + file_script << "#!/bin/bash\n" + << "echo HELLO MPI\n"; + file_script.flush(); + file_script.close(); + chmod(_test_file_script.c_str(), 0x1ED); + + std::string _test_file_mpi = _test_filename + "_mpi"; + std::ofstream file_mpi; + file_mpi.open(_test_file_mpi.c_str(), std::ofstream::out); + file_mpi << "#!/bin/bash\n" + << "#PBS -l nodes=1\n" + << "#PBS -l walltime=00:01:00\n" + << "#PBS -o " << home << "/" << _date << "_mpi_output.log\n" + << "#PBS -e " << home << "/" << _date << "_mpi_error.log\n" + << mpiImpl->boot("${PBS_NODEFILE}", 1) + << mpiImpl->run("${PBS_NODEFILE}", 1, _base_filename + "_script") + << mpiImpl->halt(); + file_mpi.flush(); + file_mpi.close(); + + + // Build command for copy + command = "scp"; + if(protocol == "rsh") + command = "rcp"; + command += " " + _test_file_script + " " + + username + "@" + alias + ":" + home; + status = system(command.c_str()); + if(status) { + std::ostringstream oss; + oss << status; + result += "Error in copy job file to remote host ! status = "; + result += oss.str(); + return result; + } + command = "scp"; + if(protocol == "rsh") + command = "rcp"; + command += " " + _test_file_mpi + " " + + username + "@" + alias + ":" + home; + status = system(command.c_str()); + if(status) { + std::ostringstream oss; + oss << status; + result += "Error in copy job file to remote host ! status = "; + result += oss.str(); + return result; + } + + // Build command for submit job + std::string file_job_name = _test_filename + "_jobid"; + command = protocol + " " + username + "@" + alias + " qsub " + _base_filename + "_mpi > " + file_job_name; + status = system(command.c_str()); + if(status) { + std::ostringstream oss; + oss << status; + result += "Error in sending qsub to remote host ! status = "; + result += oss.str(); + return result; + } + std::string jobid; + std::ifstream file_job(file_job_name.c_str()); + if (!file_job) + { + result += "Error in reading temporary file ! filename = " + file_job_name; + return result; + } + std::getline(file_job, jobid); + file_job.close(); + + // Wait the end of the job + command = protocol + " " + username + "@" + alias + " qstat -f " + jobid + " > " + file_job_name; + bool stop = false; + while (!stop) + { + status = system(command.c_str()); + if(status && status != 153 && status != 256*153) + { + std::ostringstream oss; + oss << status; + result += "Error in sending qstat to remote host ! status = "; + result += oss.str(); + return result; + } + + if(status == 153 || status == 256*153 ) + stop = true; + sleep(1); + } + + // Build command for getting results + command = "scp"; + if(protocol == "rsh") + command = "rcp"; + command += " " + + username + "@" + alias + ":" + home + "/" + _date + "_mpi* /tmp"; + status = system(command.c_str()); + if(status) { + std::ostringstream oss; + oss << status; + result += "error in getting file result of qsub mpi from remote host ! status = "; + result += oss.str(); + return result; + } + + // Test results + std::string normal_input; + std::string file_normal_name = "/tmp/" + _date + "_mpi_output.log"; + std::ifstream file_normal(file_normal_name.c_str()); + if (!file_normal) + { + result += "Error in reading temporary file ! filename = " + file_normal_name; + return result; + } + bool test_ok = false; + while (std::getline(file_normal, normal_input)) + { + if (normal_input == "HELLO MPI") + test_ok = true; + } + file_normal.close(); + if (!test_ok) + { + result += "error from mpi ouput file ! waiting for HELLO MPI please watch /tmp/" + _date + "_mpi_output.log file"; + return result; + } + result = "OK"; + return result; +} + +std::string +BatchTest::test_appli() +{ + int status; + std::string home; + std::string command; + std::string result("Failed : "); + std::string alias = _batch_descr.alias.in(); + std::string username = _batch_descr.username.in(); + std::string protocol = _batch_descr.protocol.in(); + std::string applipath = _batch_descr.applipath.in(); + + // Getting home directory + std::string rst = get_home(&home); + if(rst != "") { + result += rst; + return result; + } + + std::string _test_file_appli = _test_filename + "_appli_test"; + std::ofstream file_appli; + file_appli.open(_test_file_appli.c_str(), std::ofstream::out); + file_appli << "#!/bin/bash\n" + << "if [ -f " << applipath << "/runAppli ]\n" + << "then\n" + << " echo OK\n" + << "else\n" + << " echo NOK\n" + << "fi\n"; + file_appli.flush(); + file_appli.close(); + + // Build command for copy + command = "scp"; + if(protocol == "rsh") + command = "rcp"; + command += " " + _test_file_appli + " " + + username + "@" + alias + ":" + home; + status = system(command.c_str()); + if(status) { + std::ostringstream oss; + oss << status; + result += "Error in copy appli test file to remote host ! status = "; + result += oss.str(); + return result; + } + + // Launch test + command = protocol + " " + username + "@" + alias + + " sh " + home + "/" + _base_filename + "_appli_test > " + + _test_filename + "_appli_test_result"; + + status = system(command.c_str()); + if(status) { + std::ostringstream oss; + oss << status; + result += "Error in launching appli test on remote host ! status = "; + result += oss.str(); + return result; + } + + // Read test result + std::string rst_appli; + std::string file_appli_result_name = _test_filename + "_appli_test_result"; + std::ifstream file_appli_result(file_appli_result_name.c_str()); + if (!file_appli_result) + { + result += "Error in reading temporary file ! filename = " + file_appli_result_name; + return result; + } + std::getline(file_appli_result, rst_appli); + file_appli_result.close(); + + if (rst_appli != "OK") + { + result += "Error checking application on remote host ! result = " + rst; + return result; + } + + result = "OK"; + return result; +} + +// Useful methods +std::string +BatchTest::get_home(std::string * home) +{ + int status; + std::string result = ""; + std::string command; + std::string alias = _batch_descr.alias.in(); + std::string username = _batch_descr.username.in(); + std::string protocol = _batch_descr.protocol.in(); + std::string file_home_name = _test_filename + "_home"; + + command = protocol + " " + username + "@" + alias + " 'echo $HOME' > " + file_home_name; + status = system(command.c_str()); + if(status) { + std::ostringstream oss; + oss << status; + result += "Error in getting home directory ! status = "; + result += oss.str(); + return result; + } + + std::ifstream file_home(file_home_name.c_str()); + if (!file_home) + { + result += "Error in reading temporary file ! filename = " + file_home_name; + return result; + } + std::getline(file_home, *home); + file_home.close(); + return result; +} diff --git a/src/Launcher/BatchTest.hxx b/src/Launcher/BatchTest.hxx new file mode 100644 index 000000000..600b71bee --- /dev/null +++ b/src/Launcher/BatchTest.hxx @@ -0,0 +1,60 @@ +// Copyright (C) 2008 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN, +// CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License. +// +// This library is distributed in the hope that it will be useful +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// + +#ifndef __BatchTest_HXX__ +#define __BatchTest_HXX__ + +#include +#include +#include + +#include +#include "utilities.h" +#include CORBA_CLIENT_HEADER(SALOME_ContainerManager) + +#include "Batch_Date.hxx" +#include "MpiImpl.hxx" + +class BatchTest +{ + public: + BatchTest(const Engines::MachineParameters& batch_descr); + virtual ~BatchTest(); + + bool test(); + + std::string test_connection(); + std::string test_filecopy(); + std::string test_getresult(); + std::string test_jobsubmit_simple(); + std::string test_jobsubmit_mpi(); + std::string test_appli(); + + protected: + std::string get_home(std::string * home); + + private: + Engines::MachineParameters _batch_descr; + std::string _test_filename; + std::string _base_filename; + std::string _date; +}; + +#endif diff --git a/src/Launcher/Launcher.cxx b/src/Launcher/Launcher.cxx new file mode 100644 index 000000000..8a92e1d87 --- /dev/null +++ b/src/Launcher/Launcher.cxx @@ -0,0 +1,628 @@ +// Copyright (C) 2005 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN, +// CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License. +// +// This library is distributed in the hope that it will be useful +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// +#include "Batch_Date.hxx" +#include "Batch_FactBatchManager_eLSF.hxx" +#include "Batch_FactBatchManager_ePBS.hxx" +#include "Launcher.hxx" +#include +#include +#include + +using namespace std; + +//============================================================================= +/*! + * Constructor + * \param orb + * Define a CORBA single thread policy for the server, which avoid to deal + * with non thread-safe usage like Change_Directory in SALOME naming service + */ +//============================================================================= + +Launcher_cpp::Launcher_cpp() +{ + cerr << "Launcher_cpp constructor" << endl; +} + +//============================================================================= +/*! + * destructor + */ +//============================================================================= + +Launcher_cpp::~Launcher_cpp() +{ + cerr << "Launcher_cpp destructor" << endl; + std::map < string, Batch::BatchManager_eClient * >::const_iterator it1; + for(it1=_batchmap.begin();it1!=_batchmap.end();it1++) + delete it1->second; + std::map < std::pair , Batch::Job* >::const_iterator it2; + for(it2=_jobmap.begin();it2!=_jobmap.end();it2++) + delete it2->second; +} + +//============================================================================= +/*! CORBA Method: + * Submit a batch job on a cluster and returns the JobId + * \param fileToExecute : .py/.exe/.sh/... to execute on the batch cluster + * \param filesToExport : to export on the batch cluster + * \param NumberOfProcessors : Number of processors needed on the batch cluster + * \param params : Constraints for the choice of the batch cluster + */ +//============================================================================= +long Launcher_cpp::submitSalomeJob( const string fileToExecute , + const vector& filesToExport , + const vector& filesToImport , + const batchParams& batch_params, + const machineParams& params) throw(LauncherException) +{ + cerr << "BEGIN OF Launcher_cpp::submitSalomeJob" << endl; + long jobId; + vector aMachineList; + + // check batch params + if ( !check(batch_params) ) + throw LauncherException("Batch parameters are bad (see informations above)"); + + // find a cluster matching the structure params + vector aCompoList ; + try{ + aMachineList = _ResManager->GetFittingResources(params, aCompoList); + } + catch(const ResourcesException &ex){ + throw LauncherException(ex.msg.c_str()); + } + if (aMachineList.size() == 0) + throw LauncherException("No resources have been found with your parameters"); + + ParserResourcesType p = _ResManager->GetResourcesList(aMachineList[0]); + string clustername(p.Alias); + cerr << "Choose cluster: " << clustername << endl; + + // search batch manager for that cluster in map or instanciate one + map < string, Batch::BatchManager_eClient * >::const_iterator it = _batchmap.find(clustername); + if(it == _batchmap.end()) + { + _batchmap[clustername] = FactoryBatchManager(p); + // TODO: Add a test for the cluster ! + } + + try{ + // tmp directory on cluster to put files to execute + string tmpdir = getTmpDirForBatchFiles(); + + // create and submit job on cluster + Batch::Parametre param; + param[USER] = p.UserName; + param[EXECUTABLE] = buildSalomeCouplingScript(fileToExecute,tmpdir,p); + param[INFILE] = Batch::Couple( fileToExecute, getRemoteFile(tmpdir,fileToExecute) ); + for(int i=0;isubmitJob(*job); + + // get job id in long + istringstream iss(jid.getReference()); + iss >> jobId; + + _jobmap[ pair(clustername,jobId) ] = job; + } + catch(const Batch::EmulationException &ex){ + throw LauncherException(ex.msg.c_str()); + } + + return jobId; +} + +//============================================================================= +/*! CORBA Method: + * Query a batch job on a cluster and returns the status of job + * \param jobId : identification of Salome job + * \param params : Constraints for the choice of the batch cluster + */ +//============================================================================= +string Launcher_cpp::querySalomeJob( long id, + const machineParams& params) throw(LauncherException) +{ + // find a cluster matching params structure + vector aCompoList ; + vector aMachineList = _ResManager->GetFittingResources( params , aCompoList ) ; + ParserResourcesType p = _ResManager->GetResourcesList(aMachineList[0]); + string clustername(p.Alias); + + // search batch manager for that cluster in map + std::map < string, Batch::BatchManager_eClient * >::const_iterator it = _batchmap.find(clustername); + if(it == _batchmap.end()) + throw LauncherException("no batchmanager for that cluster"); + + ostringstream oss; + oss << id; + Batch::JobId jobId( _batchmap[clustername], oss.str() ); + + Batch::JobInfo jinfo = jobId.queryJob(); + Batch::Parametre par = jinfo.getParametre(); + return par[STATE]; +} + +//============================================================================= +/*! CORBA Method: + * Delete a batch job on a cluster + * \param jobId : identification of Salome job + * \param params : Constraints for the choice of the batch cluster + */ +//============================================================================= +void Launcher_cpp::deleteSalomeJob( const long id, + const machineParams& params) throw(LauncherException) +{ + // find a cluster matching params structure + vector aCompoList ; + vector aMachineList = _ResManager->GetFittingResources( params , aCompoList ) ; + ParserResourcesType p = _ResManager->GetResourcesList(aMachineList[0]); + string clustername(p.Alias); + + // search batch manager for that cluster in map + map < string, Batch::BatchManager_eClient * >::const_iterator it = _batchmap.find(clustername); + if(it == _batchmap.end()) + throw LauncherException("no batchmanager for that cluster"); + + ostringstream oss; + oss << id; + Batch::JobId jobId( _batchmap[clustername], oss.str() ); + + jobId.deleteJob(); +} + +//============================================================================= +/*! CORBA Method: + * Get result files of job on a cluster + * \param jobId : identification of Salome job + * \param params : Constraints for the choice of the batch cluster + */ +//============================================================================= +void Launcher_cpp::getResultSalomeJob( const string directory, + const long id, + const machineParams& params) throw(LauncherException) +{ + vector aCompoList ; + vector aMachineList = _ResManager->GetFittingResources( params , aCompoList ) ; + ParserResourcesType p = _ResManager->GetResourcesList(aMachineList[0]); + string clustername(p.Alias); + + // search batch manager for that cluster in map + map < string, Batch::BatchManager_eClient * >::const_iterator it = _batchmap.find(clustername); + if(it == _batchmap.end()) + throw LauncherException("no batchmanager for that cluster"); + + Batch::Job* job = _jobmap[ pair(clustername,id) ]; + + _batchmap[clustername]->importOutputFiles( *job, directory ); +} + +//============================================================================= +/*! + * Factory to instanciate the good batch manager for choosen cluster. + */ +//============================================================================= + +Batch::BatchManager_eClient *Launcher_cpp::FactoryBatchManager( const ParserResourcesType& params ) throw(LauncherException) +{ + + std::string hostname, protocol, mpi; + Batch::FactBatchManager_eClient* fact; + + hostname = params.Alias; + switch(params.Protocol){ + case rsh: + protocol = "rsh"; + break; + case ssh: + protocol = "ssh"; + break; + default: + throw LauncherException("unknown protocol"); + break; + } + switch(params.mpi){ + case lam: + mpi = "lam"; + break; + case mpich1: + mpi = "mpich1"; + break; + case mpich2: + mpi = "mpich2"; + break; + case openmpi: + mpi = "openmpi"; + break; + case slurm: + mpi = "slurm"; + break; + default: + mpi = "indif"; + break; + } + cerr << "Instanciation of batch manager" << endl; + switch( params.Batch ){ + case pbs: + cerr << "Instantiation of PBS batch manager" << endl; + fact = new Batch::FactBatchManager_ePBS; + break; + case lsf: + cerr << "Instantiation of LSF batch manager" << endl; + fact = new Batch::FactBatchManager_eLSF; + break; + default: + cerr << "BATCH = " << params.Batch << endl; + throw LauncherException("no batchmanager for that cluster"); + } + return (*fact)(hostname.c_str(),protocol.c_str(),mpi.c_str()); +} + +string Launcher_cpp::buildSalomeCouplingScript(const string fileToExecute, const string dirForTmpFiles, const ParserResourcesType& params) +{ + int idx = dirForTmpFiles.find("Batch/"); + std::string filelogtemp = dirForTmpFiles.substr(idx+6, dirForTmpFiles.length()); + + string::size_type p1 = fileToExecute.find_last_of("/"); + string::size_type p2 = fileToExecute.find_last_of("."); + std::string fileNameToExecute = fileToExecute.substr(p1+1,p2-p1-1); + std::string TmpFileName = "/tmp/runSalome_" + fileNameToExecute + ".sh"; + + MpiImpl* mpiImpl = FactoryMpiImpl(params.mpi); + + ofstream tempOutputFile; + tempOutputFile.open(TmpFileName.c_str(), ofstream::out ); + + // Begin + tempOutputFile << "#! /bin/sh -f" << endl ; + tempOutputFile << "cd " ; + tempOutputFile << params.AppliPath << endl ; + tempOutputFile << "export SALOME_BATCH=1\n"; + tempOutputFile << "export PYTHONPATH=~/" ; + tempOutputFile << dirForTmpFiles ; + tempOutputFile << ":$PYTHONPATH" << endl ; + + // Test node rank + tempOutputFile << "if test " ; + tempOutputFile << mpiImpl->rank() ; + tempOutputFile << " = 0; then" << endl ; + + // ----------------------------------------------- + // Code for rank 0 : launch runAppli and a container + // RunAppli + if(params.ModulesList.size()>0) + tempOutputFile << " ./runAppli --terminal --modules=" ; + else + tempOutputFile << " ./runAppli --terminal "; + for ( int i = 0 ; i < params.ModulesList.size() ; i++ ) { + tempOutputFile << params.ModulesList[i] ; + if ( i != params.ModulesList.size()-1 ) + tempOutputFile << "," ; + } + tempOutputFile << " --standalone=registry,study,moduleCatalog --ns-port-log=" + << filelogtemp + << " &\n"; + + // Wait NamingService + tempOutputFile << " current=0\n" + << " stop=20\n" + << " while ! test -f " << filelogtemp << "\n" + << " do\n" + << " sleep 2\n" + << " let current=current+1\n" + << " if [ \"$current\" -eq \"$stop\" ] ; then\n" + << " echo Error Naming Service failed ! >&2" + << " exit\n" + << " fi\n" + << " done\n" + << " port=`cat " << filelogtemp << "`\n"; + + // Wait other containers + tempOutputFile << " for ((ip=1; ip < "; + tempOutputFile << mpiImpl->size(); + tempOutputFile << " ; ip++))" << endl; + tempOutputFile << " do" << endl ; + tempOutputFile << " arglist=\"$arglist YACS_Server_\"$ip" << endl ; + tempOutputFile << " done" << endl ; + tempOutputFile << " sleep 5" << endl ; + tempOutputFile << " ./runSession waitContainers.py $arglist" << endl ; + + // Launch user script + tempOutputFile << " ./runSession python ~/" << dirForTmpFiles << "/" << fileNameToExecute << ".py" << endl; + + // Stop application + tempOutputFile << " rm " << filelogtemp << "\n" + << " ./runSession shutdownSalome.py" << endl; + + // ------------------------------------- + // Other nodes launch a container + tempOutputFile << "else" << endl ; + + // Wait NamingService + tempOutputFile << " current=0\n" + << " stop=20\n" + << " while ! test -f " << filelogtemp << "\n" + << " do\n" + << " sleep 2\n" + << " let current=current+1\n" + << " if [ \"$current\" -eq \"$stop\" ] ; then\n" + << " echo Error Naming Service failed ! >&2" + << " exit\n" + << " fi\n" + << " done\n" + << " port=`cat " << filelogtemp << "`\n"; + + // Launching container + tempOutputFile << " ./runSession SALOME_Container YACS_Server_"; + tempOutputFile << mpiImpl->rank() + << " > ~/" << dirForTmpFiles << "/YACS_Server_" + << mpiImpl->rank() << "_container_log." << filelogtemp + << " 2>&1\n"; + tempOutputFile << "fi" << endl ; + tempOutputFile.flush(); + tempOutputFile.close(); + chmod(TmpFileName.c_str(), 0x1ED); + cerr << TmpFileName.c_str() << endl; + + delete mpiImpl; + + return TmpFileName; + +} + +MpiImpl *Launcher_cpp::FactoryMpiImpl(MpiImplType mpi) throw(LauncherException) +{ + switch(mpi){ + case lam: + return new MpiImpl_LAM(); + case mpich1: + return new MpiImpl_MPICH1(); + case mpich2: + return new MpiImpl_MPICH2(); + case openmpi: + return new MpiImpl_OPENMPI(); + case slurm: + return new MpiImpl_SLURM(); + case indif: + throw LauncherException("you must specify a mpi implementation in CatalogResources.xml file"); + default: + ostringstream oss; + oss << mpi << " : not yet implemented"; + throw LauncherException(oss.str().c_str()); + } + +} + +string Launcher_cpp::getTmpDirForBatchFiles() +{ + string ret; + string thedate; + + // Adding date to the directory name + Batch::Date date = Batch::Date(time(0)); + thedate = date.str(); + int lend = thedate.size() ; + int i = 0 ; + while ( i < lend ) { + if ( thedate[i] == '/' || thedate[i] == '-' || thedate[i] == ':' ) { + thedate[i] = '_' ; + } + i++ ; + } + + ret = string("Batch/"); + ret += thedate; + return ret; +} + +string Launcher_cpp::getRemoteFile( std::string remoteDir, std::string localFile ) +{ + string::size_type pos = localFile.find_last_of("/") + 1; + int ln = localFile.length() - pos; + string remoteFile = remoteDir + "/" + localFile.substr(pos,ln); + return remoteFile; +} + +bool Launcher_cpp::check(const batchParams& batch_params) +{ + bool rtn = true; + cerr << "Job parameters are :" << endl; + cerr << "Directory : $HOME/Batch/$date" << endl; + + // check expected_during_time (check the format) + std::string edt_info = batch_params.expected_during_time; + std::string edt_value = batch_params.expected_during_time; + if (edt_value != "") { + std::string begin_edt_value = edt_value.substr(0, 2); + std::string mid_edt_value = edt_value.substr(2, 1); + std::string end_edt_value = edt_value.substr(3); + + long value; + std::istringstream iss(begin_edt_value); + if (!(iss >> value)) { + edt_info = "Error on definition ! : " + edt_value; + rtn = false; + } + else if (value < 0) { + edt_info = "Error on definition time is negative ! : " + value; + rtn = false; + } + std::istringstream iss_2(end_edt_value); + if (!(iss_2 >> value)) { + edt_info = "Error on definition ! : " + edt_value; + rtn = false; + } + else if (value < 0) { + edt_info = "Error on definition time is negative ! : " + value; + rtn = false; + } + if (mid_edt_value != ":") { + edt_info = "Error on definition ! :" + edt_value; + rtn = false; + } + } + else { + edt_info = "No value given"; + } + cerr << "Expected during time : " << edt_info << endl;; + + // check memory (check the format) + std::string mem_info; + std::string mem_value = batch_params.mem; + if (mem_value != "") { + std::string begin_mem_value = mem_value.substr(0, mem_value.length()-2); + long re_mem_value; + std::istringstream iss(begin_mem_value); + if (!(iss >> re_mem_value)) { + mem_info = "Error on definition ! : " + mem_value; + rtn = false; + } + else if (re_mem_value <= 0) { + mem_info = "Error on definition memory is negative ! : " + mem_value; + rtn = false; + } + std::string end_mem_value = mem_value.substr(mem_value.length()-2); + if (end_mem_value != "gb" and end_mem_value != "mb") { + mem_info = "Error on definition, type is bad ! " + mem_value; + rtn = false; + } + } + else { + mem_info = "No value given"; + } + cerr << "Memory : " << mem_info << endl; + + // check nb_proc + std::string nb_proc_info; + ostringstream nb_proc_value; + nb_proc_value << batch_params.nb_proc; + if(batch_params.nb_proc <= 0) { + nb_proc_info = "Bad value ! nb_proc = "; + nb_proc_info += nb_proc_value.str(); + rtn = false; + } + else { + nb_proc_info = nb_proc_value.str(); + } + cerr << "Nb of processors : " << nb_proc_info << endl; + + return rtn; +} + +long Launcher_cpp::getWallTime(std::string edt) +{ + long hh, mm, ret; + + if( edt.size() == 0 ) + return 0; + + string::size_type pos = edt.find(":"); + string h = edt.substr(0,pos); + string m = edt.substr(pos+1,edt.size()-pos+1); + istringstream issh(h); + issh >> hh; + istringstream issm(m); + issm >> mm; + ret = hh*60 + mm; + return ret; +} + +long Launcher_cpp::getRamSize(std::string mem) +{ + long mv; + + if( mem.size() == 0 ) + return 0; + + string ram = mem.substr(0,mem.size()-2); + istringstream iss(ram); + iss >> mv; + string unity = mem.substr(mem.size()-2,2); + if( (unity.find("gb") != string::npos) || (unity.find("GB") != string::npos) ) + return mv*1024; + else if( (unity.find("mb") != string::npos) || (unity.find("MB") != string::npos) ) + return mv; + else if( (unity.find("kb") != string::npos) || (unity.find("KB") != string::npos) ) + return mv/1024; + else if( (unity.find("b") != string::npos) || (unity.find("B") != string::npos) ) + return mv/(1024*1024); + else + return 0; +} + +std::string +Launcher_cpp::getHomeDir(const ParserResourcesType& p, const std::string& tmpdir) +{ + std::string home; + std::string command; + int idx = tmpdir.find("Batch/"); + std::string filelogtemp = tmpdir.substr(idx+6, tmpdir.length()); + filelogtemp = "/tmp/logs" + filelogtemp + "_home"; + + if( p.Protocol == rsh ) + command = "rsh "; + else if( p.Protocol == ssh ) + command = "ssh "; + else + throw LauncherException("Unknown protocol"); + if (p.UserName != ""){ + command += p.UserName; + command += "@"; + } + command += p.Alias; + command += " 'echo $HOME' > "; + command += filelogtemp; + std::cerr << command.c_str() << std::endl; + int status = system(command.c_str()); + if(status) + throw LauncherException("Error of launching home command on remote host"); + + std::ifstream file_home(filelogtemp.c_str()); + std::getline(file_home, home); + file_home.close(); + return home; +} diff --git a/src/Launcher/Launcher.hxx b/src/Launcher/Launcher.hxx new file mode 100644 index 000000000..84dd59dd5 --- /dev/null +++ b/src/Launcher/Launcher.hxx @@ -0,0 +1,79 @@ +// Copyright (C) 2005 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN, +// CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License. +// +// This library is distributed in the hope that it will be useful +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// +#ifndef __LAUNCHER_HXX__ +#define __LAUNCHER_HXX__ + +#include "Batch_BatchManager_eClient.hxx" +#include "ResourcesManager.hxx" + +#include + +struct batchParams{ + std::string batch_directory; + std::string expected_during_time; + std::string mem; + unsigned long nb_proc; +}; + +class LauncherException +{ +public: + const std::string msg; + + LauncherException(const std::string m) : msg(m) {} +}; + +class Launcher_cpp +{ + +public: + Launcher_cpp(); + ~Launcher_cpp(); + + long submitSalomeJob(const std::string fileToExecute , + const std::vector& filesToExport , + const std::vector& filesToImport , + const batchParams& batch_params, + const machineParams& params) throw(LauncherException); + + std::string querySalomeJob( const long jobId, const machineParams& params) throw(LauncherException); + void deleteSalomeJob( const long jobId, const machineParams& params) throw(LauncherException); + void getResultSalomeJob( const std::string directory, const long jobId, const machineParams& params ) throw(LauncherException); + + void SetResourcesManager( ResourcesManager_cpp* rm ) { _ResManager = rm; } + +protected: + + std::string buildSalomeCouplingScript(const string fileToExecute, const string dirForTmpFiles, const ParserResourcesType& params); + MpiImpl *FactoryMpiImpl(MpiImplType mpiImpl) throw(LauncherException); + Batch::BatchManager_eClient *FactoryBatchManager( const ParserResourcesType& params ) throw(LauncherException); + std::string getTmpDirForBatchFiles(); + std::string getRemoteFile( std::string remoteDir, std::string localFile ); + std::string getHomeDir(const ParserResourcesType& p, const std::string & tmpdir); + + std::map _batchmap; + std::map < std::pair , Batch::Job* > _jobmap; + ResourcesManager_cpp *_ResManager; + bool check(const batchParams& batch_params); + long getWallTime(std::string edt); + long getRamSize(std::string mem); +}; + +#endif diff --git a/src/ParallelContainer/SALOME_ParallelContainerProxy_i.cxx b/src/ParallelContainer/SALOME_ParallelContainerProxy_i.cxx new file mode 100644 index 000000000..c88588c00 --- /dev/null +++ b/src/ParallelContainer/SALOME_ParallelContainerProxy_i.cxx @@ -0,0 +1,43 @@ +// SALOME_ParallelContainerProxy : implementation of container and engine for Parallel Kernel +// +// Copyright (C) 2008 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN, +// CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.opencascade.org/SALOME/ or email : webmaster.salome@opencascade.org +// +// File : SALOME_ParallelContainerProxy_i.cxx +// Author : André RIBES, EDF + +#include "SALOME_ParallelContainerProxy_i.hxx" + +Container_proxy_impl_final::Container_proxy_impl_final(CORBA::ORB_ptr orb, + paco_fabrique_thread * fab_thread, + bool is_a_return_proxy) : + Engines::Container_proxy_impl(orb, fab_thread, is_a_return_proxy), + InterfaceManager_impl(orb, fab_thread, is_a_return_proxy) +{} + +Container_proxy_impl_final:: ~Container_proxy_impl_final() {} + +void +Container_proxy_impl_final::Shutdown() +{ + INFOS("Shutdown Parallel Proxy"); + if(!CORBA::is_nil(_orb)) + _orb->shutdown(0); +} + diff --git a/src/ParallelContainer/SALOME_ParallelContainerProxy_i.hxx b/src/ParallelContainer/SALOME_ParallelContainerProxy_i.hxx new file mode 100644 index 000000000..77d59144f --- /dev/null +++ b/src/ParallelContainer/SALOME_ParallelContainerProxy_i.hxx @@ -0,0 +1,44 @@ +// SALOME_ParallelContainerProxy : implementation of container and engine for Parallel Kernel +// +// Copyright (C) 2008 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN, +// CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.opencascade.org/SALOME/ or email : webmaster.salome@opencascade.org +// +// File : SALOME_ParallelContainerProxy_i.hxx +// Author : André RIBES, EDF + +#ifndef _SALOME_PARALLEL_CONTAINER_PROXY_I_HXX_ +#define _SALOME_PARALLEL_CONTAINER_PROXY_I_HXX_ + +#include "utilities.h" +#include "SALOME_ComponentPaCO_Engines_Container_server.h" + +class Container_proxy_impl_final : + public Engines::Container_proxy_impl +{ + public: + Container_proxy_impl_final(CORBA::ORB_ptr orb, + paco_fabrique_thread * fab_thread, + bool is_a_return_proxy = false); + + virtual ~Container_proxy_impl_final(); + + virtual void Shutdown(); +}; + +#endif diff --git a/src/ResourcesManager/ResourcesManager.cxx b/src/ResourcesManager/ResourcesManager.cxx new file mode 100644 index 000000000..ddf3712ff --- /dev/null +++ b/src/ResourcesManager/ResourcesManager.cxx @@ -0,0 +1,488 @@ +// Copyright (C) 2005 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN, +// CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License. +// +// This library is distributed in the hope that it will be useful +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// +#include "ResourcesManager.hxx" +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "utilities.h" + +#define MAX_SIZE_FOR_HOSTNAME 256; + +using namespace std; + +//============================================================================= +/*! + * just for test + */ +//============================================================================= + +ResourcesManager_cpp:: +ResourcesManager_cpp(const char *xmlFilePath) : + _path_resources(xmlFilePath) +{ + MESSAGE ( "ResourcesManager_cpp constructor" ); +} + +//============================================================================= +/*! + * Standard constructor, parse resource file. + * - if ${APPLI} exists in environment, + * look for ${HOME}/${APPLI}/CatalogResources.xml + * - else look for default: + * ${KERNEL_ROOT_DIR}/share/salome/resources/kernel/CatalogResources.xml + * - parse XML resource file. + */ +//============================================================================= + +ResourcesManager_cpp::ResourcesManager_cpp() +{ + MESSAGE ( "ResourcesManager_cpp constructor" ); + _isAppliSalomeDefined = (getenv("APPLI") != 0); + + if (_isAppliSalomeDefined) + { + _path_resources = getenv("HOME"); + _path_resources += "/"; + _path_resources += getenv("APPLI"); + _path_resources += "/CatalogResources.xml"; + } + + else + { + _path_resources = getenv("KERNEL_ROOT_DIR"); + _path_resources += "/share/salome/resources/kernel/CatalogResources.xml"; + } + + ParseXmlFile(); + MESSAGE ( "ResourcesManager_cpp constructor end" ); +} + +//============================================================================= +/*! + * Standard Destructor + */ +//============================================================================= + +ResourcesManager_cpp::~ResourcesManager_cpp() +{ + MESSAGE ( "ResourcesManager_cpp destructor" ); +} + +//============================================================================= +/*! + * get the list of name of ressources fitting for the specified module. + * If hostname specified, check it is local or known in resources catalog. + * + * Else + * - select first machines with corresponding OS (all machines if + * parameter OS empty), + * - then select the sublist of machines on witch the module is known + * (if the result is empty, that probably means that the inventory of + * modules is probably not done, so give complete list from previous step) + */ +//============================================================================= + +std::vector +ResourcesManager_cpp::GetFittingResources(const machineParams& params, + const std::vector& componentList) throw(ResourcesException) +{ +// cerr << "ResourcesManager_cpp::GetFittingResources" << endl; + vector vec; + + ParseXmlFile(); + + const char *hostname = params.hostname.c_str(); + INFOS ( "GetFittingResources " << hostname << " " << GetHostname().c_str() ); + + if (hostname[0] != '\0'){ + // cerr << "ResourcesManager_cpp::GetFittingResources : hostname specified" << endl; + + if ( strcmp(hostname, "localhost") == 0 || + strcmp(hostname, GetHostname().c_str()) == 0 ) + { + // cerr << "ResourcesManager_cpp::GetFittingResources : localhost" << endl; + vec.push_back(GetHostname().c_str()); + // cerr << "ResourcesManager_cpp::GetFittingResources : " << vec.size() << endl; + } + + else if (_resourcesList.find(hostname) != _resourcesList.end()) + { + // --- params.hostname is in the list of resources so return it. + vec.push_back(hostname); + } + else if (_resourcesBatchList.find(hostname) != _resourcesBatchList.end()) + { + // --- params.hostname is in the list of resources so return it. + vec.push_back(hostname); + } + else + { + // Cas d'un cluster: nombre de noeuds > 1 + int cpt=0; + for (map::const_iterator iter = _resourcesList.begin(); iter != _resourcesList.end(); iter++){ + if( (*iter).second.DataForSort._nbOfNodes > 1 ){ + if( strncmp(hostname,(*iter).first.c_str(),strlen(hostname)) == 0 ){ + vec.push_back((*iter).first.c_str()); + //cerr << "SALOME_ResourcesManager_cpp::GetFittingResources vector[" + // << cpt << "] = " << (*iter).first.c_str() << endl ; + cpt++; + } + } + } + if(cpt==0){ + // --- user specified an unknown hostame so notify him. + MESSAGE ( "ResourcesManager_cpp::GetFittingResources : SALOME_Exception" ); + throw ResourcesException("unknown host"); + } + } + } + + else{ + // --- Search for available resources sorted by priority + SelectOnlyResourcesWithOS(vec, params.OS.c_str()); + + KeepOnlyResourcesWithModule(vec, componentList); + + if (vec.size() == 0) + SelectOnlyResourcesWithOS(vec, params.OS.c_str()); + + // --- set wanted parameters + ResourceDataToSort::_nbOfNodesWanted = params.nb_node; + + ResourceDataToSort::_nbOfProcPerNodeWanted = params.nb_proc_per_node; + + ResourceDataToSort::_CPUFreqMHzWanted = params.cpu_clock; + + ResourceDataToSort::_memInMBWanted = params.mem_mb; + + // --- end of set + + list li; + + for (vector::iterator iter = vec.begin(); + iter != vec.end(); + iter++) + li.push_back(_resourcesList[(*iter)].DataForSort); + + li.sort(); + + unsigned int i = 0; + + for (list::iterator iter2 = li.begin(); + iter2 != li.end(); + iter2++) + vec[i++] = (*iter2)._hostName; + } + + return vec; + +} + +//============================================================================= +/*! + * add an entry in the ressources catalog xml file. + * Return 0 if OK (KERNEL found in new resources modules) else throw exception + */ +//============================================================================= + +int +ResourcesManager_cpp:: +AddResourceInCatalog(const machineParams& paramsOfNewResources, + const vector& modulesOnNewResources, + const char *alias, + const char *userName, + AccessModeType mode, + AccessProtocolType prot) +throw(ResourcesException) +{ + vector::const_iterator iter = find(modulesOnNewResources.begin(), + modulesOnNewResources.end(), + "KERNEL"); + + if (iter != modulesOnNewResources.end()) + { + ParserResourcesType newElt; + newElt.DataForSort._hostName = paramsOfNewResources.hostname; + newElt.Alias = alias; + newElt.Protocol = prot; + newElt.Mode = mode; + newElt.UserName = userName; + newElt.ModulesList = modulesOnNewResources; + newElt.OS = paramsOfNewResources.OS; + newElt.DataForSort._memInMB = paramsOfNewResources.mem_mb; + newElt.DataForSort._CPUFreqMHz = paramsOfNewResources.cpu_clock; + newElt.DataForSort._nbOfNodes = paramsOfNewResources.nb_node; + newElt.DataForSort._nbOfProcPerNode = + paramsOfNewResources.nb_proc_per_node; + _resourcesList[newElt.DataForSort._hostName] = newElt; + return 0; + } + + else + throw ResourcesException("KERNEL is not present in this resource"); +} + +//============================================================================= +/*! + * Deletes a resource from the catalog + */ +//============================================================================= + +void ResourcesManager_cpp::DeleteResourceInCatalog(const char *hostname) +{ + _resourcesList.erase(hostname); +} + +//============================================================================= +/*! + * write the current data in memory in file. + */ +//============================================================================= + +void ResourcesManager_cpp::WriteInXmlFile() +{ + const char* aFilePath = _path_resources.c_str(); + + FILE* aFile = fopen(aFilePath, "w"); + + if (aFile == NULL) + { + INFOS ( "Error opening file !" ); + return; + } + + xmlDocPtr aDoc = xmlNewDoc(BAD_CAST "1.0"); + xmlNewDocComment(aDoc, BAD_CAST "ResourcesCatalog"); + + SALOME_ResourcesCatalog_Handler* handler = + new SALOME_ResourcesCatalog_Handler(_resourcesList, _resourcesBatchList); + handler->PrepareDocToXmlFile(aDoc); + delete handler; + + int isOk = xmlSaveFile(aFilePath, aDoc); + + if (!isOk) + INFOS ( "Error while XML file saving." ); + + // Free the document + xmlFreeDoc(aDoc); + + fclose(aFile); + + INFOS ( "WRITING DONE!" ); +} + +//============================================================================= +/*! + * parse the data type catalog + */ +//============================================================================= + +const MapOfParserResourcesType& ResourcesManager_cpp::ParseXmlFile() +{ + SALOME_ResourcesCatalog_Handler* handler = + new SALOME_ResourcesCatalog_Handler(_resourcesList, _resourcesBatchList); + + const char* aFilePath = _path_resources.c_str(); + FILE* aFile = fopen(aFilePath, "r"); + + if (aFile != NULL) + { + xmlDocPtr aDoc = xmlReadFile(aFilePath, NULL, 0); + + if (aDoc != NULL) + handler->ProcessXmlDocument(aDoc); + else + INFOS ( "ResourcesManager_cpp: could not parse file "<< aFilePath ); + + // Free the document + xmlFreeDoc(aDoc); + + fclose(aFile); + } + else + INFOS ( "ResourcesManager_cpp: file "<& listOfMachines) +{ + return _dynamicResourcesSelecter.FindFirst(listOfMachines); +} + +//============================================================================= +/*! + * dynamically obtains the best machines + */ +//============================================================================= + +string ResourcesManager_cpp::FindNext(const std::vector& listOfMachines) +{ + return _dynamicResourcesSelecter.FindNext(listOfMachines,_resourcesList); +} +//============================================================================= +/*! + * dynamically obtains the best machines + */ +//============================================================================= + +string ResourcesManager_cpp::FindBest(const std::vector& listOfMachines) +{ + return _dynamicResourcesSelecter.FindBest(listOfMachines); +} + +//============================================================================= +/*! + * Gives a sublist of machines with matching OS. + * If parameter OS is empty, gives the complete list of machines + */ +//============================================================================= + +// Warning need an updated parsed list : _resourcesList +void ResourcesManager_cpp::SelectOnlyResourcesWithOS( vector& hosts, const char *OS) const +throw(ResourcesException) +{ + string base(OS); + + for (map::const_iterator iter = + _resourcesList.begin(); + iter != _resourcesList.end(); + iter++) + { + if ( (*iter).second.OS == base || base.size() == 0) + hosts.push_back((*iter).first); + } +} + + +//============================================================================= +/*! + * Gives a sublist of machines on which the module is known. + */ +//============================================================================= + +//Warning need an updated parsed list : _resourcesList +void ResourcesManager_cpp::KeepOnlyResourcesWithModule( vector& hosts, const vector& componentList) const +throw(ResourcesException) +{ + for (vector::iterator iter = hosts.begin(); iter != hosts.end();) + { + MapOfParserResourcesType::const_iterator it = _resourcesList.find(*iter); + const vector& mapOfModulesOfCurrentHost = (((*it).second).ModulesList); + + bool erasedHost = false; + if( mapOfModulesOfCurrentHost.size() > 0 ){ + for(int i=0;i::const_iterator itt = find(mapOfModulesOfCurrentHost.begin(), + mapOfModulesOfCurrentHost.end(), + compoi); +// componentList[i]); + if (itt == mapOfModulesOfCurrentHost.end()){ + erasedHost = true; + break; + } + } + } + if(erasedHost) + hosts.erase(iter); + else + iter++; + } +} + + +ParserResourcesType ResourcesManager_cpp::GetResourcesList(const std::string& machine) +{ + if (_resourcesList.find(machine) != _resourcesList.end()) + return _resourcesList[machine]; + else + return _resourcesBatchList[machine]; +} + +std::string ResourcesManager_cpp::GetHostname() +{ + int ls = 100, r = 1; + char *s; + + while (ls < 10000 && r) { + ls *= 2; + s = new char[ls]; + r = gethostname(s, ls-1); + switch (r) + { + case 0: + break; + default: +#ifdef EINVAL + case EINVAL: +#endif +#ifdef ENAMETOOLONG + case ENAMETOOLONG: +#endif + delete [] s; + continue; + } + } + + if (r != 0) { + s = new char[50]; + strcpy(s, "localhost"); + } + + // remove all after '.' + char *aDot = (strchr(s,'.')); + if (aDot) aDot[0] = '\0'; + + string p = s; + delete [] s; + return p; +} + diff --git a/src/ResourcesManager/ResourcesManager.hxx b/src/ResourcesManager/ResourcesManager.hxx new file mode 100644 index 000000000..951aba5da --- /dev/null +++ b/src/ResourcesManager/ResourcesManager.hxx @@ -0,0 +1,116 @@ +// Copyright (C) 2005 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN, +// CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License. +// +// This library is distributed in the hope that it will be useful +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// +#ifndef __RESOURCESMANAGER_HXX__ +#define __RESOURCESMANAGER_HXX__ + +#include +#include +#include +#include "SALOME_ResourcesCatalog_Parser.hxx" +#include "SALOME_ResourcesCatalog_Handler.hxx" +#include "SALOME_LoadRateManager.hxx" + +// --- WARNING --- +// The call of BuildTempFileToLaunchRemoteContainer and RmTmpFile must be done +// in a critical section to be sure to be clean. +// Only one thread should use the SALOME_ResourcesManager class in a SALOME +// session. + +struct machineParams{ + std::string hostname; + std::string OS; + unsigned int nb_node; + unsigned int nb_proc_per_node; + unsigned int cpu_clock; + unsigned int mem_mb; +}; + +class ResourcesException +{ +public: + const std::string msg; + + ResourcesException(const std::string m) : msg(m) {} +}; + +class ResourcesManager_cpp + { + + public: + + ResourcesManager_cpp(const char *xmlFilePath); + ResourcesManager_cpp(); + + ~ResourcesManager_cpp(); + + std::vector + GetFittingResources(const machineParams& params, + const std::vector& componentList) throw(ResourcesException); + + std::string FindFirst(const std::vector& listOfMachines); + std::string FindNext(const std::vector& listOfMachines); + std::string FindBest(const std::vector& listOfMachines); + + int AddResourceInCatalog + (const machineParams& paramsOfNewResources, + const std::vector& modulesOnNewResources, + const char *alias, + const char *userName, + AccessModeType mode, + AccessProtocolType prot) throw(ResourcesException); + + void DeleteResourceInCatalog(const char *hostname); + + void WriteInXmlFile(); + + const MapOfParserResourcesType& ParseXmlFile(); + + const MapOfParserResourcesType& GetList() const; + + ParserResourcesType GetResourcesList(const std::string& machine); + + protected: + + void SelectOnlyResourcesWithOS(std::vector& hosts, + const char *OS) const + throw(ResourcesException); + + void KeepOnlyResourcesWithModule(std::vector& hosts, + const std::vector& componentList) const + throw(ResourcesException); + + //! will contain the path to the ressources catalog + std::string _path_resources; + + //! will contain the informations on the data type catalog(after parsing) + MapOfParserResourcesType _resourcesList; + + //! will contain the informations on the data type catalog(after parsing) + MapOfParserResourcesType _resourcesBatchList; + + SALOME_LoadRateManager _dynamicResourcesSelecter; + + //! different behaviour if $APPLI exists (SALOME Application) + bool _isAppliSalomeDefined; + + std::string GetHostname(); + }; + +#endif // __RESOURCESMANAGER_HXX__ -- 2.39.2